Skip to content

Commit

Permalink
update Space 로직 추가 (#202)
Browse files Browse the repository at this point in the history
* feat: space-delete-api

* fix: 에러 메시지 추가

* fix: json pasing 안되는 이슈 해결

* refactor: validation Service validation으로 네이밍 변경

* feat: space update 로직 추가

* fix: updateSpaceByName

* fix: 노드 위 우클릭시 이동모드 자동 활성화되는 오류 수정 (#195)

fix: 노드 위 우클릭 시 이동모드 활성화되는 것 방지

* fix: 이동모드 활성화 함수에서 button 판별오류 수정 (#196)

* fix: mouse-event에 대해서만 button 판별하도록 수정

* feat: 인터랙션 가이드 UI 추가 (#197)

* feat: 인터랙션 가이드 추가

* chore: 인터랙션 가이드 내용 추가

* feat: 모바일 환경에서 더보기 버튼을 통한 노드.간선 편집 (#199)

* feat: 터치디바이스 여부 확인 로직

* feat: 모바일환경에서 더보기 버튼 클릭 시 context-menu 표시

* feat: 더보기 버튼 클릭 시 clientX, clientY값 정의하여 전달

* feat: 간선 삭제 버튼 스타일 수정

* feat: 삭제 버튼 터치 핸들러 연결

* feat: 서브스페이스 삭제 api 연동

* feat: 작업 내용 저장

* feat: save

* fix: space delete id가 아니라 src에 있는 걸 갖고 올 수 있도록 수정

* feat: deleteNote Controller API 추가

* feat: 서브스페이스 수정 api 연동

---------

Co-authored-by: CatyJazzy <[email protected]>
Co-authored-by: Heejin Na <[email protected]>
Co-authored-by: CatyJazzy <[email protected]>
  • Loading branch information
4 people authored Dec 3, 2024
1 parent 17dd8aa commit 022635a
Show file tree
Hide file tree
Showing 20 changed files with 625 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ export const ERROR_MESSAGES = {
UPDATE_FAILED: '스페이스 업데이트에 실패하였습니다.',
INITIALIZE_FAILED: '스페이스가 초기화에 실패하였습니다.',
PARENT_NOT_FOUND: '부모 스페이스가 존재하지 않습니다.',
DELETE_FAILED: '노트 삭제에 실패하였습니다.',
},
NOTE: {
BAD_REQUEST: '잘못된 요청입니다.',
NOT_FOUND: '노트가 존재하지 않습니다.',
CREATION_FAILED: '노트 생성에 실패하였습니다.',
UPDATE_FAILED: '노트 업데이트에 실패하였습니다.',
INITIALIZE_FAILED: '노트가 초기화에 실패하였습니다.',
DELETE_FAILED: '노트 삭제에 실패하였습니다.',
},
SOCKET: {
INVALID_URL: '유효하지 않은 URL 주소입니다.',
Expand Down
16 changes: 16 additions & 0 deletions packages/backend/src/note/note.controller.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
Body,
Controller,
Delete,
Get,
HttpException,
HttpStatus,
Expand Down Expand Up @@ -113,4 +114,19 @@ export class NoteController {
);
}
}

@Version('1')
@Delete('/:id')
@ApiOperation({ summary: '노트 조회' })
@ApiResponse({ status: 200, description: '노트 조회 성공' })
@ApiResponse({ status: 404, description: '노트 조회 실패' })
async deleteNote(@Param('id') id: string) {
const result = await this.noteService.deleteById(id);
this.logger.log('노트 삭제 완료', {
method: 'deleteNote',
id,
result: !!result,
});
return !!result;
}
}
22 changes: 20 additions & 2 deletions packages/backend/src/note/note.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ export class NoteService {
}

async findById(id: string) {
this.logger.log(`ID가 ${id}인 노트를 검색 중입니다.`); // 로그 메시지 개선
this.logger.log(`ID가 ${id}인 노트를 검색 중입니다.`);

const note = await this.noteModel.findOne({ id }).exec();

this.logger.debug(`ID가 ${id}인 노트 검색 결과: ${!!note}`); // 로그 메시지 개선
this.logger.debug(`ID가 ${id}인 노트 검색 결과: ${!!note}`);

return note;
}
Expand Down Expand Up @@ -80,4 +80,22 @@ export class NoteService {
throw new BadRequestException(ERROR_MESSAGES.NOTE.UPDATE_FAILED);
}
}
async deleteById(id: string) {
this.logger.log(`ID가 ${id}인 노트를 삭제하는 중입니다.`);

try {
const result = await this.noteModel.deleteOne({ id }).exec();

if (result.deletedCount === 0) {
this.logger.warn(`삭제 실패: ID가 ${id}인 노트를 찾을 수 없습니다.`);
throw new BadRequestException(ERROR_MESSAGES.NOTE.NOT_FOUND);
}

this.logger.log(`ID가 ${id}인 노트 삭제 완료.`);
return { success: true, message: '노트가 성공적으로 삭제되었습니다.' };
} catch (error) {
this.logger.error(`ID가 ${id}인 노트 삭제 중 오류 발생.`, error.stack);
throw new BadRequestException(ERROR_MESSAGES.NOTE.DELETE_FAILED);
}
}
}
1 change: 1 addition & 0 deletions packages/backend/src/space/dto/update.space.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export class UpdateSpaceDto {
userId: string;
@ApiProperty({ description: '스페이스 이름' })
spaceName: string;

@ApiProperty({ description: 'Parent Space Id' })
parentContextNodeId: string | null;
}
123 changes: 123 additions & 0 deletions packages/backend/src/space/space.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { Test, TestingModule } from '@nestjs/testing';
import { SpaceController } from './space.controller';
import { SpaceService } from './space.service';
import { CreateSpaceDto } from './dto/create.space.dto';
import { HttpException } from '@nestjs/common';
import { GUEST_USER_ID } from '../common/constants/space.constants';

describe('SpaceController', () => {
let spaceController: SpaceController;
let spaceService: Partial<SpaceService>;

beforeEach(async () => {
spaceService = {
existsById: jest.fn(),
getBreadcrumb: jest.fn(),
create: jest.fn(),
};

const module: TestingModule = await Test.createTestingModule({
controllers: [SpaceController],
providers: [
{
provide: SpaceService,
useValue: spaceService,
},
],
}).compile();

spaceController = module.get<SpaceController>(SpaceController);
});

describe('existsBySpace', () => {
it('스페이스가 존재할 경우 true를 반환해야 한다', async () => {
const spaceId = '123';
(spaceService.existsById as jest.Mock).mockResolvedValue(true);

const result = await spaceController.existsBySpace(spaceId);

expect(spaceService.existsById).toHaveBeenCalledWith(spaceId);
expect(result).toBe(true);
});

it('예외가 발생하면 오류를 던져야 한다', async () => {
const spaceId = '123';
(spaceService.existsById as jest.Mock).mockRejectedValue(
new Error('Unexpected Error'),
);

await expect(spaceController.existsBySpace(spaceId)).rejects.toThrow(
'Unexpected Error',
);
});
});

describe('getBreadcrumb', () => {
it('주어진 스페이스 ID에 대한 경로를 반환해야 한다', async () => {
const spaceId = '123';
const breadcrumb = ['Home', 'Space'];
(spaceService.getBreadcrumb as jest.Mock).mockResolvedValue(breadcrumb);

const result = await spaceController.getBreadcrumb(spaceId);

expect(spaceService.getBreadcrumb).toHaveBeenCalledWith(spaceId);
expect(result).toEqual(breadcrumb);
});
});

describe('createSpace', () => {
it('스페이스를 생성하고 URL 경로를 반환해야 한다', async () => {
const createSpaceDto: CreateSpaceDto = {
userId: GUEST_USER_ID,
spaceName: 'New Space',
parentContextNodeId: '123',
};

const mockSpace = { toObject: () => ({ id: 'space123' }) };
(spaceService.create as jest.Mock).mockResolvedValue(mockSpace);

const result = await spaceController.createSpace(createSpaceDto);

expect(spaceService.create).toHaveBeenCalledWith(
GUEST_USER_ID,
'New Space',
'123',
);
expect(result).toEqual({ urlPath: 'space123' });
});

it('잘못된 요청인 경우 400 오류를 던져야 한다', async () => {
const createSpaceDto: CreateSpaceDto = {
userId: 'invalidUser',
spaceName: '',
parentContextNodeId: '123',
};

await expect(spaceController.createSpace(createSpaceDto)).rejects.toThrow(
HttpException,
);

expect(spaceService.create).not.toHaveBeenCalled();
});

it('스페이스 생성에 실패한 경우 404 오류를 던져야 한다', async () => {
const createSpaceDto: CreateSpaceDto = {
userId: GUEST_USER_ID,
spaceName: 'New Space',
parentContextNodeId: '123',
};

(spaceService.create as jest.Mock).mockResolvedValue(null);

await expect(spaceController.createSpace(createSpaceDto)).rejects.toThrow(
HttpException,
);

expect(spaceService.create).toHaveBeenCalledWith(
GUEST_USER_ID,
'New Space',
'123',
);
});
});
});
41 changes: 39 additions & 2 deletions packages/backend/src/space/space.controller.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
Body,
Controller,
Delete,
Get,
HttpException,
HttpStatus,
Expand All @@ -16,6 +17,8 @@ import { ERROR_MESSAGES } from '../common/constants/error.message.constants';
import { GUEST_USER_ID } from '../common/constants/space.constants';
import { CreateSpaceDto } from './dto/create.space.dto';
import { SpaceService } from './space.service';
import { UpdateSpaceDto } from './dto/update.space.dto';
import { SpaceDocument } from './space.schema';

@ApiTags('space')
@Controller('space')
Expand Down Expand Up @@ -142,8 +145,17 @@ export class SpaceController {
@ApiOperation({ summary: '스페이스 업데이트' })
@ApiResponse({ status: 201, description: '스페이스 업데이트 성공' })
@ApiResponse({ status: 400, description: '잘못된 요청' })
async updateSpace(@Param('id') id: string) {
const result = await this.spaceService.existsById(id);
async updateSpaceByName(
@Param('id') id: string,
@Body() updateSpaceDto: UpdateSpaceDto,
) {
const updateData: Partial<SpaceDocument> = {
name: updateSpaceDto.spaceName,
parentSpaceId: updateSpaceDto.parentContextNodeId,
userId: updateSpaceDto.userId,
};

const result = await this.spaceService.updateById(id, updateData);

if (!result) {
this.logger.error('스페이스 업데이트 실패 - 스페이스를 찾을 수 없음', {
Expand All @@ -156,5 +168,30 @@ export class SpaceController {
HttpStatus.NOT_FOUND,
);
}
return result;
}

@Version('1')
@Delete('/:id')
@ApiOperation({ summary: '스페이스 삭제' })
@ApiResponse({ status: 201, description: '스페이스 삭제 성공' })
@ApiResponse({ status: 400, description: '잘못된 요청' })
async deleteSpace(@Param('id') id: string) {
const result = await this.spaceService.deleteById(id);

if (!result) {
this.logger.error(
'스페이스 삭제 실패 - 스페이스 삭제에 실패하였습니다.',
{
method: 'deleteSpace',
error: ERROR_MESSAGES.SPACE.DELETE_FAILED,
id,
},
);
throw new HttpException(
ERROR_MESSAGES.SPACE.DELETE_FAILED,
HttpStatus.NOT_FOUND,
);
}
}
}
6 changes: 4 additions & 2 deletions packages/backend/src/space/space.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,18 @@ import { MongooseModule } from '@nestjs/mongoose';
import { SpaceController } from './space.controller';
import { SpaceDocument, SpaceSchema } from './space.schema';
import { SpaceService } from './space.service';
import { SpaceValidationService } from './space.validation.service';
import { SpaceValidation } from './space.validation.service';
import { NoteModule } from 'src/note/note.module';

@Module({
imports: [
NoteModule,
MongooseModule.forFeature([
{ name: SpaceDocument.name, schema: SpaceSchema },
]),
],
controllers: [SpaceController],
providers: [SpaceService, SpaceValidationService],
providers: [SpaceService, SpaceValidation],
exports: [SpaceService],
})
export class SpaceModule {}
80 changes: 80 additions & 0 deletions packages/backend/src/space/space.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { Test, TestingModule } from '@nestjs/testing';
import { SpaceService } from './space.service';
import { getModelToken } from '@nestjs/mongoose';
import { SpaceDocument } from './space.schema';
import { SpaceValidation } from './space.validation.service';
import { Model } from 'mongoose';

jest.mock('uuid', () => ({
v4: jest.fn(() => 'mock-uuid'),
}));

describe('SpaceService', () => {
let spaceService: SpaceService;
let spaceModel: Model<SpaceDocument>;
let spaceValidation: SpaceValidation;

beforeEach(async () => {
const mockSpaceModel = {
findOne: jest.fn().mockReturnValue({
exec: jest.fn(),
}),
findOneAndUpdate: jest.fn().mockReturnValue({
exec: jest.fn(),
}),
countDocuments: jest.fn(),
create: jest.fn(),
};

const mockSpaceValidation = {
validateSpaceLimit: jest.fn().mockResolvedValue(undefined),
validateParentNodeExists: jest.fn().mockResolvedValue(undefined),
};

const module: TestingModule = await Test.createTestingModule({
providers: [
SpaceService,
{
provide: getModelToken(SpaceDocument.name),
useValue: mockSpaceModel,
},
{
provide: SpaceValidation,
useValue: mockSpaceValidation,
},
],
}).compile();

spaceService = module.get<SpaceService>(SpaceService);
spaceModel = module.get<Model<SpaceDocument>>(
getModelToken(SpaceDocument.name),
);
spaceValidation = module.get<SpaceValidation>(SpaceValidation);
});

describe('getBreadcrumb', () => {
it('스페이스의 경로를 반환해야 한다', async () => {
const mockSpaces = [
{ id: 'parent-id', name: 'Parent Space', parentSpaceId: null },
{ id: '123', name: 'Child Space', parentSpaceId: 'parent-id' },
];

(spaceModel.findOne as jest.Mock)
.mockReturnValueOnce({
exec: jest.fn().mockResolvedValue(mockSpaces[1]),
})
.mockReturnValueOnce({
exec: jest.fn().mockResolvedValue(mockSpaces[0]),
});

const result = await spaceService.getBreadcrumb('123');

expect(spaceModel.findOne).toHaveBeenCalledWith({ id: '123' });
expect(spaceModel.findOne).toHaveBeenCalledWith({ id: 'parent-id' });
expect(result).toEqual([
{ name: 'Parent Space', url: 'parent-id' },
{ name: 'Child Space', url: '123' },
]);
});
});
});
Loading

0 comments on commit 022635a

Please sign in to comment.