Skip to content

Commit

Permalink
feat: duplicate regular board (#1281)
Browse files Browse the repository at this point in the history
Co-authored-by: Nuno Caseiro <n.caseiro@kigroup.de>
  • Loading branch information
jpvsalvador and nunocaseiro authored Mar 23, 2023
1 parent ba96d9e commit 70c3cf2
Show file tree
Hide file tree
Showing 20 changed files with 794 additions and 115 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { Test, TestingModule } from '@nestjs/testing';
import * as BoardUsers from 'src/modules/boardUsers/interfaces/types';
import * as Boards from 'src/modules/boards/interfaces/types';
import * as Users from 'src/modules/users/interfaces/types';
import { DeepMocked, createMock } from '@golevelup/ts-jest';
import { duplicateBoardUseCase } from '../boards.providers';
import { UseCase } from 'src/libs/interfaces/use-case.interface';
import Board from '../entities/board.schema';
import { CreateBoardUserServiceInterface } from 'src/modules/boardUsers/interfaces/services/create.board.user.service.interface';
import { BoardRepositoryInterface } from '../repositories/board.repository.interface';
import { GetBoardServiceInterface } from '../interfaces/services/get.board.service.interface';
import { NotFoundException } from '@nestjs/common';
import faker from '@faker-js/faker';
import { GetUserServiceInterface } from 'src/modules/users/interfaces/services/get.user.service.interface';
import { BoardFactory } from 'src/libs/test-utils/mocks/factories/board-factory.mock';
import { UserFactory } from 'src/libs/test-utils/mocks/factories/user-factory';
import { DuplicateBoardDto } from './duplicate-board.use-case';

describe('DuplicateBoardUseCase', () => {
let duplicateBoardMock: UseCase<DuplicateBoardDto, Board>;
let getUserServiceMock: DeepMocked<GetUserServiceInterface>;
let getBoardServiceMock: DeepMocked<GetBoardServiceInterface>;
let boardRepositoryMock: DeepMocked<BoardRepositoryInterface>;
let createBoardUserServiceMock: DeepMocked<CreateBoardUserServiceInterface>;

beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
duplicateBoardUseCase,
{
provide: Users.TYPES.services.GetUserService,
useValue: createMock<GetUserServiceInterface>()
},
{
provide: Boards.TYPES.services.GetBoardService,
useValue: createMock<GetBoardServiceInterface>()
},
{
provide: Boards.TYPES.repositories.BoardRepository,
useValue: createMock<BoardRepositoryInterface>()
},
{
provide: BoardUsers.TYPES.services.CreateBoardUserService,
useValue: createMock<CreateBoardUserServiceInterface>()
}
]
}).compile();

duplicateBoardMock = module.get<UseCase<DuplicateBoardDto, Board>>(
Boards.TYPES.applications.DuplicateBoardUseCase
);

getUserServiceMock = module.get(Users.TYPES.services.GetUserService);
getBoardServiceMock = module.get(Boards.TYPES.services.GetBoardService);
boardRepositoryMock = module.get(Boards.TYPES.repositories.BoardRepository);
createBoardUserServiceMock = module.get(BoardUsers.TYPES.services.CreateBoardUserService);
});

const DEFAULT_PROPS = {
boardId: faker.datatype.uuid(),
userId: faker.datatype.uuid(),
boardTitle: 'My Board'
};

beforeEach(() => {
jest.clearAllMocks();
});

it('should be defined', () => {
expect(duplicateBoardMock).toBeDefined();
});

