diff --git a/src/admin/admin.controller.spec.ts b/src/admin/admin.controller.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..0b8ca9028651014a25b02e06c12425dee70ab800 --- /dev/null +++ b/src/admin/admin.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { AdminController } from './admin.controller'; + +describe('AdminController', () => { + let controller: AdminController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [AdminController], + }).compile(); + + controller = module.get<AdminController>(AdminController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/src/admin/admin.controller.ts b/src/admin/admin.controller.ts new file mode 100644 index 0000000000000000000000000000000000000000..5dc32d88222999fdd011ae76aae56cc548da3627 --- /dev/null +++ b/src/admin/admin.controller.ts @@ -0,0 +1,45 @@ +import { Body } from '@nestjs/common'; +import { Controller, Get, Post, UseGuards } from '@nestjs/common'; +import { ApiOperation } from '@nestjs/swagger'; +import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; +import { Roles } from '../users/decorators/roles.decorator'; +import { RolesGuard } from '../users/guards/roles.guard'; +import { UsersService } from '../users/users.service'; +import { PendingStructureDto } from './dto/pending-structure.dto'; + +@Controller('admin') +export class AdminController { + constructor(private usersService: UsersService) {} + + @UseGuards(JwtAuthGuard, RolesGuard) + @Roles('admin') + @Get('pendingStructures') + @ApiOperation({ description: 'Get pending structre for validation' }) + public getPendingAttachments(): Promise<PendingStructureDto[]> { + return this.usersService.getPendingStructures(); + } + + @UseGuards(JwtAuthGuard, RolesGuard) + @Roles('admin') + @Post('validatePendingStructure') + @ApiOperation({ description: 'Validate structure ownership' }) + public validatePendingStructure(@Body() pendingStructureDto: PendingStructureDto) { + return this.usersService.validatePendingStructure( + pendingStructureDto.userEmail, + pendingStructureDto.structureId, + true + ); + } + + @UseGuards(JwtAuthGuard, RolesGuard) + @Roles('admin') + @Post('rejectPendingStructure') + @ApiOperation({ description: 'Refuse structure ownership' }) + public refusePendingStructure(@Body() pendingStructureDto: PendingStructureDto) { + return this.usersService.validatePendingStructure( + pendingStructureDto.userEmail, + pendingStructureDto.structureId, + false + ); + } +} diff --git a/src/admin/admin.module.ts b/src/admin/admin.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..3376e00f618f654dbdb6213a2cfd48ddb8e99cb2 --- /dev/null +++ b/src/admin/admin.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { UsersModule } from '../users/users.module'; +import { UsersService } from '../users/users.service'; +import { AdminController } from './admin.controller'; +import { AdminService } from './admin.service'; + +@Module({ + imports: [UsersModule], + controllers: [AdminController], + providers: [AdminService], +}) +export class AdminModule {} diff --git a/src/admin/admin.service.spec.ts b/src/admin/admin.service.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..5e5e153df03a8437915bb59ff2861c9366e40f02 --- /dev/null +++ b/src/admin/admin.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { AdminService } from './admin.service'; + +describe('AdminService', () => { + let service: AdminService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [AdminService], + }).compile(); + + service = module.get<AdminService>(AdminService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/src/admin/admin.service.ts b/src/admin/admin.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..796f9fd1a19a12cbc184a3364dbebe88e2165365 --- /dev/null +++ b/src/admin/admin.service.ts @@ -0,0 +1,4 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class AdminService {} diff --git a/src/admin/dto/pending-structure.dto.ts b/src/admin/dto/pending-structure.dto.ts new file mode 100644 index 0000000000000000000000000000000000000000..04936833a26fea61d590dac33f3f682492f7c9dc --- /dev/null +++ b/src/admin/dto/pending-structure.dto.ts @@ -0,0 +1,14 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsEmail, IsNotEmpty, IsNumber } from 'class-validator'; + +export class PendingStructureDto { + @IsNotEmpty() + @IsEmail() + @ApiProperty({ type: String }) + readonly userEmail: string; + + @IsNotEmpty() + @IsNumber() + @ApiProperty({ type: Number }) + readonly structureId: number; +} diff --git a/src/app.module.ts b/src/app.module.ts index d3a7ebdbbc56ff9f03b326d78405b5cb81008137..0d49be79613c4a4a01487ac0ab4110fe19006ba7 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -8,6 +8,7 @@ import { AuthModule } from './auth/auth.module'; import { UsersModule } from './users/users.module'; import { MailerModule } from './mailer/mailer.module'; import { TclModule } from './tcl/tcl.module'; +import { AdminModule } from './admin/admin.module'; @Module({ imports: [ ConfigurationModule, @@ -20,6 +21,7 @@ import { TclModule } from './tcl/tcl.module'; UsersModule, MailerModule, TclModule, + AdminModule, ], controllers: [AppController], }) diff --git a/src/auth/auth.controller.spec.ts b/src/auth/auth.controller.spec.ts index 819bebe40d056c7a9315d71c309bd092e61e17d5..52a4184107d34402e9bdd97e8d5e9dfd27209fb4 100644 --- a/src/auth/auth.controller.spec.ts +++ b/src/auth/auth.controller.spec.ts @@ -4,7 +4,7 @@ import { PassportModule } from '@nestjs/passport'; import { Test, TestingModule } from '@nestjs/testing'; import { ConfigurationModule } from '../configuration/configuration.module'; import { MailerModule } from '../mailer/mailer.module'; -import { User } from '../users/user.schema'; +import { User } from '../users/schemas/user.schema'; import { UsersService } from '../users/users.service'; import { AuthController } from './auth.controller'; import { AuthService } from './auth.service'; diff --git a/src/auth/auth.service.spec.ts b/src/auth/auth.service.spec.ts index 0582053c0ba31e3b72bc38e036442121a5e58ab1..2e85b13e99460b761605d520d20d936392dc2d9c 100644 --- a/src/auth/auth.service.spec.ts +++ b/src/auth/auth.service.spec.ts @@ -5,7 +5,7 @@ import { PassportModule } from '@nestjs/passport'; import { Test, TestingModule } from '@nestjs/testing'; import { ConfigurationModule } from '../configuration/configuration.module'; import { MailerModule } from '../mailer/mailer.module'; -import { User } from '../users/user.schema'; +import { User } from '../users/schemas/user.schema'; import { UsersService } from '../users/users.service'; import { AuthService } from './auth.service'; import { LoginDto } from './login-dto'; diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index 2719b0ba95e55f801e744ed89c98f11effeca726..54b820141ed6bd7776f8911e8c679893d6b4c20b 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -2,9 +2,8 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { UsersService } from '../users/users.service'; import { JwtService } from '@nestjs/jwt'; import { LoginDto } from './login-dto'; -import { CreateUserDto } from '../users/create-user.dto'; import { DateTime } from 'luxon'; -import { User } from '../users/user.schema'; +import { User } from '../users/schemas/user.schema'; @Injectable() export class AuthService { @@ -34,10 +33,10 @@ export class AuthService { }; } - private _createToken({ email }: CreateUserDto): any { + private _createToken(user: User): any { const local = DateTime.local().setZone('Europe/Paris'); return { - accessToken: this.jwtService.sign({ email }), + accessToken: this.jwtService.sign({ email: user.email, role: user.role }), expiresAt: local.plus({ days: 1 }), }; } diff --git a/src/configuration/config.ts b/src/configuration/config.ts index b6d29acca6ec81c264ea73c4c482790a914a3c36..00bbfff6d98d45181cad4ed3e58d048e1467a3a5 100644 --- a/src/configuration/config.ts +++ b/src/configuration/config.ts @@ -21,5 +21,13 @@ export const config = { ejs: 'resetPassword.ejs', json: 'resetPassword.json', }, + adminStructureClaim: { + ejs: 'adminStructureClaim.ejs', + json: 'adminStructureClaim.json', + }, + structureClaimValidation: { + ejs: 'structureClaimValidation.ejs', + json: 'structureClaimValidation.json', + }, }, }; diff --git a/src/mailer/mail-templates/adminStructureClaim.ejs b/src/mailer/mail-templates/adminStructureClaim.ejs new file mode 100644 index 0000000000000000000000000000000000000000..12ba539835a44a9a609bd7d1937d45ec48e09474 --- /dev/null +++ b/src/mailer/mail-templates/adminStructureClaim.ejs @@ -0,0 +1,6 @@ +Bonjour<br /> +<br /> +Une nouvelle structure a été revendiquée. Pour valider ou refuser la demande, merci de vous rendre sur +<a href="<%= config.protocol %>://<%= config.host %><%= config.port ? ':' + config.port : '' %>/admin">ce lien</a>. +<br /> +Ce mail est un mail automatique. Merci de ne pas y répondre. diff --git a/src/mailer/mail-templates/adminStructureClaim.json b/src/mailer/mail-templates/adminStructureClaim.json new file mode 100644 index 0000000000000000000000000000000000000000..bad53f08f4c0debbe28b988b65f37b6a3902ddb2 --- /dev/null +++ b/src/mailer/mail-templates/adminStructureClaim.json @@ -0,0 +1,3 @@ +{ + "subject": "Nouvelle demande de revendication de structure" +} diff --git a/src/mailer/mail-templates/structureClaimValidation.ejs b/src/mailer/mail-templates/structureClaimValidation.ejs new file mode 100644 index 0000000000000000000000000000000000000000..eca79ec7fe230a7dd947880ee5795c57cd1647cc --- /dev/null +++ b/src/mailer/mail-templates/structureClaimValidation.ejs @@ -0,0 +1,6 @@ +Bonjour<br /> +<br /> +La demande de rattachement de structure a votre compte a été <strong><%= status %></strong>. +<br /> +<br /> +Ce mail est un mail automatique. Merci de ne pas y répondre. diff --git a/src/mailer/mail-templates/structureClaimValidation.json b/src/mailer/mail-templates/structureClaimValidation.json new file mode 100644 index 0000000000000000000000000000000000000000..bdcb607dc59c9beed42c21a607287be19c06e217 --- /dev/null +++ b/src/mailer/mail-templates/structureClaimValidation.json @@ -0,0 +1,3 @@ +{ + "subject": "Votre demande de rattachement, Réseau des Acteurs de la Médiation Numérique de la Métropole de Lyon" +} diff --git a/src/structures/structures.controller.ts b/src/structures/structures.controller.ts index eaa5722913675e3d639e7cae7944486e24097b22..cce9d59b0e76e601fc080358c1ba1bfe03e872a5 100644 --- a/src/structures/structures.controller.ts +++ b/src/structures/structures.controller.ts @@ -1,5 +1,5 @@ import { Body, Controller, Get, Param, ParseIntPipe, Post, Put, Query } from '@nestjs/common'; -import { User } from '../users/user.schema'; +import { User } from '../users/schemas/user.schema'; import { UsersService } from '../users/users.service'; import { CreateStructureDto } from './dto/create-structure.dto'; import { QueryStructure } from './dto/query-structure.dto'; @@ -22,7 +22,8 @@ export class StructuresController { } @Put(':id') - public async update(@Param('id') id: number, @Body() body: structureDto) { + //TODO: protect, only structure owner can edit it + public async update(@Param('id') id: number, @Body() body: structureDto): Promise<Structure> { return this.structureService.update(id, body); } @@ -37,7 +38,7 @@ export class StructuresController { } @Post(':id/claim') - public async claim(@Param('id', new ParseIntPipe()) idStructure: number, @Body() user: User) { + public async claim(@Param('id', new ParseIntPipe()) idStructure: number, @Body() user: User): Promise<number[]> { return this.userService.updateStructureLinked(user.email, idStructure); } diff --git a/src/structures/structures.service.ts b/src/structures/structures.service.ts index 85d241a5f62339cbdd2768667f286ad75dd2ec46..e1cb232eed112e76dc63e370017564cef63471ba 100644 --- a/src/structures/structures.service.ts +++ b/src/structures/structures.service.ts @@ -26,6 +26,7 @@ export class StructuresService { createdStructure.save(); user.structuresLink.push(createdStructure.id); user.save(); + return createdStructure; } @@ -116,13 +117,11 @@ export class StructuresService { }); } - public async isClaimed(idStructure): Promise<boolean> { - const users = await this.userService.findAll(); - users.forEach((user) => { - if (user.structuresLink.includes(idStructure)) { - return true; - } - }); + public async isClaimed(structureId: number): Promise<boolean> { + const isStructureClaimed = await this.userService.isStructureClaimed(structureId.toString()); + if (isStructureClaimed) { + return true; + } return false; } diff --git a/src/users/decorators/roles.decorator.ts b/src/users/decorators/roles.decorator.ts new file mode 100644 index 0000000000000000000000000000000000000000..b0376727cc30742bda0e26d1e498c4f36fd4be02 --- /dev/null +++ b/src/users/decorators/roles.decorator.ts @@ -0,0 +1,3 @@ +import { SetMetadata } from '@nestjs/common'; + +export const Roles = (...roles: string[]) => SetMetadata('roles', roles); diff --git a/src/users/change-email.dto.ts b/src/users/dto/change-email.dto.ts similarity index 100% rename from src/users/change-email.dto.ts rename to src/users/dto/change-email.dto.ts diff --git a/src/users/change-password.dto.ts b/src/users/dto/change-password.dto.ts similarity index 100% rename from src/users/change-password.dto.ts rename to src/users/dto/change-password.dto.ts diff --git a/src/users/create-user.dto.ts b/src/users/dto/create-user.dto.ts similarity index 89% rename from src/users/create-user.dto.ts rename to src/users/dto/create-user.dto.ts index acb92be7f354d6a6afc05b729a4e6c0da247f6f3..34297e1cae9c8ac7634be3311347a8fa316abb12 100644 --- a/src/users/create-user.dto.ts +++ b/src/users/dto/create-user.dto.ts @@ -14,5 +14,5 @@ export class CreateUserDto { @IsArray() @IsOptional() - structuresLink?: Array<number>; + pendingStructuresLink?: Array<number>; } diff --git a/src/users/reset-password-apply.dto.ts b/src/users/dto/reset-password-apply.dto.ts similarity index 100% rename from src/users/reset-password-apply.dto.ts rename to src/users/dto/reset-password-apply.dto.ts diff --git a/src/users/reset-password.dto.ts b/src/users/dto/reset-password.dto.ts similarity index 100% rename from src/users/reset-password.dto.ts rename to src/users/dto/reset-password.dto.ts diff --git a/src/users/guards/roles.guard.ts b/src/users/guards/roles.guard.ts new file mode 100644 index 0000000000000000000000000000000000000000..2ad8873575a0458ca5f15ffa6568f6af8767cbdd --- /dev/null +++ b/src/users/guards/roles.guard.ts @@ -0,0 +1,33 @@ +import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import { UserRole } from '../enum/user-role.enum'; + +@Injectable() +export class RolesGuard implements CanActivate { + constructor(private reflector: Reflector) {} + + canActivate(context: ExecutionContext): boolean { + const roles = this.reflector.get<string[]>('roles', context.getHandler()); + if (!roles) { + return true; + } + const request = context.switchToHttp().getRequest(); + const user = request.user; + return this.matchRoles(user.role, roles[0]); + } + + /** + * Return true if user is admin or if the requested role match the user role. + * @param userRole user role from request + * @param expectedRole role requested by the endpoint + */ + private matchRoles(userRole: number, expectedRole: string): boolean { + if (userRole === UserRole.admin) { + return true; + } else if (userRole === UserRole[expectedRole]) { + return true; + } else { + return false; + } + } +} diff --git a/src/users/user.interface.ts b/src/users/interfaces/user.interface.ts similarity index 90% rename from src/users/user.interface.ts rename to src/users/interfaces/user.interface.ts index d235abc515217700995b46e9fa21a851af6ac2f8..243b6057b04185acfadae85a87863f0eb37f6dad 100644 --- a/src/users/user.interface.ts +++ b/src/users/interfaces/user.interface.ts @@ -11,4 +11,5 @@ export interface IUser extends Document { changeEmailToken: string; newEmail: string; structuresLink: number[]; + pendingStructuresLink: number[]; } diff --git a/src/users/user.schema.ts b/src/users/schemas/user.schema.ts similarity index 85% rename from src/users/user.schema.ts rename to src/users/schemas/user.schema.ts index 490f7b424b14c5c756aa30a65ce0d84805f903b8..7e019777ac5a768bf84eb00259b2337fb910d791 100644 --- a/src/users/user.schema.ts +++ b/src/users/schemas/user.schema.ts @@ -1,5 +1,5 @@ import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; -import { UserRole } from './enum/user-role.enum'; +import { UserRole } from '../enum/user-role.enum'; @Schema() export class User { @Prop({ required: true }) @@ -28,6 +28,9 @@ export class User { @Prop({ default: null }) structuresLink: number[]; + + @Prop({ default: null }) + pendingStructuresLink: number[]; } export const UserSchema = SchemaFactory.createForClass(User); diff --git a/src/users/users.controller.spec.ts b/src/users/users.controller.spec.ts index 714006debf399341880614d0a8e280e93bbadb7c..7dd57ee72fcb1a6243abc0c94b7351825843f492 100644 --- a/src/users/users.controller.spec.ts +++ b/src/users/users.controller.spec.ts @@ -3,7 +3,7 @@ import { getModelToken } from '@nestjs/mongoose'; import { Test, TestingModule } from '@nestjs/testing'; import { ConfigurationModule } from '../configuration/configuration.module'; import { MailerService } from '../mailer/mailer.service'; -import { User } from './user.schema'; +import { User } from './schemas/user.schema'; import { UsersController } from './users.controller'; import { UsersService } from './users.service'; diff --git a/src/users/users.controller.ts b/src/users/users.controller.ts index 1585ae1a31d515e9a0cecfd0f65578566e0694b2..9c8c2c31b19ad0fec488cc11fd3ec1e53976d93a 100644 --- a/src/users/users.controller.ts +++ b/src/users/users.controller.ts @@ -1,11 +1,11 @@ import { Body, Controller, Get, Param, Post, Query, Request, UseGuards } from '@nestjs/common'; import { ApiOperation, ApiParam, ApiResponse } from '@nestjs/swagger'; import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; -import { PasswordChangeDto } from './change-password.dto'; -import { EmailChangeDto } from './change-email.dto'; -import { CreateUserDto } from './create-user.dto'; -import { PasswordResetApplyDto } from './reset-password-apply.dto'; -import { PasswordResetDto } from './reset-password.dto'; +import { PasswordChangeDto } from './dto/change-password.dto'; +import { EmailChangeDto } from './dto/change-email.dto'; +import { CreateUserDto } from './dto/create-user.dto'; +import { PasswordResetApplyDto } from './dto/reset-password-apply.dto'; +import { PasswordResetDto } from './dto/reset-password.dto'; import { UsersService } from './users.service'; @Controller('users') @@ -26,9 +26,9 @@ export class UsersController { public async create(@Body() createUserDto: CreateUserDto) { // remove structureId for creation and add structure after let structureId = null; - if (createUserDto.structuresLink.length > 0) { - structureId = createUserDto.structuresLink[0]; - delete createUserDto.structuresLink; + if (createUserDto.pendingStructuresLink.length > 0) { + structureId = createUserDto.pendingStructuresLink[0]; + delete createUserDto.pendingStructuresLink; } const user = await this.usersService.create(createUserDto); if (structureId) { diff --git a/src/users/users.module.ts b/src/users/users.module.ts index 69d7ee133b34253305a5d91968d8fe2da518deeb..ae73bfe01263691656b98892941287ea85642e23 100644 --- a/src/users/users.module.ts +++ b/src/users/users.module.ts @@ -2,7 +2,7 @@ import { Module } from '@nestjs/common'; import { MongooseModule } from '@nestjs/mongoose'; import { UsersService } from './users.service'; import { UsersController } from './users.controller'; -import { User, UserSchema } from './user.schema'; +import { User, UserSchema } from './schemas/user.schema'; import { MailerModule } from '../mailer/mailer.module'; @Module({ diff --git a/src/users/users.service.spec.ts b/src/users/users.service.spec.ts index e797c49a4dd38cb22d2d2b9f8ecbfe623ce3cd1b..e24a4807c60a4d93623c6d6b03220032e34905b1 100644 --- a/src/users/users.service.spec.ts +++ b/src/users/users.service.spec.ts @@ -1,12 +1,12 @@ import { Test, TestingModule } from '@nestjs/testing'; import { MailerModule } from '../mailer/mailer.module'; -import { User } from './user.schema'; +import { User } from './schemas/user.schema'; import { UsersService } from './users.service'; import { getModelToken } from '@nestjs/mongoose'; -import { CreateUserDto } from './create-user.dto'; +import { CreateUserDto } from './dto/create-user.dto'; import { HttpException, HttpStatus } from '@nestjs/common'; import { LoginDto } from '../auth/login-dto'; -import { EmailChangeDto } from './change-email.dto'; +import { EmailChangeDto } from './dto/change-email.dto'; describe('UsersService', () => { let service: UsersService; diff --git a/src/users/users.service.ts b/src/users/users.service.ts index a14e86643cf2ec3d71fecdbec71d35ebf690c9b7..606f097d649200f722e7b3cd235a0770f331d7a1 100644 --- a/src/users/users.service.ts +++ b/src/users/users.service.ts @@ -5,15 +5,16 @@ import * as ejs from 'ejs'; import * as crypto from 'crypto'; import { Model } from 'mongoose'; import { LoginDto } from '../auth/login-dto'; -import { CreateUserDto } from './create-user.dto'; -import { User } from './user.schema'; +import { CreateUserDto } from './dto/create-user.dto'; +import { User } from './schemas/user.schema'; import { MailerService } from '../mailer/mailer.service'; -import { IUser } from './user.interface'; -import { EmailChangeDto } from './change-email.dto'; +import { IUser } from './interfaces/user.interface'; +import { EmailChangeDto } from './dto/change-email.dto'; +import { PendingStructureDto } from '../admin/dto/pending-structure.dto'; @Injectable() export class UsersService { - constructor(@InjectModel(User.name) private userModel: Model<IUser>, private mailerService: MailerService) {} + constructor(@InjectModel(User.name) private userModel: Model<IUser>, private readonly mailerService: MailerService) {} /** * Create a user account @@ -36,6 +37,7 @@ export class UsersService { // Send verification email createUser = await this.verifyUserMail(createUser); createUser.save(); + this.sendAdminStructureValidationMail(); return await this.findOne(createUserDto.email); } @@ -124,6 +126,42 @@ export class UsersService { return user; } + /** + * Send to all admins validation email for structures + * a new account. + * @param user User + */ + private async sendAdminStructureValidationMail(): Promise<any> { + const config = this.mailerService.config; + const ejsPath = this.mailerService.getTemplateLocation(config.templates.adminStructureClaim.ejs); + const jsonConfig = this.mailerService.loadJsonConfig(config.templates.adminStructureClaim.json); + + const html = await ejs.renderFile(ejsPath, { + config, + }); + const admins = await this.getAdmins(); + admins.forEach((admin) => { + this.mailerService.send(admin.email, jsonConfig.subject, html); + }); + } + + /** + * Send approval email for user + * a new account. + * @param user User + */ + private async sendStructureClaimApproval(userEmail: string, status: boolean): Promise<any> { + const config = this.mailerService.config; + const ejsPath = this.mailerService.getTemplateLocation(config.templates.structureClaimValidation.ejs); + const jsonConfig = this.mailerService.loadJsonConfig(config.templates.structureClaimValidation.json); + + const html = await ejs.renderFile(ejsPath, { + config, + status: status ? 'accepté' : 'refusée', + }); + this.mailerService.send(userEmail, jsonConfig.subject, html); + } + /** * Check that the given token is associated to userId. If it's true, validate user account. * @param userId string @@ -241,13 +279,74 @@ export class UsersService { throw new HttpException('Invalid token', HttpStatus.UNAUTHORIZED); } - public async updateStructureLinked(userEmail: string, idStructure: number): Promise<any> { + public async getAdmins(): Promise<User[]> { + return this.userModel.find({ role: 1 }).exec(); + } + + public async isStructureClaimed(structureId: string): Promise<User> { + return this.userModel + .findOne({ $or: [{ pendingStructuresLink: parseInt(structureId) }, { structuresLink: parseInt(structureId) }] }) + .exec(); + } + + public async updateStructureLinked(userEmail: string, idStructure: number): Promise<number[]> { const user = await this.findOne(userEmail, true); if (user) { - user.structuresLink.push(idStructure); + user.pendingStructuresLink.push(idStructure); user.save(); - return user.structuresLink; + this.sendAdminStructureValidationMail(); + return user.pendingStructuresLink; } throw new HttpException('Invalid user', HttpStatus.NOT_FOUND); } + + /** + * Return all pending attachments of all profiles + */ + public async getPendingStructures(): Promise<PendingStructureDto[]> { + const users = await this.userModel.find(); + const structuresPending = []; + + // For each user, if they have structures in pending, push them in tab and return this tab. + users.forEach((user) => { + if (user.pendingStructuresLink.length) { + user.pendingStructuresLink.forEach((structureId) => { + structuresPending.push({ userEmail: user.email, structureId: structureId }); + }); + } + }); + return structuresPending; + } + + /** + * Validate or refuse a pending structure given a email and structure id + */ + public async validatePendingStructure( + userEmail: string, + structureId: number, + validate: boolean + ): Promise<PendingStructureDto[]> { + const users = await this.findOne(userEmail); + let status = false; + if (!users) { + throw new HttpException('User not found', HttpStatus.NOT_FOUND); + } + if (users.pendingStructuresLink.includes(structureId)) { + users.pendingStructuresLink = users.pendingStructuresLink.filter((item) => item !== structureId); + // If it's a validation case, push structureId into validated user structures + if (validate) { + users.structuresLink.push(structureId); + // Send validation email + status = true; + } + this.sendStructureClaimApproval(userEmail, status); + await users.save(); + return this.getPendingStructures(); + } else { + throw new HttpException( + 'Cannot validate strucutre. It might have been already validate, or the structure does`nt belong to the user', + HttpStatus.NOT_FOUND + ); + } + } }