Skip to content

Commit 6cd8791

Browse files
committed
feat: Epic, Task, Story 생성, 수정시 중복된 rankValue를 서버가 조정해, 조정된 rankValue를 반환하는 로직 구현
- Entity - 유니크 제약조건 이름 명시 - Repository - Create 메서드를 Unique오류이며, RankValue중복일 경우 커스텀 에러를 throw하게 변경 - Update 메서드를 Unique오류이며, RankValue중복일 경우 커스텀 에러를 throw하게 변경 - getNext[Epic/Task/Story]ByRankValue 메서드 구현 - 특정 rankValue값 다음 순서의 rankValue를 가진 엔티티 반환 - Controller - update시 조정된 rankValue값을 반영해 유저에게 반환하도록 수정 - Test - Epic/Task/Story 생성 시 동시에 중복된 rankValue를 전송했을때 조정되는것 테스트 - Epic/Task/Story 변경 시 동시에 중복된 rankValue를 전송했을때 조정되는것 테스트
1 parent ec55097 commit 6cd8791

File tree

11 files changed

+757
-90
lines changed

11 files changed

+757
-90
lines changed

backend/src/project/entity/epic.entity.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export enum EpicColor {
2323
}
2424

2525
@Entity()
26-
@Unique(['rankValue', 'projectId'])
26+
@Unique('EPIC_UQ_RANK_VALUE_AND_PROJECT_ID', ['rankValue', 'projectId'])
2727
export class Epic {
2828
@PrimaryGeneratedColumn('increment', { type: 'int' })
2929
id: number;

backend/src/project/entity/story.entity.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export enum StoryStatus {
1818
}
1919

2020
@Entity()
21-
@Unique(['rankValue', 'epicId'])
21+
@Unique('STORY_UQ_RANK_VALUE_AND_EPIC_ID', ['rankValue', 'epicId'])
2222
export class Story {
2323
@PrimaryGeneratedColumn('increment', { type: 'int' })
2424
id: number;

backend/src/project/entity/task.entity.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export enum TaskStatus {
1717
}
1818

1919
@Entity()
20-
@Unique(['rankValue', 'storyId'])
20+
@Unique('TASK_UQ_RANK_VALUE_AND_STORY_ID', ['rankValue', 'storyId'])
2121
export class Task {
2222
@PrimaryGeneratedColumn('increment', { type: 'int' })
2323
id: number;

backend/src/project/project.repository.ts

Lines changed: 108 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { InjectRepository } from '@nestjs/typeorm';
2-
import { Repository } from 'typeorm';
2+
import { Connection, DataSource, MoreThan, Repository } from 'typeorm';
33
import { Injectable } from '@nestjs/common';
44
import { Project } from './entity/project.entity';
55
import { ProjectToMember } from './entity/project-member.entity';
@@ -9,6 +9,7 @@ import { Link } from './entity/link.entity.';
99
import { Epic, EpicColor } from './entity/epic.entity';
1010
import { Story, StoryStatus } from './entity/story.entity';
1111
import { Task, TaskStatus } from './entity/task.entity';
12+
import { LexoRank } from 'lexorank';
1213

1314
@Injectable()
1415
export class ProjectRepository {
@@ -29,6 +30,7 @@ export class ProjectRepository {
2930
private readonly storyRepository: Repository<Story>,
3031
@InjectRepository(Task)
3132
private readonly taskRepository: Repository<Task>,
33+
private readonly dataSource: DataSource,
3234
) {}
3335

3436
create(project: Project): Promise<Project> {
@@ -117,8 +119,27 @@ export class ProjectRepository {
117119
return result.affected ? result.affected : 0;
118120
}
119121

120-
createEpic(epic: Epic): Promise<Epic> {
121-
return this.epicRepository.save(epic);
122+
async createEpic(epic: Epic): Promise<Epic> {
123+
try {
124+
return await this.epicRepository.save(epic);
125+
} catch (e) {
126+
if (
127+
e.code === 'ER_DUP_ENTRY' &&
128+
e.sqlMessage.includes('EPIC_UQ_RANK_VALUE_AND_PROJECT_ID')
129+
)
130+
throw new Error('DUPLICATED RANK VALUE');
131+
throw e;
132+
}
133+
}
134+
135+
getNextEpicByRankValue(projectId: number, rankValue: string) {
136+
return this.epicRepository.findOne({
137+
where: {
138+
projectId,
139+
rankValue: MoreThan(rankValue),
140+
},
141+
order: { rankValue: 'ASC' },
142+
});
122143
}
123144

124145
async deleteEpic(project: Project, epicId: number): Promise<number> {
@@ -149,11 +170,20 @@ export class ProjectRepository {
149170
updateData.rankValue = rankValue;
150171
}
151172

152-
const result = await this.epicRepository.update(
153-
{ id, project: { id: project.id } },
154-
updateData,
155-
);
156-
return !!result.affected;
173+
try {
174+
const result = await this.epicRepository.update(
175+
{ id, project: { id: project.id } },
176+
updateData,
177+
);
178+
return !!result.affected;
179+
} catch (e) {
180+
if (
181+
e.code === 'ER_DUP_ENTRY' &&
182+
e.sqlMessage.includes('EPIC_UQ_RANK_VALUE_AND_PROJECT_ID')
183+
)
184+
throw new Error('DUPLICATED RANK VALUE');
185+
throw e;
186+
}
157187
}
158188

159189
getEpicById(project: Project, id: number) {
@@ -162,8 +192,27 @@ export class ProjectRepository {
162192
});
163193
}
164194

165-
createStory(story: Story): Promise<Story> {
166-
return this.storyRepository.save(story);
195+
async createStory(story: Story): Promise<Story> {
196+
try {
197+
return await this.storyRepository.save(story);
198+
} catch (e) {
199+
if (
200+
e.code === 'ER_DUP_ENTRY' &&
201+
e.sqlMessage.includes('STORY_UQ_RANK_VALUE_AND_EPIC_ID')
202+
)
203+
throw new Error('DUPLICATED RANK VALUE');
204+
throw e;
205+
}
206+
}
207+
208+
getNextStoryByRankValue(epicId: number, rankValue: string) {
209+
return this.storyRepository.findOne({
210+
where: {
211+
epicId,
212+
rankValue: MoreThan(rankValue),
213+
},
214+
order: { rankValue: 'ASC' },
215+
});
167216
}
168217

169218
async deleteStory(project: Project, storyId: number): Promise<number> {
@@ -201,11 +250,20 @@ export class ProjectRepository {
201250
updateData.rankValue = rankValue;
202251
}
203252

204-
const result = await this.storyRepository.update(
205-
{ id, project: { id: project.id } },
206-
updateData,
207-
);
208-
return !!result.affected;
253+
try {
254+
const result = await this.storyRepository.update(
255+
{ id, project: { id: project.id } },
256+
updateData,
257+
);
258+
return !!result.affected;
259+
} catch (e) {
260+
if (
261+
e.code === 'ER_DUP_ENTRY' &&
262+
e.sqlMessage.includes('STORY_UQ_RANK_VALUE_AND_EPIC_ID')
263+
)
264+
throw new Error('DUPLICATED RANK VALUE');
265+
throw e;
266+
}
209267
}
210268

211269
getStoryById(project: Project, id: number) {
@@ -224,8 +282,27 @@ export class ProjectRepository {
224282
return targetProject.displayIdCount;
225283
}
226284

227-
async createTask(task: Task) {
228-
return this.taskRepository.save(task);
285+
async createTask(task: Task): Promise<Task> {
286+
try {
287+
return await this.taskRepository.save(task);
288+
} catch (e) {
289+
if (
290+
e.code === 'ER_DUP_ENTRY' &&
291+
e.sqlMessage.includes('TASK_UQ_RANK_VALUE_AND_STORY_ID')
292+
)
293+
throw new Error('DUPLICATED RANK VALUE');
294+
throw e;
295+
}
296+
}
297+
298+
getNextTaskByRankValue(storyId: number, rankValue: string) {
299+
return this.taskRepository.findOne({
300+
where: {
301+
storyId,
302+
rankValue: MoreThan(rankValue),
303+
},
304+
order: { rankValue: 'ASC' },
305+
});
229306
}
230307

231308
async deleteTask(project: Project, taskId: number): Promise<number> {
@@ -271,11 +348,20 @@ export class ProjectRepository {
271348
updateData.rankValue = rankValue;
272349
}
273350

274-
const result = await this.taskRepository.update(
275-
{ id, project: { id: project.id } },
276-
updateData,
277-
);
278-
return !!result.affected;
351+
try {
352+
const result = await this.taskRepository.update(
353+
{ id, project: { id: project.id } },
354+
updateData,
355+
);
356+
return !!result.affected;
357+
} catch (e) {
358+
if (
359+
e.code === 'ER_DUP_ENTRY' &&
360+
e.sqlMessage.includes('TASK_UQ_RANK_VALUE_AND_STORY_ID')
361+
)
362+
throw new Error('DUPLICATED RANK VALUE');
363+
throw e;
364+
}
279365
}
280366

281367
getProjectBacklog(project: Project) {

0 commit comments

Comments
 (0)