describe('execute', () => {
it('should throw an error if user not found', async () => {
getUserServiceMock.getById.mockResolvedValueOnce(null);
await expect(duplicateBoardMock.execute(DEFAULT_PROPS)).rejects.toThrowError(
NotFoundException
);
});

it('should throw an error if board not found', async () => {
getBoardServiceMock.getBoard.mockResolvedValueOnce({ board: null });
await expect(duplicateBoardMock.execute(DEFAULT_PROPS)).rejects.toThrowError(
NotFoundException
);
});

it('should call boardRepository.create', async () => {
const user = UserFactory.create({ _id: DEFAULT_PROPS.userId });
getUserServiceMock.getById.mockResolvedValueOnce(user);

const board = BoardFactory.create({ _id: DEFAULT_PROPS.boardId });
getBoardServiceMock.getBoard.mockResolvedValueOnce({ board });

await duplicateBoardMock.execute(DEFAULT_PROPS);
expect(boardRepositoryMock.create).toHaveBeenCalled();
});

it('should call createBoardUserService.saveBoardUsers', async () => {
const user = UserFactory.create({ _id: DEFAULT_PROPS.userId });
getUserServiceMock.getById.mockResolvedValueOnce(user);

const board = BoardFactory.create({ _id: DEFAULT_PROPS.boardId });
getBoardServiceMock.getBoard.mockResolvedValueOnce({ board });
boardRepositoryMock.create.mockResolvedValueOnce(board);

await duplicateBoardMock.execute(DEFAULT_PROPS);
expect(createBoardUserServiceMock.saveBoardUsers).toHaveBeenCalled();
});
});
});
129 changes: 129 additions & 0 deletions backend/src/modules/boards/applications/duplicate-board.use-case.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { UseCase } from 'src/libs/interfaces/use-case.interface';
import { CreateBoardUserServiceInterface } from 'src/modules/boardUsers/interfaces/services/create.board.user.service.interface';
import * as BoardUsers from 'src/modules/boardUsers/interfaces/types';
import Card from 'src/modules/cards/entities/card.schema';
import Column from 'src/modules/columns/entities/column.schema';
import { GetUserServiceInterface } from 'src/modules/users/interfaces/services/get.user.service.interface';
import * as Users from 'src/modules/users/interfaces/types';
import { BadRequestException, Inject, Injectable } from '@nestjs/common';
import Board from '../entities/board.schema';
import { GetBoardServiceInterface } from '../interfaces/services/get.board.service.interface';
import { TYPES } from '../interfaces/types';
import { BoardRepositoryInterface } from '../repositories/board.repository.interface';
import User from 'src/modules/users/entities/user.schema';
import Team from 'src/modules/teams/entities/team.schema';
import BoardUserDto from 'src/modules/boardUsers/dto/board.user.dto';
import { UserNotFoundException } from 'src/libs/exceptions/userNotFoundException';
import { BoardNotFoundException } from 'src/libs/exceptions/boardNotFoundException';
import { INSERT_FAILED } from 'src/libs/exceptions/messages';

export type DuplicateBoardDto = { boardId: string; userId: string; boardTitle: string };

