From b35c7d87a8b8f97a5c70f7bb308276fb3b312a47 Mon Sep 17 00:00:00 2001 From: Nuno Caseiro Date: Fri, 8 Apr 2022 13:32:30 +0100 Subject: [PATCH 1/3] feat: dashboard statistics and get all boards with pagination --- .../src/infrastructure/config/jwt.register.ts | 17 ++ .../src/libs/dto/param/pagination.params.ts | 16 ++ backend/src/modules/auth/auth.module.ts | 20 +-- .../auth/controller/auth.controller.ts | 20 +++ .../applications/get.board.application.ts | 13 +- backend/src/modules/boards/boards.module.ts | 10 +- .../boards/controller/boards.controller.ts | 23 ++- .../get.board.application.interface.ts | 14 +- .../services/get.board.service.interface.ts | 12 +- .../modules/boards/schemas/board.schema.ts | 3 - .../boards/services/get.board.service.ts | 154 ++++++++++++++++-- .../teams/services/get.team.service.ts | 6 +- backend/src/modules/users/dto/user.dto.ts | 9 +- backend/test/auth/auth.controller.spec.ts | 28 ++++ backend/test/boards/boards.controller.spec.ts | 14 ++ 15 files changed, 319 insertions(+), 40 deletions(-) create mode 100644 backend/src/infrastructure/config/jwt.register.ts create mode 100644 backend/src/libs/dto/param/pagination.params.ts diff --git a/backend/src/infrastructure/config/jwt.register.ts b/backend/src/infrastructure/config/jwt.register.ts new file mode 100644 index 000000000..470f3bc4d --- /dev/null +++ b/backend/src/infrastructure/config/jwt.register.ts @@ -0,0 +1,17 @@ +import { ConfigModule, ConfigService } from '@nestjs/config'; +import { JwtModule } from '@nestjs/jwt'; +import { + JWT_ACCESS_TOKEN_SECRET, + JWT_ACCESS_TOKEN_EXPIRATION_TIME, +} from '../../libs/constants/jwt'; + +export const JwtRegister = JwtModule.registerAsync({ + imports: [ConfigModule], + inject: [ConfigService], + useFactory: async (configService: ConfigService) => ({ + secret: configService.get(JWT_ACCESS_TOKEN_SECRET), + signOptions: { + expiresIn: `${configService.get(JWT_ACCESS_TOKEN_EXPIRATION_TIME)}s`, + }, + }), +}); diff --git a/backend/src/libs/dto/param/pagination.params.ts b/backend/src/libs/dto/param/pagination.params.ts new file mode 100644 index 000000000..0782bbe78 --- /dev/null +++ b/backend/src/libs/dto/param/pagination.params.ts @@ -0,0 +1,16 @@ +import { IsNumber, Min, IsOptional } from 'class-validator'; +import { Type } from 'class-transformer'; + +export class PaginationParams { + @IsOptional() + @Type(() => Number) + @IsNumber() + @Min(0) + page?: number; + + @IsOptional() + @Type(() => Number) + @IsNumber() + @Min(1) + size?: number; +} diff --git a/backend/src/modules/auth/auth.module.ts b/backend/src/modules/auth/auth.module.ts index 51ac6326e..a65d9bbdf 100644 --- a/backend/src/modules/auth/auth.module.ts +++ b/backend/src/modules/auth/auth.module.ts @@ -1,8 +1,6 @@ import { Module } from '@nestjs/common'; -import { ConfigModule, ConfigService } from '@nestjs/config'; +import { ConfigModule } from '@nestjs/config'; import { PassportModule } from '@nestjs/passport'; -import { JwtModule } from '@nestjs/jwt'; - import AuthController from './controller/auth.controller'; import UsersModule from '../users/users.module'; import LocalStrategy from './strategy/local.strategy'; @@ -15,22 +13,18 @@ import { getTokenAuthApplication, registerAuthApplication, } from './auth.providers'; +import TeamsModule from '../teams/teams.module'; +import BoardsModule from '../boards/boards.module'; +import { JwtRegister } from '../../infrastructure/config/jwt.register'; @Module({ imports: [ UsersModule, + TeamsModule, + JwtRegister, + BoardsModule, PassportModule, ConfigModule, - JwtModule.registerAsync({ - imports: [ConfigModule], - inject: [ConfigService], - useFactory: async (configService: ConfigService) => ({ - secret: configService.get('jwt.accessToken.secret'), - signOptions: { - expiresIn: `${configService.get('jwt.accessToken.expirationTime')}s`, - }, - }), - }), ], providers: [ getTokenAuthService, diff --git a/backend/src/modules/auth/controller/auth.controller.ts b/backend/src/modules/auth/controller/auth.controller.ts index 77a504063..8a0a9bdb3 100644 --- a/backend/src/modules/auth/controller/auth.controller.ts +++ b/backend/src/modules/auth/controller/auth.controller.ts @@ -26,7 +26,13 @@ import CreateUserDto from '../../users/dto/create.user.dto'; import { uniqueViolation } from '../../../infrastructure/database/errors/unique.user'; import { signIn } from '../shared/login.auth'; import * as User from '../../users/interfaces/types'; +import * as Teams from '../../teams/interfaces/types'; +import * as Boards from '../../boards/interfaces/types'; +import { EmailParam } from '../../../libs/dto/param/email.param'; import { GetUserApplication } from '../../users/interfaces/applications/get.user.application.interface'; +import JwtAuthenticationGuard from '../../../libs/guards/jwtAuth.guard'; +import { GetBoardApplication } from '../../boards/interfaces/applications/get.board.application.interface'; +import { GetTeamApplication } from '../../teams/interfaces/applications/get.team.application.interface'; @Controller('auth') export default class AuthController { @@ -37,6 +43,10 @@ export default class AuthController { private getTokenAuthApp: GetTokenAuthApplication, @Inject(User.TYPES.applications.GetUserApplication) private getUserApp: GetUserApplication, + @Inject(Teams.TYPES.applications.GetTeamApplication) + private getTeamsApp: GetTeamApplication, + @Inject(Boards.TYPES.applications.GetBoardApplication) + private getBoardApp: GetBoardApplication, ) {} @Post('register') @@ -78,4 +88,14 @@ export default class AuthController { checkEmail(@Param() { email }: EmailParam): Promise { return this.getUserApp.getByEmail(email).then((user) => !!user); } + + @UseGuards(JwtAuthenticationGuard) + @Get('/dashboardStatistics') + async getDashboardHeaderInfo(@Req() request: RequestWithUser) { + const { _id: userId } = request.user; + const usersCount = await this.getUserApp.countUsers(); + const teamsCount = await this.getTeamsApp.countTeams(userId); + const boardsCount = await this.getBoardApp.countBoards(userId); + return { usersCount, teamsCount, boardsCount }; + } } diff --git a/backend/src/modules/boards/applications/get.board.application.ts b/backend/src/modules/boards/applications/get.board.application.ts index fa7843d53..8dbf249a3 100644 --- a/backend/src/modules/boards/applications/get.board.application.ts +++ b/backend/src/modules/boards/applications/get.board.application.ts @@ -10,11 +10,20 @@ export class GetBoardApplication implements GetBoardApplicationInterface { private getBoardService: GetBoardService, ) {} - getAllBoards(userId: string) { - return this.getBoardService.getAllBoards(userId); + getBoards( + option: 'dashboard' | 'allBoards' | 'myBoards', + userId: string, + page?: number, + size?: number, + ) { + return this.getBoardService.getBoards(option, userId, page, size); } getBoard(boardId: string, userId: string) { return this.getBoardService.getBoard(boardId, userId); } + + countBoards(userId: string) { + return this.getBoardService.countBoards(userId); + } } diff --git a/backend/src/modules/boards/boards.module.ts b/backend/src/modules/boards/boards.module.ts index 3403bb11f..e3023400f 100644 --- a/backend/src/modules/boards/boards.module.ts +++ b/backend/src/modules/boards/boards.module.ts @@ -15,9 +15,15 @@ import { mongooseBoardModule, mongooseBoardUserModule, } from '../../infrastructure/database/mongoose.module'; +import TeamsModule from '../teams/teams.module'; @Module({ - imports: [UsersModule, mongooseBoardModule, mongooseBoardUserModule], + imports: [ + UsersModule, + TeamsModule, + mongooseBoardModule, + mongooseBoardUserModule, + ], providers: [ createBoardService, updateBoardService, @@ -29,6 +35,6 @@ import { getBoardApplication, ], controllers: [BoardsController], - exports: [], + exports: [getBoardApplication], }) export default class BoardsModule {} diff --git a/backend/src/modules/boards/controller/boards.controller.ts b/backend/src/modules/boards/controller/boards.controller.ts index d655bd3ef..73eee9374 100644 --- a/backend/src/modules/boards/controller/boards.controller.ts +++ b/backend/src/modules/boards/controller/boards.controller.ts @@ -9,6 +9,7 @@ import { Param, Post, Put, + Query, Req, UseGuards, } from '@nestjs/common'; @@ -27,6 +28,8 @@ import { INSERT_FAILED, UPDATE_FAILED, } from '../../../libs/exceptions/messages'; +import { BaseParam } from '../../../libs/dto/param/base.param'; +import { PaginationParams } from '../../../libs/dto/param/pagination.params'; @Controller('boards') export default class BoardsController { @@ -53,10 +56,26 @@ export default class BoardsController { return board; } + @UseGuards(JwtAuthenticationGuard) + @Get('/dashboard') + getDashboardBoards( + @Req() request: RequestWithUser, + @Query() { page, size }: PaginationParams, + ) { + const { _id: userId } = request.user; + return this.getBoardApp.getBoards('dashboard', userId, page, size); + } + @UseGuards(JwtAuthenticationGuard) @Get() - boards(@Req() request: RequestWithUser) { - return this.getBoardApp.getAllBoards(request.user._id); + getAllBoards( + @Req() request: RequestWithUser, + @Query() { page, size }: PaginationParams, + ) { + const { _id: userId, isSAdmin } = request.user; + if (isSAdmin) + return this.getBoardApp.getBoards('allBoards', userId, page, size); + return this.getBoardApp.getBoards('myBoards', userId, page, size); } @UseGuards(JwtAuthenticationGuard) diff --git a/backend/src/modules/boards/interfaces/applications/get.board.application.interface.ts b/backend/src/modules/boards/interfaces/applications/get.board.application.interface.ts index fadcf959f..f08c77a22 100644 --- a/backend/src/modules/boards/interfaces/applications/get.board.application.interface.ts +++ b/backend/src/modules/boards/interfaces/applications/get.board.application.interface.ts @@ -1,10 +1,20 @@ import { LeanDocument } from 'mongoose'; import { BoardDocument } from '../../schemas/board.schema'; -export interface GetBoardApplicationInterface { - getAllBoards(userId: string): Promise[] | null>; +export interface GetBoardApplication { + getBoards( + option: 'dashboard' | 'allBoards' | 'myBoards', + userId?: string, + page?: number, + size?: number, + ): Promise<{ + boards: LeanDocument[]; + hasNextPage: boolean; + page: number; + } | null>; getBoard( boardId: string, userId: string, ): Promise | null>; + countBoards(userId: string): Promise; } diff --git a/backend/src/modules/boards/interfaces/services/get.board.service.interface.ts b/backend/src/modules/boards/interfaces/services/get.board.service.interface.ts index 08f221870..94421ee7b 100644 --- a/backend/src/modules/boards/interfaces/services/get.board.service.interface.ts +++ b/backend/src/modules/boards/interfaces/services/get.board.service.interface.ts @@ -2,7 +2,16 @@ import { LeanDocument } from 'mongoose'; import { BoardDocument } from '../../schemas/board.schema'; export interface GetBoardService { - getAllBoards(userId: string): Promise[] | null>; + getBoards( + option: 'dashboard' | 'allBoards' | 'myBoards', + userId?: string, + page?: number, + size?: number, + ): Promise<{ + boards: LeanDocument[]; + hasNextPage: boolean; + page: number; + } | null>; getBoardFromRepo( boardId: string, ): Promise | null>; @@ -10,4 +19,5 @@ export interface GetBoardService { boardId: string, userId: string, ): Promise | null>; + countBoards(userId: string): Promise; } diff --git a/backend/src/modules/boards/schemas/board.schema.ts b/backend/src/modules/boards/schemas/board.schema.ts index 9a40ff617..c6e9dbb96 100644 --- a/backend/src/modules/boards/schemas/board.schema.ts +++ b/backend/src/modules/boards/schemas/board.schema.ts @@ -9,9 +9,6 @@ export type BoardDocument = Board & Document; @Schema({ timestamps: true, - toJSON: { - virtuals: true, - }, }) export default class Board { @Prop({ nullable: false }) diff --git a/backend/src/modules/boards/services/get.board.service.ts b/backend/src/modules/boards/services/get.board.service.ts index f282b6ab9..aa126f800 100644 --- a/backend/src/modules/boards/services/get.board.service.ts +++ b/backend/src/modules/boards/services/get.board.service.ts @@ -1,35 +1,163 @@ -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable } from '@nestjs/common'; import { InjectModel } from '@nestjs/mongoose'; import { Model } from 'mongoose'; +import { GetTeamService } from '../../teams/interfaces/services/get.team.service.interface'; +import * as Team from '../../teams/interfaces/types'; import { GetBoardService } from '../interfaces/services/get.board.service.interface'; import Board, { BoardDocument } from '../schemas/board.schema'; +import BoardUser, { BoardUserDocument } from '../schemas/board.user.schema'; @Injectable() export default class GetBoardServiceImpl implements GetBoardService { constructor( @InjectModel(Board.name) private boardModel: Model, + @InjectModel(BoardUser.name) + private boardUserModel: Model, + @Inject(Team.TYPES.services.GetTeamService) + private getTeamService: GetTeamService, ) {} - getAllBoards(userId: string) { - return this.boardModel - .find({ - createdBy: userId, - }) + getAllBoardsIdsOfUser(userId: string) { + return this.boardUserModel + .find({ user: userId }) + .select('board') + .distinct('board') .lean() .exec(); } - getBoardFromRepo(boardId: string) { - return this.boardModel.findById(boardId).lean().exec(); + async getAllBoardIdsAndTeamIdsOfUser(userId: string) { + const boardIds = await this.getAllBoardsIdsOfUser(userId); + const teamIds = await this.getTeamService.getTeamsOfUser(userId); + return { boardIds, teamIds }; } - async getBoard(boardId: string, userId: string) { - const board = await this.getBoardFromRepo(boardId); + async getFindQuery( + option: 'dashboard' | 'allBoards' | 'myBoards', + userId: string, + ) { + const { boardIds, teamIds } = await this.getAllBoardIdsAndTeamIdsOfUser( + userId, + ); - if (board?.isPublic || board?.createdBy?.toString() === userId) { - return board; + if (option === 'dashboard') { + const last3Months = new Date( + new Date().setMonth(new Date().getMonth() - 3), + ); + return { + $and: [ + { isSubBoard: false, updatedAt: { $gte: last3Months } }, + { $or: [{ _id: { $in: boardIds } }, { team: { $in: teamIds } }] }, + ], + }; } - return null; + if (option === 'myBoards') + return { + $and: [ + { isSubBoard: false }, + { $or: [{ _id: { $in: boardIds } }, { team: { $in: teamIds } }] }, + ], + }; + + return { + $and: [ + { isSubBoard: false }, + { $or: [{ _id: { $in: boardIds } }, { team: { $ne: null } }] }, + ], + }; + } + + async getBoards( + option: 'dashboard' | 'allBoards' | 'myBoards', + userId: string, + page = 0, + size = 10, + ) { + const query = await this.getFindQuery(option, userId); + const count = await this.boardModel.find(query).countDocuments().exec(); + const hasNextPage = + page + 1 < Math.ceil(count / (option === 'allBoards' ? count : size)); + const boards = await this.boardModel + .find(query) + .sort({ updatedAt: 'desc' }) + .skip(option === 'allBoards' ? 0 : page * size) + .limit(option === 'allBoards' ? count : size) + .select('-__v -createdAt -id') + .populate({ path: 'createdBy', select: 'firstName lastName' }) + .populate({ + path: 'team', + select: 'name users _id', + populate: { + path: 'users', + select: 'user role', + populate: { + path: 'user', + select: 'firstName lastName', + }, + }, + }) + .populate({ + path: 'dividedBoards', + select: '-__v -createdAt -id', + populate: { + path: 'users', + select: 'role user', + populate: { + path: 'user', + model: 'User', + select: 'firstName lastName', + }, + }, + }) + .populate({ + path: 'users', + select: 'user role -board', + populate: { + path: 'user', + select: 'firstName lastName', + }, + }) + .lean({ virtuals: true }) + .exec(); + + return { boards, hasNextPage, page }; + } + + getBoardFromRepo(boardId: string) { + return this.boardModel.findById(boardId).lean().exec(); + } + + async getBoard(boardId: string) { + const board = await this.boardModel + .findById(boardId) + .populate({ + path: 'users', + select: 'user role -board', + }) + .populate({ + path: 'team', + select: 'name users -_id', + populate: { + path: 'users', + select: 'user role', + }, + }) + .lean({ virtuals: true }) + .exec(); + if (!board) return null; + return board; + } + + async countBoards(userId: string) { + const { boardIds, teamIds } = await this.getAllBoardIdsAndTeamIdsOfUser( + userId, + ); + return this.boardModel.countDocuments({ + $and: [ + { isSubBoard: false }, + { $or: [{ _id: { $in: boardIds } }, { team: { $in: teamIds } }] }, + ], + }); } } diff --git a/backend/src/modules/teams/services/get.team.service.ts b/backend/src/modules/teams/services/get.team.service.ts index 3fee97d0b..6be36d052 100644 --- a/backend/src/modules/teams/services/get.team.service.ts +++ b/backend/src/modules/teams/services/get.team.service.ts @@ -21,7 +21,11 @@ export default class GetTeamServiceImpl implements GetTeamService { } getTeamsOfUser(userId: string) { - return this.teamUserModel.find({ user: userId }).lean().exec(); + return this.teamUserModel + .find({ user: userId }) + .distinct('team') + .lean() + .exec(); } getTeamUser(userId: string, teamId: string) { diff --git a/backend/src/modules/users/dto/user.dto.ts b/backend/src/modules/users/dto/user.dto.ts index 78b5f9829..ed0d26eab 100644 --- a/backend/src/modules/users/dto/user.dto.ts +++ b/backend/src/modules/users/dto/user.dto.ts @@ -1,4 +1,4 @@ -import { IsMongoId, IsNotEmpty, IsString } from 'class-validator'; +import { IsMongoId, IsNotEmpty, IsOptional, IsString } from 'class-validator'; export default class UserDto { @IsNotEmpty() @@ -17,4 +17,11 @@ export default class UserDto { @IsNotEmpty() @IsString() email!: string; + + @IsNotEmpty() + @IsString() + strategy!: string; + + @IsOptional() + isSAdmin?: boolean; } diff --git a/backend/test/auth/auth.controller.spec.ts b/backend/test/auth/auth.controller.spec.ts index b69434e67..928075e5b 100644 --- a/backend/test/auth/auth.controller.spec.ts +++ b/backend/test/auth/auth.controller.spec.ts @@ -20,6 +20,14 @@ import { getUserService, updateUserService, } from '../../src/modules/users/users.providers'; +import { + getTeamApplication, + getTeamService, +} from '../../src/modules/teams/providers'; +import { + getBoardApplication, + getBoardService, +} from '../../src/modules/boards/boards.providers'; describe('AuthController', () => { let app: INestApplication; @@ -36,6 +44,10 @@ describe('AuthController', () => { registerAuthApplication, getTokenAuthApplication, getTokenAuthService, + getTeamService, + getTeamApplication, + getBoardApplication, + getBoardService, registerAuthService, updateUserService, createUserService, @@ -50,10 +62,26 @@ describe('AuthController', () => { provide: JwtService, useValue: jwtService, }, + { + provide: getModelToken('Board'), + useValue: {}, + }, + { + provide: getModelToken('BoardUser'), + useValue: {}, + }, { provide: getModelToken('User'), useValue: usersRepository, }, + { + provide: getModelToken('Team'), + useValue: {}, + }, + { + provide: getModelToken('TeamUser'), + useValue: {}, + }, ], }).compile(); diff --git a/backend/test/boards/boards.controller.spec.ts b/backend/test/boards/boards.controller.spec.ts index acdd57939..f366c01a5 100644 --- a/backend/test/boards/boards.controller.spec.ts +++ b/backend/test/boards/boards.controller.spec.ts @@ -12,6 +12,10 @@ import { updateBoardApplication, updateBoardService, } from '../../src/modules/boards/boards.providers'; +import { + getTeamApplication, + getTeamService, +} from '../../src/modules/teams/providers'; describe('BoardsController', () => { let controller: BoardsController; @@ -25,6 +29,8 @@ describe('BoardsController', () => { createBoardService, getBoardApplication, getBoardService, + getTeamService, + getTeamApplication, updateBoardApplication, updateBoardService, deleteBoardApplication, @@ -41,6 +47,14 @@ describe('BoardsController', () => { provide: getModelToken('BoardUser'), useValue: {}, }, + { + provide: getModelToken('Team'), + useValue: {}, + }, + { + provide: getModelToken('TeamUser'), + useValue: {}, + }, ], }).compile(); From 16d75ae3fd5cec04e8af5b3aec29c96d81d68e3d Mon Sep 17 00:00:00 2001 From: Nuno Caseiro Date: Fri, 8 Apr 2022 16:05:30 +0100 Subject: [PATCH 2/3] refactor: dashboard statistics and get board service methods --- backend/src/infrastructure/config/jwt.register.ts | 2 +- .../src/modules/auth/controller/auth.controller.ts | 8 +++++--- .../modules/boards/controller/boards.controller.ts | 11 ++++++++--- .../src/modules/boards/services/get.board.service.ts | 11 ++++++----- 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/backend/src/infrastructure/config/jwt.register.ts b/backend/src/infrastructure/config/jwt.register.ts index 470f3bc4d..b4e5f8393 100644 --- a/backend/src/infrastructure/config/jwt.register.ts +++ b/backend/src/infrastructure/config/jwt.register.ts @@ -8,7 +8,7 @@ import { export const JwtRegister = JwtModule.registerAsync({ imports: [ConfigModule], inject: [ConfigService], - useFactory: async (configService: ConfigService) => ({ + useFactory: (configService: ConfigService) => ({ secret: configService.get(JWT_ACCESS_TOKEN_SECRET), signOptions: { expiresIn: `${configService.get(JWT_ACCESS_TOKEN_EXPIRATION_TIME)}s`, diff --git a/backend/src/modules/auth/controller/auth.controller.ts b/backend/src/modules/auth/controller/auth.controller.ts index 8a0a9bdb3..c77f254e0 100644 --- a/backend/src/modules/auth/controller/auth.controller.ts +++ b/backend/src/modules/auth/controller/auth.controller.ts @@ -93,9 +93,11 @@ export default class AuthController { @Get('/dashboardStatistics') async getDashboardHeaderInfo(@Req() request: RequestWithUser) { const { _id: userId } = request.user; - const usersCount = await this.getUserApp.countUsers(); - const teamsCount = await this.getTeamsApp.countTeams(userId); - const boardsCount = await this.getBoardApp.countBoards(userId); + const [usersCount, teamsCount, boardsCount] = await Promise.all([ + this.getUserApp.countUsers(), + this.getTeamsApp.countTeams(userId), + this.getBoardApp.countBoards(userId), + ]); return { usersCount, teamsCount, boardsCount }; } } diff --git a/backend/src/modules/boards/controller/boards.controller.ts b/backend/src/modules/boards/controller/boards.controller.ts index 73eee9374..4f0513071 100644 --- a/backend/src/modules/boards/controller/boards.controller.ts +++ b/backend/src/modules/boards/controller/boards.controller.ts @@ -62,8 +62,12 @@ export default class BoardsController { @Req() request: RequestWithUser, @Query() { page, size }: PaginationParams, ) { - const { _id: userId } = request.user; - return this.getBoardApp.getBoards('dashboard', userId, page, size); + return this.getBoardApp.getBoards( + 'dashboard', + request.user._id, + page, + size, + ); } @UseGuards(JwtAuthenticationGuard) @@ -73,8 +77,9 @@ export default class BoardsController { @Query() { page, size }: PaginationParams, ) { const { _id: userId, isSAdmin } = request.user; - if (isSAdmin) + if (isSAdmin) { return this.getBoardApp.getBoards('allBoards', userId, page, size); + } return this.getBoardApp.getBoards('myBoards', userId, page, size); } diff --git a/backend/src/modules/boards/services/get.board.service.ts b/backend/src/modules/boards/services/get.board.service.ts index aa126f800..cdb141676 100644 --- a/backend/src/modules/boards/services/get.board.service.ts +++ b/backend/src/modules/boards/services/get.board.service.ts @@ -27,8 +27,10 @@ export default class GetBoardServiceImpl implements GetBoardService { } async getAllBoardIdsAndTeamIdsOfUser(userId: string) { - const boardIds = await this.getAllBoardsIdsOfUser(userId); - const teamIds = await this.getTeamService.getTeamsOfUser(userId); + const [boardIds, teamIds] = await Promise.all([ + this.getAllBoardsIdsOfUser(userId), + this.getTeamService.getTeamsOfUser(userId), + ]); return { boardIds, teamIds }; } @@ -41,9 +43,8 @@ export default class GetBoardServiceImpl implements GetBoardService { ); if (option === 'dashboard') { - const last3Months = new Date( - new Date().setMonth(new Date().getMonth() - 3), - ); + const now = new Date(); + const last3Months = new Date().setMonth(now.getMonth() - 3); return { $and: [ { isSubBoard: false, updatedAt: { $gte: last3Months } }, From d35f636e0658a038c7ef9475e3f3d476d7312b2f Mon Sep 17 00:00:00 2001 From: Nuno Caseiro Date: Mon, 11 Apr 2022 15:44:53 +0100 Subject: [PATCH 3/3] refactor: get boards method splited --- .../auth/controller/auth.controller.ts | 9 ++- .../azure/services/auth.azure.service.ts | 2 + .../applications/get.board.application.ts | 28 ++++++-- .../boards/controller/boards.controller.ts | 8 +-- .../get.board.application.interface.ts | 24 ++++--- .../interfaces/boards-page.interface.ts | 8 +++ .../modules/boards/interfaces/findQuery.ts | 33 +++++++++ .../services/get.board.service.interface.ts | 24 ++++--- .../boards/services/create.board.service.ts | 1 + .../boards/services/get.board.service.ts | 71 ++++++++++--------- .../applications/get.team.application.ts | 4 +- .../get.team.application.interface.ts | 2 +- 12 files changed, 145 insertions(+), 69 deletions(-) create mode 100644 backend/src/modules/boards/interfaces/boards-page.interface.ts create mode 100644 backend/src/modules/boards/interfaces/findQuery.ts diff --git a/backend/src/modules/auth/controller/auth.controller.ts b/backend/src/modules/auth/controller/auth.controller.ts index c77f254e0..24bc00643 100644 --- a/backend/src/modules/auth/controller/auth.controller.ts +++ b/backend/src/modules/auth/controller/auth.controller.ts @@ -11,7 +11,6 @@ import { BadRequestException, Param, } from '@nestjs/common'; -import { EmailParam } from '../../../libs/dto/param/email.param'; import LocalAuthGuard from '../../../libs/guards/localAuth.guard'; import RequestWithUser from '../../../libs/interfaces/requestWithUser.interface'; import JwtRefreshGuard from '../../../libs/guards/jwtRefreshAuth.guard'; @@ -31,8 +30,8 @@ import * as Boards from '../../boards/interfaces/types'; import { EmailParam } from '../../../libs/dto/param/email.param'; import { GetUserApplication } from '../../users/interfaces/applications/get.user.application.interface'; import JwtAuthenticationGuard from '../../../libs/guards/jwtAuth.guard'; -import { GetBoardApplication } from '../../boards/interfaces/applications/get.board.application.interface'; -import { GetTeamApplication } from '../../teams/interfaces/applications/get.team.application.interface'; +import { GetBoardApplicationInterface } from '../../boards/interfaces/applications/get.board.application.interface'; +import { GetTeamApplicationInterface } from '../../teams/interfaces/applications/get.team.application.interface'; @Controller('auth') export default class AuthController { @@ -44,9 +43,9 @@ export default class AuthController { @Inject(User.TYPES.applications.GetUserApplication) private getUserApp: GetUserApplication, @Inject(Teams.TYPES.applications.GetTeamApplication) - private getTeamsApp: GetTeamApplication, + private getTeamsApp: GetTeamApplicationInterface, @Inject(Boards.TYPES.applications.GetBoardApplication) - private getBoardApp: GetBoardApplication, + private getBoardApp: GetBoardApplicationInterface, ) {} @Post('register') diff --git a/backend/src/modules/azure/services/auth.azure.service.ts b/backend/src/modules/azure/services/auth.azure.service.ts index aa3d76d58..b728d3f9f 100644 --- a/backend/src/modules/azure/services/auth.azure.service.ts +++ b/backend/src/modules/azure/services/auth.azure.service.ts @@ -42,6 +42,8 @@ export default class AuthAzureServiceImpl implements AuthAzureService { const { unique_name, email, given_name, family_name } = ( jwt_decode(azureToken) ); + const userExists = await this.checkUserExistsInActiveDirectory(email); + if (!userExists) return null; const emailOrUniqueName = email ?? unique_name; const user = await this.getUserService.getByEmail(emailOrUniqueName); diff --git a/backend/src/modules/boards/applications/get.board.application.ts b/backend/src/modules/boards/applications/get.board.application.ts index 8dbf249a3..ac49d13ac 100644 --- a/backend/src/modules/boards/applications/get.board.application.ts +++ b/backend/src/modules/boards/applications/get.board.application.ts @@ -1,22 +1,38 @@ import { Inject, Injectable } from '@nestjs/common'; import { GetBoardApplicationInterface } from '../interfaces/applications/get.board.application.interface'; -import { GetBoardService } from '../interfaces/services/get.board.service.interface'; +import { BoardsAndPage } from '../interfaces/boards-page.interface'; +import { GetBoardServiceInterface } from '../interfaces/services/get.board.service.interface'; import { TYPES } from '../interfaces/types'; @Injectable() export class GetBoardApplication implements GetBoardApplicationInterface { constructor( @Inject(TYPES.services.GetBoardService) - private getBoardService: GetBoardService, + private getBoardService: GetBoardServiceInterface, ) {} - getBoards( - option: 'dashboard' | 'allBoards' | 'myBoards', + getUserBoardsOfLast3Months( userId: string, page?: number, size?: number, - ) { - return this.getBoardService.getBoards(option, userId, page, size); + ): Promise { + return this.getBoardService.getUserBoardsOfLast3Months(userId, page, size); + } + + getSuperAdminBoards( + userId: string, + page?: number, + size?: number, + ): Promise { + return this.getBoardService.getSuperAdminBoards(userId, page, size); + } + + getUsersBoards( + userId: string, + page?: number, + size?: number, + ): Promise { + return this.getBoardService.getUsersBoards(userId, page, size); } getBoard(boardId: string, userId: string) { diff --git a/backend/src/modules/boards/controller/boards.controller.ts b/backend/src/modules/boards/controller/boards.controller.ts index 4f0513071..233b2af54 100644 --- a/backend/src/modules/boards/controller/boards.controller.ts +++ b/backend/src/modules/boards/controller/boards.controller.ts @@ -13,7 +13,6 @@ import { Req, UseGuards, } from '@nestjs/common'; -import { BaseParam } from '../../../libs/dto/param/base.param'; import BoardDto from '../dto/board.dto'; import JwtAuthenticationGuard from '../../../libs/guards/jwtAuth.guard'; import RequestWithUser from '../../../libs/interfaces/requestWithUser.interface'; @@ -62,8 +61,7 @@ export default class BoardsController { @Req() request: RequestWithUser, @Query() { page, size }: PaginationParams, ) { - return this.getBoardApp.getBoards( - 'dashboard', + return this.getBoardApp.getUserBoardsOfLast3Months( request.user._id, page, size, @@ -78,9 +76,9 @@ export default class BoardsController { ) { const { _id: userId, isSAdmin } = request.user; if (isSAdmin) { - return this.getBoardApp.getBoards('allBoards', userId, page, size); + return this.getBoardApp.getSuperAdminBoards(userId, page, size); } - return this.getBoardApp.getBoards('myBoards', userId, page, size); + return this.getBoardApp.getUsersBoards(userId, page, size); } @UseGuards(JwtAuthenticationGuard) diff --git a/backend/src/modules/boards/interfaces/applications/get.board.application.interface.ts b/backend/src/modules/boards/interfaces/applications/get.board.application.interface.ts index f08c77a22..ba53bc7de 100644 --- a/backend/src/modules/boards/interfaces/applications/get.board.application.interface.ts +++ b/backend/src/modules/boards/interfaces/applications/get.board.application.interface.ts @@ -1,17 +1,23 @@ import { LeanDocument } from 'mongoose'; import { BoardDocument } from '../../schemas/board.schema'; +import { BoardsAndPage } from '../boards-page.interface'; -export interface GetBoardApplication { - getBoards( - option: 'dashboard' | 'allBoards' | 'myBoards', - userId?: string, +export interface GetBoardApplicationInterface { + getUserBoardsOfLast3Months( + userId: string, + page?: number, + size?: number, + ): Promise; + getSuperAdminBoards( + userId: string, + page?: number, + size?: number, + ): Promise; + getUsersBoards( + userId: string, page?: number, size?: number, - ): Promise<{ - boards: LeanDocument[]; - hasNextPage: boolean; - page: number; - } | null>; + ): Promise; getBoard( boardId: string, userId: string, diff --git a/backend/src/modules/boards/interfaces/boards-page.interface.ts b/backend/src/modules/boards/interfaces/boards-page.interface.ts new file mode 100644 index 000000000..7c5735a80 --- /dev/null +++ b/backend/src/modules/boards/interfaces/boards-page.interface.ts @@ -0,0 +1,8 @@ +import { LeanDocument } from 'mongoose'; +import { BoardDocument } from '../schemas/board.schema'; + +export interface BoardsAndPage { + boards: LeanDocument[]; + hasNextPage: boolean; + page: number; +} diff --git a/backend/src/modules/boards/interfaces/findQuery.ts b/backend/src/modules/boards/interfaces/findQuery.ts new file mode 100644 index 000000000..32f8b08d3 --- /dev/null +++ b/backend/src/modules/boards/interfaces/findQuery.ts @@ -0,0 +1,33 @@ +import { LeanDocument } from 'mongoose'; +import { TeamUserDocument } from '../../teams/schemas/team.user.schema'; + +export type QueryType = { + $and: ( + | { + isSubBoard: boolean; + updatedAt?: { + $gte: number; + }; + $or?: undefined; + } + | { + $or: ( + | { + _id: { + $in: LeanDocument[]; + }; + team?: undefined; + } + | { + team: { + $in?: LeanDocument[]; + $ne?: undefined | null; + }; + _id?: undefined; + } + )[]; + isSubBoard?: undefined; + updatedAt?: undefined; + } + )[]; +}; diff --git a/backend/src/modules/boards/interfaces/services/get.board.service.interface.ts b/backend/src/modules/boards/interfaces/services/get.board.service.interface.ts index 94421ee7b..ff1a53944 100644 --- a/backend/src/modules/boards/interfaces/services/get.board.service.interface.ts +++ b/backend/src/modules/boards/interfaces/services/get.board.service.interface.ts @@ -1,17 +1,23 @@ import { LeanDocument } from 'mongoose'; import { BoardDocument } from '../../schemas/board.schema'; +import { BoardsAndPage } from '../boards-page.interface'; -export interface GetBoardService { - getBoards( - option: 'dashboard' | 'allBoards' | 'myBoards', - userId?: string, +export interface GetBoardServiceInterface { + getUserBoardsOfLast3Months( + userId: string, + page?: number, + size?: number, + ): Promise; + getSuperAdminBoards( + userId: string, + page?: number, + size?: number, + ): Promise; + getUsersBoards( + userId: string, page?: number, size?: number, - ): Promise<{ - boards: LeanDocument[]; - hasNextPage: boolean; - page: number; - } | null>; + ): Promise; getBoardFromRepo( boardId: string, ): Promise | null>; diff --git a/backend/src/modules/boards/services/create.board.service.ts b/backend/src/modules/boards/services/create.board.service.ts index 948a631cb..6557a057b 100644 --- a/backend/src/modules/boards/services/create.board.service.ts +++ b/backend/src/modules/boards/services/create.board.service.ts @@ -52,6 +52,7 @@ export default class CreateBoardServiceImpl implements CreateBoardService { ...boardData, createdBy: userId, dividedBoards: await this.createDividedBoards(dividedBoards, userId), + isSubBoard: isEmpty(dividedBoards), }); } diff --git a/backend/src/modules/boards/services/get.board.service.ts b/backend/src/modules/boards/services/get.board.service.ts index cdb141676..bcb7ec8b3 100644 --- a/backend/src/modules/boards/services/get.board.service.ts +++ b/backend/src/modules/boards/services/get.board.service.ts @@ -3,12 +3,13 @@ import { InjectModel } from '@nestjs/mongoose'; import { Model } from 'mongoose'; import { GetTeamService } from '../../teams/interfaces/services/get.team.service.interface'; import * as Team from '../../teams/interfaces/types'; -import { GetBoardService } from '../interfaces/services/get.board.service.interface'; +import { QueryType } from '../interfaces/findQuery'; +import { GetBoardServiceInterface } from '../interfaces/services/get.board.service.interface'; import Board, { BoardDocument } from '../schemas/board.schema'; import BoardUser, { BoardUserDocument } from '../schemas/board.user.schema'; @Injectable() -export default class GetBoardServiceImpl implements GetBoardService { +export default class GetBoardServiceImpl implements GetBoardServiceInterface { constructor( @InjectModel(Board.name) private boardModel: Model, @InjectModel(BoardUser.name) @@ -34,56 +35,62 @@ export default class GetBoardServiceImpl implements GetBoardService { return { boardIds, teamIds }; } - async getFindQuery( - option: 'dashboard' | 'allBoards' | 'myBoards', + async getUserBoardsOfLast3Months( userId: string, + page: number, + size?: number, ) { const { boardIds, teamIds } = await this.getAllBoardIdsAndTeamIdsOfUser( userId, ); - if (option === 'dashboard') { - const now = new Date(); - const last3Months = new Date().setMonth(now.getMonth() - 3); - return { - $and: [ - { isSubBoard: false, updatedAt: { $gte: last3Months } }, - { $or: [{ _id: { $in: boardIds } }, { team: { $in: teamIds } }] }, - ], - }; - } + const now = new Date(); + const last3Months = new Date().setMonth(now.getMonth() - 3); + const query = { + $and: [ + { isSubBoard: false, updatedAt: { $gte: last3Months } }, + { $or: [{ _id: { $in: boardIds } }, { team: { $in: teamIds } }] }, + ], + }; + + return this.getBoards(false, query, page, size); + } - if (option === 'myBoards') - return { - $and: [ - { isSubBoard: false }, - { $or: [{ _id: { $in: boardIds } }, { team: { $in: teamIds } }] }, - ], - }; + async getSuperAdminBoards(userId: string, page: number, size?: number) { + const { boardIds } = await this.getAllBoardIdsAndTeamIdsOfUser(userId); - return { + const query = { $and: [ { isSubBoard: false }, { $or: [{ _id: { $in: boardIds } }, { team: { $ne: null } }] }, ], }; + + return this.getBoards(true, query, page, size); } - async getBoards( - option: 'dashboard' | 'allBoards' | 'myBoards', - userId: string, - page = 0, - size = 10, - ) { - const query = await this.getFindQuery(option, userId); + async getUsersBoards(userId: string, page: number, size?: number) { + const { boardIds, teamIds } = await this.getAllBoardIdsAndTeamIdsOfUser( + userId, + ); + const query = { + $and: [ + { isSubBoard: false }, + { $or: [{ _id: { $in: boardIds } }, { team: { $in: teamIds } }] }, + ], + }; + return this.getBoards(false, query, page, size); + } + + async getBoards(allBoards: boolean, query: QueryType, page = 0, size = 10) { const count = await this.boardModel.find(query).countDocuments().exec(); const hasNextPage = - page + 1 < Math.ceil(count / (option === 'allBoards' ? count : size)); + page + 1 < Math.ceil(count / (allBoards ? count : size)); const boards = await this.boardModel .find(query) .sort({ updatedAt: 'desc' }) - .skip(option === 'allBoards' ? 0 : page * size) - .limit(option === 'allBoards' ? count : size) + .skip(allBoards ? 0 : page * size) + .limit(allBoards ? count : size) .select('-__v -createdAt -id') .populate({ path: 'createdBy', select: 'firstName lastName' }) .populate({ diff --git a/backend/src/modules/teams/applications/get.team.application.ts b/backend/src/modules/teams/applications/get.team.application.ts index 62cc539b7..d98d91486 100644 --- a/backend/src/modules/teams/applications/get.team.application.ts +++ b/backend/src/modules/teams/applications/get.team.application.ts @@ -1,10 +1,10 @@ import { Inject, Injectable } from '@nestjs/common'; -import { GetTeamApplication } from '../interfaces/applications/get.team.application.interface'; +import { GetTeamApplicationInterface } from '../interfaces/applications/get.team.application.interface'; import { GetTeamService } from '../interfaces/services/get.team.service.interface'; import { TYPES } from '../interfaces/types'; @Injectable() -export class GetTeamApplicationImpl implements GetTeamApplication { +export class GetTeamApplicationImpl implements GetTeamApplicationInterface { constructor( @Inject(TYPES.services.GetTeamService) private getTeamService: GetTeamService, diff --git a/backend/src/modules/teams/interfaces/applications/get.team.application.interface.ts b/backend/src/modules/teams/interfaces/applications/get.team.application.interface.ts index b444948af..0a4928100 100644 --- a/backend/src/modules/teams/interfaces/applications/get.team.application.interface.ts +++ b/backend/src/modules/teams/interfaces/applications/get.team.application.interface.ts @@ -1,3 +1,3 @@ -export interface GetTeamApplication { +export interface GetTeamApplicationInterface { countTeams(userId: string): Promise; }