Skip to content

Commit

Permalink
feat: 스토리 생성, 수정 API에 우선순위 데이터(rankValue)추가
Browse files Browse the repository at this point in the history
- story 엔티티
  - rankValue 프로퍼티 추가
  - 스토리의 rankValue가 project에서 고유하도록 유니크 제약조건 추가
- project 레포지토리, project 서비스의 story 관련 update, create 메서드에 rankValue정보 추가
- story 컨트롤러에 rankValue 정보 추가
- Story DTO에 rankValue 정보 추가
- E2E 테스트
  - 스토리, 태스크, 백로그 테스트에 스토리의 rankValue정보 추가
  - 스토리 테스트에 같은 에픽 내 rankValue 업데이트 테스트 추가
  - 스토리 테스트에 다른 에픽으로의 rankValue 업데이트 테스트 추가
  • Loading branch information
choyoungwoo9 committed Jul 30, 2024
1 parent c13ae07 commit 7c19a12
Show file tree
Hide file tree
Showing 11 changed files with 184 additions and 37 deletions.
3 changes: 3 additions & 0 deletions backend/src/project/dto/story/StoryCreateNotify.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ class StoryDto {
point: number;
status: StoryStatus;
epicId: number;
rankValue: string;

static of(story: Story) {
const dto = new StoryDto();
dto.id = story.id;
dto.title = story.title;
dto.point = story.point;
dto.status = story.status;
dto.epicId = story.epicId;
dto.rankValue = story.rankValue;
return dto;
}
}
Expand Down
6 changes: 6 additions & 0 deletions backend/src/project/dto/story/StoryCreateRequest.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
Min,
ValidateNested,
} from 'class-validator';
import { IsLexoRankValue } from 'src/common/decorator/IsLexoRankValue';
import { StoryStatus } from 'src/project/entity/story.entity';