@Injectable()
export class DuplicateBoardUseCase implements UseCase<DuplicateBoardDto, Board> {
constructor(
@Inject(TYPES.services.GetBoardService)
private getBoardService: GetBoardServiceInterface,
@Inject(Users.TYPES.services.GetUserService)
private getUserService: GetUserServiceInterface,
@Inject(TYPES.repositories.BoardRepository)
private readonly boardRepository: BoardRepositoryInterface,
@Inject(BoardUsers.TYPES.services.CreateBoardUserService)
private createBoardUserService: CreateBoardUserServiceInterface
) {}

async execute({ boardId, userId, boardTitle }) {
const currentUser = await this.getUserService.getById(userId);

if (!currentUser) {
throw new UserNotFoundException();
}
const { _id, firstName, lastName, email, strategy } = currentUser;

const { board } = await this.getBoardService.getBoard(boardId, {
_id,
firstName,
lastName,
email,
strategy
});

if (!board) {
throw new BoardNotFoundException();
}

const boardTeam = board.team as Team;
const users: BoardUserDto[] = board.users.map((user) => {
const userData = user.user as User;
delete user._id;

return {
...user,
board: user.board as string,
user: userData._id
};
});

const columns = board.columns.map((column: Column) => {
delete column._id;

return {
...column,
cards: column.cards.map((card: Card) => {
delete card._id;

return {
...card,
createdBy: card.createdBy as string,
votes: card.votes as string[],
comments: card.comments.map((comment) => {
delete comment._id;

return {
...comment,
createdBy: comment.createdBy as string
};
}),
items: card.items.map((item) => {
delete item._id;

return {
...item,
createdBy: item.createdBy as string,
votes: item.votes as string[],
comments: item.comments.map((comment) => {
delete comment._id;

return {
...comment,
createdBy: comment.createdBy as string
};
})
};
})
};
})
};
});

const newBoard = await this.boardRepository.create<Board>({
...board,
_id: undefined,
createdAt: undefined,
updatedAt: undefined,
users,
columns,
title: boardTitle,
team: boardTeam._id,
slackEnable: false,
isSubBoard: false,
dividedBoards: []
});

if (!newBoard) throw new BadRequestException(INSERT_FAILED);

await this.createBoardUserService.saveBoardUsers(users, newBoard._id);

return newBoard;
}
}
10 changes: 6 additions & 4 deletions backend/src/modules/boards/boards.module.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import AuthModule from 'src/modules/auth/auth.module';
import { Module, forwardRef } from '@nestjs/common';
import { JwtRegister } from 'src/infrastructure/config/jwt.register';
import {
mongooseBoardModule,
mongooseUserModule
} from 'src/infrastructure/database/mongoose.module';
import AuthModule from 'src/modules/auth/auth.module';
import { CommunicationModule } from 'src/modules/communication/communication.module';
import { SchedulesModule } from 'src/modules/schedules/schedules.module';
import TeamsModule from 'src/modules/teams/teams.module';
import UsersModule from 'src/modules/users/users.module';
import { Module, forwardRef } from '@nestjs/common';
import BoardUsersModule from '../boardUsers/boardusers.module';
import { CardsModule } from '../cards/cards.module';
import {
afterUserPausedTimerSubscriber,
Expand All @@ -21,6 +23,7 @@ import {
createBoardService,
deleteBoardApplication,
deleteBoardService,
duplicateBoardUseCase,
getBoardApplication,
getBoardService,
pauseBoardTimerService,
Expand All @@ -33,8 +36,6 @@ import {
updateBoardTimerDurationService
} from './boards.providers';
import BoardsController from './controller/boards.controller';
import { JwtRegister } from 'src/infrastructure/config/jwt.register';
import BoardUsersModule from '../boardUsers/boardusers.module';
import TeamUsersModule from 'src/modules/teamUsers/teamusers.module';

@Module({
Expand All @@ -57,6 +58,7 @@ import TeamUsersModule from 'src/modules/teamUsers/teamusers.module';
deleteBoardService,
getBoardService,
createBoardApplication,
duplicateBoardUseCase,
updateBoardApplication,
deleteBoardApplication,
getBoardApplication,
Expand Down
18 changes: 12 additions & 6 deletions backend/src/modules/boards/boards.providers.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
import BoardTimerRepository from 'src/modules/boards/repositories/board-timer.repository';
import PauseBoardTimerService from 'src/modules/boards/services/pause-board-timer.service';
import SendBoardTimerStateService from 'src/modules/boards/services/send-board-timer-state.service';
import SendBoardTimerTimeLeftService from 'src/modules/boards/services/send-board-timer-time-left.service';
import StartBoardTimerService from 'src/modules/boards/services/start-board-timer.service';
import StopBoardTimerService from 'src/modules/boards/services/stop-board-timer.service';
import UpdateBoardTimerDurationService from 'src/modules/boards/services/update-board-timer-duration.service';
import AfterUserPausedTimerSubscriber from 'src/modules/boards/subscribers/after-user-paused-timer.subscriber';
import AfterUserRequestedTimerStateSubscriber from 'src/modules/boards/subscribers/after-user-requested-timer-state.subscriber';
import AfterUserStartedTimerSubscriber from 'src/modules/boards/subscribers/after-user-started-timer.subscriber';
import AfterUserStoppedTimerSubscriber from 'src/modules/boards/subscribers/after-user-stopped-timer.subscriber';
import AfterUserUpdatedDurationSubscriber from 'src/modules/boards/subscribers/after-user-updated-duration.subscriber';
import { CreateBoardApplication } from './applications/create.board.application';
import { DeleteBoardApplication } from './applications/delete.board.application';
import { DuplicateBoardUseCase } from './applications/duplicate-board.use-case';
import { GetBoardApplication } from './applications/get.board.application';
import { UpdateBoardApplication } from './applications/update.board.application';
import { TYPES } from './interfaces/types';
import { BoardRepository } from './repositories/board.repository';
import CreateBoardService from './services/create.board.service';
import DeleteBoardService from './services/delete.board.service';
import GetBoardService from './services/get.board.service';
import PauseBoardTimerService from 'src/modules/boards/services/pause-board-timer.service';
import SendBoardTimerStateService from 'src/modules/boards/services/send-board-timer-state.service';
import SendBoardTimerTimeLeftService from 'src/modules/boards/services/send-board-timer-time-left.service';
import StartBoardTimerService from 'src/modules/boards/services/start-board-timer.service';
import StopBoardTimerService from 'src/modules/boards/services/stop-board-timer.service';
import UpdateBoardTimerDurationService from 'src/modules/boards/services/update-board-timer-duration.service';
import UpdateBoardService from './services/update.board.service';

export const createBoardService = {
Expand Down Expand Up @@ -46,6 +47,11 @@ export const createBoardApplication = {
useClass: CreateBoardApplication
};

export const duplicateBoardUseCase = {
provide: TYPES.applications.DuplicateBoardUseCase,
useClass: DuplicateBoardUseCase
};

export const getBoardApplication = {
provide: TYPES.applications.GetBoardApplication,
useClass: GetBoardApplication
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import { UpdateBoardApplicationInterface } from '../interfaces/applications/upda
import { CreateBoardApplicationInterface } from '../interfaces/applications/create.board.application.interface';
import { GetBoardApplicationInterface } from '../interfaces/applications/get.board.application.interface';
import { DeleteBoardApplicationInterface } from '../interfaces/applications/delete.board.application.interface';
import { UseCase } from 'src/libs/interfaces/use-case.interface';
import Board from '../entities/board.schema';
import { DuplicateBoardDto } from '../applications/duplicate-board.use-case';

describe('BoardsController', () => {
let controller: BoardsController;
Expand All @@ -24,6 +27,10 @@ describe('BoardsController', () => {
provide: Boards.TYPES.applications.CreateBoardApplication,
useValue: createMock<CreateBoardApplicationInterface>()
},
{
provide: Boards.TYPES.applications.DuplicateBoardUseCase,
useValue: createMock<UseCase<DuplicateBoardDto, Board>>()
},
{
provide: Boards.TYPES.applications.GetBoardApplication,
useValue: createMock<GetBoardApplicationInterface>()
Expand Down
Loading

0 comments on commit 70c3cf2

Please sign in to comment.