Skip to content

Commit

Permalink
feat: board user and roles
Browse files Browse the repository at this point in the history
  • Loading branch information
nunocaseiro committed Apr 7, 2022
1 parent 4315497 commit e7f8fcd
Show file tree
Hide file tree
Showing 17 changed files with 816 additions and 1,058 deletions.
21 changes: 11 additions & 10 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,17 @@
"pre-commit": "lint-staged"
},
"dependencies": {
"@nestjs/common": "^8.4.3",
"@faker-js/faker": "^6.1.2",
"@nestjs/common": "^8.4.4",
"@nestjs/config": "^2.0.0",
"@nestjs/core": "^8.4.3",
"@nestjs/core": "^8.4.4",
"@nestjs/jwt": "^8.0.0",
"@nestjs/mongoose": "^9.0.3",
"@nestjs/passport": "^8.2.1",
"@nestjs/platform-express": "^8.4.3",
"@nestjs/platform-socket.io": "^8.4.3",
"@nestjs/platform-express": "^8.4.4",
"@nestjs/platform-socket.io": "^8.4.4",
"@nestjs/schedule": "^1.1.0",
"@nestjs/websockets": "^8.4.3",
"@nestjs/websockets": "^8.4.4",
"@types/bcrypt": "^5.0.0",
"@types/passport-jwt": "^3.0.6",
"@types/passport-local": "^1.0.34",
Expand All @@ -50,19 +51,19 @@
"lint-staged": "^12.3.7",
"mongodb": "^4.5.0",
"mongoose": "^6.2.10",
"mongoose-lean-virtuals": "^0.9.0",
"passport": "^0.5.2",
"passport-jwt": "^4.0.0",
"passport-local": "^1.0.0",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^7.5.5",
"socket.io": "^4.4.1",
"@faker-js/faker": "^6.1.2"
"socket.io": "^4.4.1"
},
"devDependencies": {
"@nestjs/cli": "^8.2.4",
"@nestjs/schematics": "^8.0.9",
"@nestjs/testing": "^8.4.3",
"@nestjs/cli": "^8.2.5",
"@nestjs/schematics": "^8.0.10",
"@nestjs/testing": "^8.4.4",
"@types/express": "^4.17.13",
"@types/jest": "^27.4.1",
"@types/mongodb": "^4.0.7",
Expand Down
7 changes: 7 additions & 0 deletions backend/src/infrastructure/database/mongoose.module.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import { MongooseModule } from '@nestjs/mongoose';
import User, { UserSchema } from '../../modules/users/schemas/user.schema';
import Board, { BoardSchema } from '../../modules/boards/schemas/board.schema';
import BoardUser, {
BoardUserSchema,
} from '../../modules/boards/schemas/board.user.schema';

export const mongooseBoardModule = MongooseModule.forFeature([
{ name: Board.name, schema: BoardSchema },
]);

export const mongooseBoardUserModule = MongooseModule.forFeature([
{ name: BoardUser.name, schema: BoardUserSchema },
]);

export const mongooseUserModule = MongooseModule.forFeature([
{ name: User.name, schema: UserSchema },
]);
5 changes: 5 additions & 0 deletions backend/src/libs/enum/board.roles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export enum BoardRoles {
OWNER = 'owner',
MEMBER = 'member',
RESPONSIBLE = 'responsible',
}
21 changes: 21 additions & 0 deletions backend/src/libs/validators/check-unique-users.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import {
ValidatorConstraint,
ValidatorConstraintInterface,
} from 'class-validator';
import BoardUserDto from '../../modules/boards/dto/board.user.dto';

@ValidatorConstraint({ name: 'checkUniqueUsers', async: false })
export class CheckUniqueUsers implements ValidatorConstraintInterface {
validate(users: BoardUserDto[]) {
const usersIds = users.map((user) => user.user);
if (usersIds.length === new Set(usersIds).size) {
return true;
}

return false;
}

defaultMessage() {
return 'Duplicate users are not allowed';
}
}
7 changes: 5 additions & 2 deletions backend/src/modules/boards/boards.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@ import {
deleteBoardApplication,
getBoardApplication,
} from './boards.providers';
import { mongooseBoardModule } from '../../infrastructure/database/mongoose.module';
import {
mongooseBoardModule,
mongooseBoardUserModule,
} from '../../infrastructure/database/mongoose.module';

@Module({
imports: [UsersModule, mongooseBoardModule],
imports: [UsersModule, mongooseBoardModule, mongooseBoardUserModule],
providers: [
createBoardService,
updateBoardService,
Expand Down
23 changes: 22 additions & 1 deletion backend/src/modules/boards/dto/board.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@ import {
IsOptional,
IsBoolean,
IsNumber,
ValidateIf,
IsMongoId,
Validate,
} from 'class-validator';
import { Transform, TransformFnParams, Type } from 'class-transformer';
import ColumnDto from './column/column.dto';
import BoardUserDto from './board.user.dto';
import { CheckUniqueUsers } from '../../../libs/validators/check-unique-users';

export default class BoardDto {
@IsOptional()
Expand All @@ -33,11 +37,28 @@ export default class BoardDto {
@IsBoolean()
isPublic!: boolean;

@IsOptional()
@ValidateIf((o) => o.isPublic === false)
@IsNotEmpty()
@Transform(({ value }: TransformFnParams) => value.trim())
password?: string;

@IsNotEmpty()
@IsNumber()
maxVotes!: number;

@IsOptional()
@ValidateNested({ each: true })
dividedBoards?: BoardDto[];

// @IsOptional()
// @IsMongoId()
// @IsString()
// team?: string;

@IsOptional()
socketId?: string;

@IsOptional()
@Validate(CheckUniqueUsers)
users!: BoardUserDto[];
}
24 changes: 24 additions & 0 deletions backend/src/modules/boards/dto/board.user.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import {
IsString,
IsNotEmpty,
IsOptional,
IsMongoId,
IsEnum,
} from 'class-validator';
import { BoardRoles } from '../../../libs/enum/board.roles';

export default class BoardUserDto {
@IsOptional()
@IsMongoId()
_id?: string;

@IsString()
@IsNotEmpty()
@IsEnum(BoardRoles, { each: true })
role!: string;

@IsMongoId()
@IsString()
@IsNotEmpty()
user!: string;
}
42 changes: 37 additions & 5 deletions backend/src/modules/boards/schemas/board.schema.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import * as leanVirtualsPlugin from 'mongoose-lean-virtuals';
import * as mongoose from 'mongoose';
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import User from '../../users/schemas/user.schema';
import { ColumnDocument, ColumnSchema } from './column.schema';
import User from '../../users/schemas/user.schema';

export type BoardDocument = Board & mongoose.Document;

@Schema()
@Schema({
timestamps: true,
toJSON: {
virtuals: true,
},
})
export default class Board {
@Prop({ nullable: false })
title!: string;
Expand All @@ -16,17 +22,43 @@ export default class Board {
@Prop({ nullable: true })
password?: string;

@Prop({ type: Date, default: Date.now })
creationDate!: Date;
@Prop({ type: mongoose.Schema.Types.ObjectId, ref: 'User' })
submitedByUser!: mongoose.Schema.Types.ObjectId;

@Prop({ nullable: false })
maxVotes!: number;

@Prop({ nullable: false, type: [ColumnSchema] })
columns!: ColumnDocument[];

@Prop({ type: mongoose.Schema.Types.ObjectId, ref: 'User', nullable: false })
@Prop({ type: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Board' }] })
dividedBoards!: Board[] | mongoose.Schema.Types.ObjectId[];

// @Prop({
// type: mongoose.Schema.Types.ObjectId,
// ref: 'Team',
// nullable: true,
// default: null,
// })
// team!: Team | mongoose.Schema.Types.ObjectId;

@Prop({ type: mongoose.Schema.Types.ObjectId, ref: 'User' })
createdBy!: User | mongoose.Schema.Types.ObjectId;

@Prop({ default: false })
recurrent!: boolean;

@Prop({ default: false })
isSubBoard!: boolean;
}

export const BoardSchema = SchemaFactory.createForClass(Board);

BoardSchema.plugin(leanVirtualsPlugin);

BoardSchema.virtual('users', {
ref: 'BoardUser',
localField: '_id',
foreignField: 'board',
justOne: false,
});
31 changes: 31 additions & 0 deletions backend/src/modules/boards/schemas/board.user.schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import * as mongoose from 'mongoose';
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { BoardRoles } from '../../../libs/enum/board.roles';
import User from '../../users/schemas/user.schema';

export type BoardUserDocument = BoardUser & mongoose.Document;

@Schema({
toJSON: {
virtuals: true,
},
})
export default class BoardUser {
@Prop({
nullable: false,
type: String,
enum: [BoardRoles.RESPONSIBLE, BoardRoles.MEMBER, BoardRoles.OWNER],
})
role!: string;

@Prop({ type: mongoose.Schema.Types.ObjectId, ref: 'User', nullable: false })
user!: User | mongoose.Schema.Types.ObjectId;

@Prop({ type: mongoose.Schema.Types.ObjectId, ref: 'Board', nullable: false })
board!: mongoose.Schema.Types.ObjectId;

@Prop({ nullable: false })
votesCount!: number;
}

export const BoardUserSchema = SchemaFactory.createForClass(BoardUser);
61 changes: 58 additions & 3 deletions backend/src/modules/boards/services/create.board.service.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,79 @@
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { BoardRoles } from '../../../libs/enum/board.roles';
import { encrypt } from '../../../libs/utils/bcrypt';
import isEmpty from '../../../libs/utils/isEmpty';
import BoardDto from '../dto/board.dto';
import { CreateBoardService } from '../interfaces/services/create.board.service.interface';
import Board, { BoardDocument } from '../schemas/board.schema';
import BoardUser, { BoardUserDocument } from '../schemas/board.user.schema';

@Injectable()
export default class CreateBoardServiceImpl implements CreateBoardService {
constructor(
@InjectModel(Board.name) private boardModel: Model<BoardDocument>,
@InjectModel(BoardUser.name)
private boardUserModel: Model<BoardUserDocument>,
) {}

async createDividedBoards(boards: BoardDto[], userId: string) {
const newBoardsIds: string[] = [];

await boards.reduce(async (previous, current) => {
await previous;
const newBoard = await this.boardModel.create({
...current,
userId,
isSubBoard: true,
});
const { users } = current;
newBoardsIds.push(newBoard._id);

if (!isEmpty(users)) {
await users.reduce(async (prevUser, currentUser) => {
await prevUser;
await this.boardUserModel.create({
...currentUser,
board: newBoard._id,
});
}, Promise.resolve());
}
}, Promise.resolve());
return newBoardsIds;
}

async create(boardData: BoardDto, userId: string) {
if (boardData.password) {
boardData.password = await encrypt(boardData.password);
const { password, dividedBoards, users } = boardData;
if (password) {
boardData.password = await encrypt(password);
}
return this.boardModel.create({

const newBoard = await this.boardModel.create({
...boardData,
createdBy: userId,
dividedBoards: await this.createDividedBoards(
dividedBoards ?? [],
userId,
),
});

const newUsers = [...users];
newUsers.push({
user: userId.toString(),
role: BoardRoles.OWNER,
});

if (!isEmpty(newUsers)) {
await newUsers.reduce(async (prevUser, currentUser) => {
await prevUser;
await this.boardUserModel.create({
...currentUser,
board: newBoard._id,
});
}, Promise.resolve());
}

return newBoard;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,8 @@ export class GetUserApplicationImpl implements GetUserApplication {
getByEmail(email: string) {
return this.getUserService.getByEmail(email);
}

countUsers() {
return this.getUserService.countUsers();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ import { UserDocument } from '../../schemas/user.schema';

export interface GetUserApplication {
getByEmail(email: string): Promise<LeanDocument<UserDocument> | null>;
countUsers(): Promise<number>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ export interface GetUserService {
refreshToken: string,
userId: string,
): Promise<LeanDocument<UserDocument> | false>;
countUsers(): Promise<number>;
}
Loading

0 comments on commit e7f8fcd

Please sign in to comment.