class Story {
Expand All @@ -27,6 +28,11 @@ class Story {

@IsInt()
epicId: number;

@IsString()
@IsLexoRankValue()
@Length(2, 255)
rankValue: string;
}

export class StoryCreateRequestDto {
Expand Down
6 changes: 5 additions & 1 deletion backend/src/project/dto/story/StoryUpdateNotify.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,23 @@ class Story {
title?: string;
point?: number;
status?: StoryStatus;
rankValue?: string;

static of(
id: number,
epicId: number | undefined,
title: string | undefined,
point: number | undefined,
status: StoryStatus | undefined,
rankValue: string | undefined,
) {
const dto = new Story();
dto.id = id;
if (title !== undefined) dto.title = title;
if (point !== undefined) dto.point = point;
if (status !== undefined) dto.status = status;
if (epicId !== undefined) dto.epicId = epicId;
if (rankValue !== undefined) dto.rankValue = rankValue;
return dto;
}
}
Expand All @@ -35,11 +38,12 @@ export class StoryUpdateNotifyDto {
title: string | undefined,
point: number | undefined,
status: StoryStatus | undefined,
rankValue: string | undefined,
) {
const dto = new StoryUpdateNotifyDto();
dto.domain = 'story';
dto.action = 'update';
dto.content = Story.of(id, epicId, title, point, status);
dto.content = Story.of(id, epicId, title, point, status, rankValue);
return dto;
}
}
16 changes: 9 additions & 7 deletions backend/src/project/dto/story/StoryUpdateRequest.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,13 @@ import {
Min,
ValidateNested,
} from 'class-validator';
import { IsLexoRankValue } from 'src/common/decorator/IsLexoRankValue';
import { StoryStatus } from 'src/project/entity/story.entity';
import { AtLeastOneProperty } from 'src/project/util/validation.util';

class Story {
@IsInt()
@AtLeastOneProperty([
'epicId',
'title',
'point',
'status'
])
@AtLeastOneProperty(['epicId', 'title', 'point', 'status', 'rankValue'])
id: number;

@IsOptional()
Expand All @@ -32,7 +28,7 @@ class Story {
@IsString()
@Length(1, 100)
title?: string;

@IsOptional()
@IsInt()
@Min(0)
Expand All @@ -42,6 +38,12 @@ class Story {
@IsOptional()
@IsEnum(StoryStatus)
status?: StoryStatus;

@IsOptional()
@IsString()
@IsLexoRankValue()
@Length(2, 255)
rankValue?: string;
}

export class StoryUpdateRequestDto {
Expand Down
7 changes: 7 additions & 0 deletions backend/src/project/entity/story.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
ManyToOne,
OneToMany,
PrimaryGeneratedColumn,
Unique,
} from 'typeorm';
import { Epic } from './epic.entity';
import { Project } from './project.entity';
Expand All @@ -17,6 +18,7 @@ export enum StoryStatus {
}

@Entity()
@Unique(['rankValue', 'projectId'])
export class Story {
@PrimaryGeneratedColumn('increment', { type: 'int' })
id: number;
Expand Down Expand Up @@ -50,19 +52,24 @@ export class Story {
@OneToMany(() => Task, (task) => task.story)
taskList: Task[];

@Column({ type: 'varchar', length: 255, nullable: false, name: 'rank_value' })
rankValue: string;

static of(
project: Project,
epicId: number,
title: string,
point: number,
status: StoryStatus,
rankValue: string,
) {
const newStory = new Story();
newStory.project = project;
newStory.epicId = epicId;
newStory.title = title;
newStory.point = point;
newStory.status = status;
newStory.rankValue = rankValue;
return newStory;
}
}
4 changes: 4 additions & 0 deletions backend/src/project/project.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ export class ProjectRepository {
title: string | undefined,
point: number | undefined,
status: StoryStatus | undefined,
rankValue: string | undefined,
): Promise<boolean> {
const updateData: any = {};

Expand All @@ -196,6 +197,9 @@ export class ProjectRepository {
if (status !== undefined) {
updateData.status = status;
}
if (rankValue !== undefined) {
updateData.rankValue = rankValue;
}

const result = await this.storyRepository.update(
{ id, project: { id: project.id } },
Expand Down
5 changes: 4 additions & 1 deletion backend/src/project/service/project.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,10 +132,11 @@ export class ProjectService {
title: string,
point: number,
status: StoryStatus,
rankValue: string,
) {
const epic = await this.projectRepository.getEpicById(project, epicId);
if (!epic) throw new Error('epic id not found');
const newStory = Story.of(project, epicId, title, point, status);
const newStory = Story.of(project, epicId, title, point, status, rankValue);
return this.projectRepository.createStory(newStory);
}

Expand All @@ -151,6 +152,7 @@ export class ProjectService {
title: string | undefined,
point: number | undefined,
status: StoryStatus | undefined,
rankValue: string | undefined,
): Promise<boolean> {
if (epicId !== undefined) {
const epic = await this.projectRepository.getEpicById(project, epicId);
Expand All @@ -163,6 +165,7 @@ export class ProjectService {
title,
point,
status,
rankValue,
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export class WsProjectStoryController {
content.title,
content.point,
content.status,
content.rankValue,
);
client.nsp
.to('backlog')
Expand Down Expand Up @@ -68,6 +69,7 @@ export class WsProjectStoryController {
content.title,
content.point,
content.status,
content.rankValue,
);

if (isUpdated) {
Expand All @@ -81,6 +83,7 @@ export class WsProjectStoryController {
content.title,
content.point,
content.status,
content.rankValue,
),
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,14 @@ describe('WS epic', () => {
await initBacklog(socket1);
const epicName = '회원';
const epicColor = 'yellow';
const epicRankValue = LexoRank.middle().toString();
const middleRankValue = LexoRank.middle().toString();
socket1.emit('epic', {
action: 'create',
content: { name: epicName, color: epicColor, rankValue: epicRankValue },
content: {
name: epicName,
color: epicColor,
rankValue: middleRankValue,
},
});
const epicId = await getEpicId(socket1);
const storyTitle = '타이틀';
Expand All @@ -33,6 +37,7 @@ describe('WS epic', () => {
point: storyPoint,
status: storyStatus,
epicId,
rankValue: middleRankValue,
},
});
const storyId = await getStoryId(socket1);
Expand Down
Loading

0 comments on commit 7c19a12

Please sign in to comment.