diff --git a/src/app.module.ts b/src/app.module.ts index e0aa57e2278e56f027bcfe0d0db969e5936708c8..467fe404c31f6fdeadc948aec96bdf136aa676f3 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -11,6 +11,7 @@ import { MailerModule } from './mailer/mailer.module'; import { TclModule } from './tcl/tcl.module'; import { AdminModule } from './admin/admin.module'; import { PostsModule } from './posts/posts.module'; +import { TempUserModule } from './temp-user/temp-user.module'; @Module({ imports: [ ConfigurationModule, @@ -26,6 +27,7 @@ import { PostsModule } from './posts/posts.module'; TclModule, AdminModule, PostsModule, + TempUserModule, ], controllers: [AppController], }) diff --git a/src/configuration/config.dev.ts b/src/configuration/config.dev.ts index 30d4a3f124d3f64e1c4427164e985e193a07535f..90917c6903316ef855ee1e2d21e7b93dcb7aa76a 100644 --- a/src/configuration/config.dev.ts +++ b/src/configuration/config.dev.ts @@ -7,31 +7,4 @@ export const configDev = { from: 'inclusionnumerique@grandlyon.com', from_name: 'Réseau des acteurs de la médiation numérique', replyTo: 'inclusionnumerique@grandlyon.com', - templates: { - directory: './src/mailer/mail-templates', - verify: { - ejs: 'verify.ejs', - json: 'verify.json', - }, - changeEmail: { - ejs: 'changeEmail.ejs', - json: 'changeEmail.json', - }, - resetPassword: { - ejs: 'resetPassword.ejs', - json: 'resetPassword.json', - }, - adminStructureClaim: { - ejs: 'adminStructureClaim.ejs', - json: 'adminStructureClaim.json', - }, - structureClaimValidation: { - ejs: 'structureClaimValidation.ejs', - json: 'structureClaimValidation.json', - }, - structureOutdatedInfo: { - ejs: 'structureOutdatedInfo.ejs', - json: 'structureOutdatedInfo.json', - }, - }, }; diff --git a/src/configuration/config.prod.ts b/src/configuration/config.prod.ts index 19167f8c124329bc4acf99b1d1ce7c3dc84ed032..d9333dd6499c2498a7853abf5ad97b8e1a2c849c 100644 --- a/src/configuration/config.prod.ts +++ b/src/configuration/config.prod.ts @@ -7,31 +7,4 @@ export const configProd = { from: 'inclusionnumerique@grandlyon.com', from_name: 'Réseau des acteurs de la médiation numérique', replyTo: 'inclusionnumerique@grandlyon.com', - templates: { - directory: './src/mailer/mail-templates', - verify: { - ejs: 'verify.ejs', - json: 'verify.json', - }, - changeEmail: { - ejs: 'changeEmail.ejs', - json: 'changeEmail.json', - }, - resetPassword: { - ejs: 'resetPassword.ejs', - json: 'resetPassword.json', - }, - adminStructureClaim: { - ejs: 'adminStructureClaim.ejs', - json: 'adminStructureClaim.json', - }, - structureClaimValidation: { - ejs: 'structureClaimValidation.ejs', - json: 'structureClaimValidation.json', - }, - structureOutdatedInfo: { - ejs: 'structureOutdatedInfo.ejs', - json: 'structureOutdatedInfo.json', - }, - }, }; diff --git a/src/configuration/config.ts b/src/configuration/config.ts index 7bb9d6f5a8b02c9fc654caa84f5cb74a223c99e9..e9d940409b5b8b927e138bb8b33a9e2ff6935743 100644 --- a/src/configuration/config.ts +++ b/src/configuration/config.ts @@ -37,5 +37,13 @@ export const config = { ejs: 'apticStructureDuplication.ejs', json: 'apticStructureDuplication.json', }, + tempUserRegistration: { + ejs: 'tempUserRegistration.ejs', + json: 'tempUserRegistration.json', + }, + structureJoinRequest: { + ejs: 'structureJoinRequest.ejs', + json: 'structureJoinRequest.json', + }, }, }; diff --git a/src/configuration/configuration.service.ts b/src/configuration/configuration.service.ts index 13b4f5e4d827e55bc85ba4cacb6dc54ada5c887c..ca6f9e6c44a78e2259c3b3e70c8a5168d3d30d95 100644 --- a/src/configuration/configuration.service.ts +++ b/src/configuration/configuration.service.ts @@ -10,9 +10,11 @@ export class ConfigurationService { // Initializing conf with values from var env if (process.env.NODE_ENV && process.env.NODE_ENV === 'production') { this._config = configProd; + this._config.templates = config.templates; // Add mail templates Logger.log('App started with production conf', 'ConfigurationService'); } else if (process.env.NODE_ENV && process.env.NODE_ENV === 'dev') { this._config = configDev; + this._config.templates = config.templates; // Add mail templates Logger.log('App started with dev conf', 'ConfigurationService'); } else { this._config = config; diff --git a/src/mailer/mail-templates/structureJoinRequest.ejs b/src/mailer/mail-templates/structureJoinRequest.ejs new file mode 100644 index 0000000000000000000000000000000000000000..550665e04bb48c9f796a21b97fef4dfc11407107 --- /dev/null +++ b/src/mailer/mail-templates/structureJoinRequest.ejs @@ -0,0 +1,21 @@ +Bonjour<br /> +<br /> +Vous recevez ce message car <strong><%= surname %></strong> <strong><%= name %></strong> demande a rejoindre votre +stucture <strong><%= structureName %></strong> sur RES'in, le réseau des acteurs de l'inclusion numérique de la +Métropole de Lyon. Vous pouvez dès maintenant valider la demande en +<a + href="<%= config.protocol %>://<%= config.host %><%= config.port ? ':' + config.port : '' %>/join?id=<%= id %>&userId=<%= userId %>&status=true" + >cliquant ici</a +> +ou refuser la demande +<a + href="<%= config.protocol %>://<%= config.host %><%= config.port ? ':' + config.port : '' %>/join?id=<%= id %>&userId=<%= userId %>&status=false" + >cliquant ici</a +>. +<br /> +Cordialement, +<br /> +L'équipe RES'in +<br /> +<br /> +Ce mail est un mail automatique. Merci de ne pas y répondre. diff --git a/src/mailer/mail-templates/structureJoinRequest.json b/src/mailer/mail-templates/structureJoinRequest.json new file mode 100644 index 0000000000000000000000000000000000000000..8b756e80dda646b74494d044a1c027c48294ac34 --- /dev/null +++ b/src/mailer/mail-templates/structureJoinRequest.json @@ -0,0 +1,3 @@ +{ + "subject": "Un acteur demande a rejoindre votre structure, Réseau des Acteurs de la Médiation Numérique de la Métropole de Lyon" +} diff --git a/src/mailer/mail-templates/tempUserRegistration.ejs b/src/mailer/mail-templates/tempUserRegistration.ejs new file mode 100644 index 0000000000000000000000000000000000000000..8422b7dbefde902844d6970d16a0b19282321a92 --- /dev/null +++ b/src/mailer/mail-templates/tempUserRegistration.ejs @@ -0,0 +1,15 @@ +Bonjour<br /> +<br /> +Vous recevez ce message car vous avez été relié a la stucture <strong><%= name %></strong> sur RES'in, le réseau des +acteurs de l'inclusion numérique de la Métropole de Lyon. Vous pouvez dès maitenant vous créer un compte sur la +plateforme pour accéder a votre structure en +<a href="<%= config.protocol %>://<%= config.host %><%= config.port ? ':' + config.port : '' %>/register?id=<%= id %>" + >cliquant ici</a +>. +<br /> +Cordialement, +<br /> +L'équipe RES'in +<br /> +<br /> +Ce mail est un mail automatique. Merci de ne pas y répondre. diff --git a/src/mailer/mail-templates/tempUserRegistration.json b/src/mailer/mail-templates/tempUserRegistration.json new file mode 100644 index 0000000000000000000000000000000000000000..b344e7953f3643b216eec56a385e464f4d6a0de6 --- /dev/null +++ b/src/mailer/mail-templates/tempUserRegistration.json @@ -0,0 +1,3 @@ +{ + "subject": "Un compte a été créé pour vous sur le Réseau des Acteurs de la Médiation Numérique de la Métropole de Lyon" +} diff --git a/src/structures/services/structures.service.ts b/src/structures/services/structures.service.ts index 934ff6e620d4402e2e4f08291ee5c75566095bb2..4f9a8b7fdcacd65080b8b2157859b62adfb9c1d2 100644 --- a/src/structures/services/structures.service.ts +++ b/src/structures/services/structures.service.ts @@ -11,7 +11,9 @@ import { User } from '../../users/schemas/user.schema'; import { MailerService } from '../../mailer/mailer.service'; import { Cron, CronExpression } from '@nestjs/schedule'; import { DateTime } from 'luxon'; +import { IUser } from '../../users/interfaces/user.interface'; import * as _ from 'lodash'; + @Injectable() export class StructuresService { constructor( @@ -117,7 +119,7 @@ export class StructuresService { return this.findOne(idStructure); } - public async findOne(idParam: string): Promise<Structure> { + public async findOne(idParam: string): Promise<StructureDocument> { return await this.structureModel.findById(Types.ObjectId(idParam)).exec(); } /** @@ -288,7 +290,7 @@ export class StructuresService { } /** - * Generate activation token and send it to user by email, in order to validate + * Send an email to prevent outdated * a new account. * @param user User */ @@ -305,6 +307,34 @@ export class StructuresService { this.mailerService.send(userEmail, jsonConfig.subject, html); } + /** + * Send an email to structure owner's in order to accept or decline a join request + * @param user User + */ + public async sendStructureJoinRequest(user: IUser, structure: StructureDocument): Promise<void> { + const config = this.mailerService.config; + const ejsPath = this.mailerService.getTemplateLocation(config.templates.structureJoinRequest.ejs); + const jsonConfig = this.mailerService.loadJsonConfig(config.templates.structureJoinRequest.json); + + const html = await ejs.renderFile(ejsPath, { + config, + structureName: structure.structureName, + name: user.name, + surname: user.surname, + id: structure._id, + userId: user._id, + }); + const owners = await this.getOwners(structure._id); + owners.forEach((owner) => { + this.mailerService.send(owner.email, jsonConfig.subject, html); + }); + } + + private async getOwners(structureId: string): Promise<IUser[]> { + // Get owners of outdated structures + return this.userService.getStructureOwners(structureId); + } + public async updateAccountVerified(idStructure: string, emailUser: string): Promise<Structure> { const user = await this.userService.findOne(emailUser); const structureLinked = await this.findOne(user.structuresLink[0].toHexString()); diff --git a/src/structures/structures.controller.ts b/src/structures/structures.controller.ts index 428154b072e576d954680a236fc6040498354a2a..5b1dde3eeed6c27796b31bedf03c57d0671325ec 100644 --- a/src/structures/structures.controller.ts +++ b/src/structures/structures.controller.ts @@ -1,7 +1,23 @@ -import { Body, Controller, Delete, Get, Param, ParseIntPipe, Post, Put, Query, UseGuards } from '@nestjs/common'; +import { + Body, + Controller, + Delete, + Get, + HttpException, + HttpStatus, + Param, + ParseIntPipe, + Post, + Put, + Query, + UseGuards, +} from '@nestjs/common'; import { ApiParam } from '@nestjs/swagger'; import { Types } from 'mongoose'; import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; +import { CreateTempUserDto } from '../temp-user/dto/create-temp-user.dto'; +import { TempUser } from '../temp-user/temp-user.schema'; +import { TempUserService } from '../temp-user/temp-user.service'; import { Roles } from '../users/decorators/roles.decorator'; import { IsStructureOwnerGuard } from '../users/guards/isStructureOwner.guard'; import { RolesGuard } from '../users/guards/roles.guard'; @@ -15,7 +31,11 @@ import { StructuresService } from './services/structures.service'; @Controller('structures') export class StructuresController { - constructor(private readonly structureService: StructuresService, private readonly userService: UsersService) {} + constructor( + private readonly structureService: StructuresService, + private readonly userService: UsersService, + private readonly tempUserService: TempUserService + ) {} @Post() public async create(@Body() createStructureDto: CreateStructureDto): Promise<Structure> { @@ -54,7 +74,7 @@ export class StructuresController { @Post(':id/claim') public async claim(@Param('id') idStructure: string, @Body() user: User): Promise<Types.ObjectId[]> { - return this.userService.updateStructureLinked(user.email, idStructure); + return this.userService.updateStructureLinkedClaim(user.email, idStructure); } @Get('count') @@ -100,4 +120,104 @@ export class StructuresController { public async delete(@Param('id') id: string) { return this.structureService.deleteOne(id); } + + @Post(':id/addOwner') + @UseGuards(JwtAuthGuard, IsStructureOwnerGuard) + @Roles('admin') + @ApiParam({ name: 'id', type: String, required: true }) + public async addOwner(@Param('id') id: string, @Body() user: CreateTempUserDto): Promise<any> { + // Get structure name + const structure = await this.structureService.findOne(id); + if (!structure) { + throw new HttpException('Invalid Structure', HttpStatus.NOT_FOUND); + } + user.pendingStructuresLink = [Types.ObjectId(id)]; + // If user already exist, use created account + if (await this.userService.verifyUserExist(user.email)) { + return this.userService.updateStructureLinked(user.email, id); + } + // If temp user exist, update it + if (await this.tempUserService.findOne(user.email)) { + return this.tempUserService.updateStructureLinked(user); + } + // If not, create + return this.tempUserService.create(user, structure.structureName); + } + + @Post(':id/join') + @ApiParam({ name: 'id', type: String, required: true }) + public async join(@Param('id') id: string, @Body() user: User): Promise<void> { + // Get structure name + const structure = await this.structureService.findOne(id); + if (!structure) { + throw new HttpException('Invalid Structure', HttpStatus.NOT_FOUND); + } + // Get user and add pending structure + const userFromDb = await this.userService.findOne(user.email); + if (!userFromDb) { + throw new HttpException('Invalid User', HttpStatus.NOT_FOUND); + } + // If user has not already request it, send owner's validation email + if (!userFromDb.pendingStructuresLink.includes(Types.ObjectId(id))) { + userFromDb.pendingStructuresLink.push(Types.ObjectId(id)); + userFromDb.save(); + // Send structure owner's an email + this.structureService.sendStructureJoinRequest(userFromDb, structure); + } + } + + @Post(':id/join/:userId/:status') + @UseGuards(JwtAuthGuard, IsStructureOwnerGuard) + @ApiParam({ name: 'id', type: String, required: true }) + @ApiParam({ name: 'userId', type: String, required: true }) + @ApiParam({ name: 'status', type: String, required: true }) + public async joinValidation( + @Param('id') id: string, + @Param('status') status: string, + @Param('userId') userId: string + ): Promise<any> { + // Get structure name + const structure = await this.structureService.findOne(id); + if (!structure) { + throw new HttpException('Invalid Structure', HttpStatus.NOT_FOUND); + } + + // Get user and add pending structure + const userFromDb = await this.userService.findById(userId); + if (!userFromDb) { + throw new HttpException('Invalid User', HttpStatus.NOT_FOUND); + } + + if (!userFromDb.pendingStructuresLink.includes(Types.ObjectId(id))) { + throw new HttpException('User not linked to structure', HttpStatus.NOT_FOUND); + } + + if (status === 'true') { + // Accept + await this.userService.updateStructureLinked(userFromDb.email, id); + await this.userService.removeFromPendingStructureLinked(userFromDb.email, id); + } else { + // Refuse + this.userService.removeFromPendingStructureLinked(userFromDb.email, id); + } + } + + @Delete(':id/owner/:userId') + @UseGuards(JwtAuthGuard, IsStructureOwnerGuard) + @Roles('admin') + @ApiParam({ name: 'id', type: String, required: true }) + @ApiParam({ name: 'userId', type: String, required: true }) + public async removeOwner(@Param('id') id: string, @Param('userId') userId: string): Promise<void> { + // Get structure + const structure = await this.structureService.findOne(id); + if (!structure) { + throw new HttpException('Invalid Structure', HttpStatus.NOT_FOUND); + } + // Get user + const userFromDb = await this.userService.findById(userId); + if (!userFromDb || !userFromDb.structuresLink.includes(Types.ObjectId(id))) { + throw new HttpException('Invalid User', HttpStatus.NOT_FOUND); + } + this.userService.removeFromStructureLinked(userFromDb.email, id); + } } diff --git a/src/structures/structures.module.ts b/src/structures/structures.module.ts index 6494ea3a5eed4386ed251679c6e5b45a66e88804..0ec48d708808d0a1a3fdf495a037c99ba14bb9cf 100644 --- a/src/structures/structures.module.ts +++ b/src/structures/structures.module.ts @@ -1,5 +1,6 @@ import { HttpModule, Module } from '@nestjs/common'; import { MongooseModule } from '@nestjs/mongoose'; +import { TempUserModule } from '../temp-user/temp-user.module'; import { MailerModule } from '../mailer/mailer.module'; import { UsersModule } from '../users/users.module'; import { Structure, StructureSchema } from './schemas/structure.schema'; @@ -19,6 +20,7 @@ import { StructureType, StructureTypeSchema } from './structure-type/structure-t HttpModule, MailerModule, UsersModule, + TempUserModule, ], controllers: [StructuresController, StructureTypeController], exports: [StructuresService, StructureTypeService], diff --git a/src/temp-user/dto/create-temp-user.dto.ts b/src/temp-user/dto/create-temp-user.dto.ts new file mode 100644 index 0000000000000000000000000000000000000000..892eb6fca9e723f5b3be341d7e3c4cc90cc009de --- /dev/null +++ b/src/temp-user/dto/create-temp-user.dto.ts @@ -0,0 +1,22 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsArray, IsEmail, IsNotEmpty, IsOptional } from 'class-validator'; +import { Types } from 'mongoose'; + +export class CreateTempUserDto { + @IsNotEmpty() + @IsEmail() + @ApiProperty({ type: String }) + email: string; + + @IsNotEmpty() + @ApiProperty({ type: String }) + name: string; + + @IsNotEmpty() + @ApiProperty({ type: String }) + surname: string; + + @IsArray() + @IsOptional() + pendingStructuresLink?: Types.ObjectId[]; +} diff --git a/src/temp-user/dto/temp-user-delete.dto.ts b/src/temp-user/dto/temp-user-delete.dto.ts new file mode 100644 index 0000000000000000000000000000000000000000..ac6149d3d67d378dc0fd6641652a636148f1782c --- /dev/null +++ b/src/temp-user/dto/temp-user-delete.dto.ts @@ -0,0 +1,9 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsEmail, IsNotEmpty } from 'class-validator'; + +export class DeleteTempUserDto { + @IsNotEmpty() + @IsEmail() + @ApiProperty({ type: String }) + email: string; +} diff --git a/src/temp-user/temp-user.controller.ts b/src/temp-user/temp-user.controller.ts new file mode 100644 index 0000000000000000000000000000000000000000..9dc21a559e179a4ba1fc59f6c70ff7b6e9a7dc01 --- /dev/null +++ b/src/temp-user/temp-user.controller.ts @@ -0,0 +1,19 @@ +import { Controller, Get, HttpException, HttpStatus, Param } from '@nestjs/common'; +import { ApiParam } from '@nestjs/swagger'; +import { TempUser } from './temp-user.schema'; +import { TempUserService } from './temp-user.service'; + +@Controller('temp-user') +export class TempUserController { + constructor(private readonly tempUserSercice: TempUserService) {} + + @Get(':id') + @ApiParam({ name: 'id', type: String, required: true }) + public async getTempUser(@Param('id') id: string): Promise<TempUser> { + const user = await this.tempUserSercice.findById(id); + if (!user) { + throw new HttpException('User does not exists', HttpStatus.BAD_REQUEST); + } + return user; + } +} diff --git a/src/temp-user/temp-user.interface.ts b/src/temp-user/temp-user.interface.ts new file mode 100644 index 0000000000000000000000000000000000000000..b802cf3dd6c66a907bf923f9eaa678c15e7e9d81 --- /dev/null +++ b/src/temp-user/temp-user.interface.ts @@ -0,0 +1,9 @@ +import { Document, Types } from 'mongoose'; + +export interface ITempUser extends Document { + readonly _id: string; + email: string; + name: string; + surname: string; + pendingStructuresLink: Types.ObjectId[]; +} diff --git a/src/temp-user/temp-user.module.ts b/src/temp-user/temp-user.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..9b55281e49839559323398587a13a487cb462ea6 --- /dev/null +++ b/src/temp-user/temp-user.module.ts @@ -0,0 +1,14 @@ +import { HttpModule, Module } from '@nestjs/common'; +import { MongooseModule } from '@nestjs/mongoose'; +import { TempUser, TempUserSchema } from './temp-user.schema'; +import { TempUserService } from './temp-user.service'; +import { TempUserController } from './temp-user.controller'; +import { MailerModule } from '../mailer/mailer.module'; + +@Module({ + imports: [MongooseModule.forFeature([{ name: TempUser.name, schema: TempUserSchema }]), HttpModule, MailerModule], + providers: [TempUserService], + exports: [TempUserService], + controllers: [TempUserController], +}) +export class TempUserModule {} diff --git a/src/temp-user/temp-user.schema.ts b/src/temp-user/temp-user.schema.ts new file mode 100644 index 0000000000000000000000000000000000000000..c8777e3ef81fba888fc2185c754bcdf8d9a435b2 --- /dev/null +++ b/src/temp-user/temp-user.schema.ts @@ -0,0 +1,21 @@ +import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; +import { Document, Types } from 'mongoose'; + +export type TempUserDocument = TempUser & Document; + +@Schema({ timestamps: { createdAt: 'createdAt', updatedAt: 'updatedAt' } }) +export class TempUser { + @Prop({ required: true }) + email: string; + + @Prop({ required: true }) + name: string; + + @Prop({ required: true }) + surname: string; + + @Prop({ default: null }) + pendingStructuresLink: Types.ObjectId[]; +} + +export const TempUserSchema = SchemaFactory.createForClass(TempUser); diff --git a/src/temp-user/temp-user.service.ts b/src/temp-user/temp-user.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..8d3eba3993df83ff996aa597ea74637907975623 --- /dev/null +++ b/src/temp-user/temp-user.service.ts @@ -0,0 +1,87 @@ +import { HttpException, HttpService, HttpStatus, Injectable } from '@nestjs/common'; +import { InjectModel } from '@nestjs/mongoose'; +import { Model, Types } from 'mongoose'; +import { MailerService } from '../mailer/mailer.service'; +import { CreateTempUserDto } from './dto/create-temp-user.dto'; +import { TempUser, TempUserDocument } from './temp-user.schema'; +import * as ejs from 'ejs'; +import { ITempUser } from './temp-user.interface'; + +@Injectable() +export class TempUserService { + constructor( + private readonly httpService: HttpService, + private readonly mailerService: MailerService, + @InjectModel(TempUser.name) private tempUserModel: Model<ITempUser> + ) {} + + public async create(createTempUser: CreateTempUserDto, structureName: string): Promise<TempUser> { + const userInDb = await this.findOne(createTempUser.email); + if (userInDb) { + throw new HttpException('User already exists', HttpStatus.BAD_REQUEST); + } + const createUser = new this.tempUserModel(createTempUser); + // Send email + this.sendUserMail(createUser, structureName); + createUser.save(); + return await this.findOne(createTempUser.email); + } + + public async findOne(mail: string): Promise<TempUser | undefined> { + return this.tempUserModel.findOne({ email: mail }).exec(); + } + + public async findById(id: string): Promise<TempUser | undefined> { + return this.tempUserModel.findById(Types.ObjectId(id)).exec(); + } + + public async delete(mail: string): Promise<TempUser> { + const userInDb = await this.findOne(mail); + if (!userInDb) { + throw new HttpException('User already exists', HttpStatus.BAD_REQUEST); + } + this.tempUserModel.deleteOne({ email: mail }).exec(); + return userInDb; + } + + public async updateStructureLinked(createTempUser: CreateTempUserDto): Promise<TempUser> { + const userInDb = await this.tempUserModel + .find({ + $and: [ + { + email: createTempUser.email, + }, + { + pendingStructuresLink: { $in: [createTempUser.pendingStructuresLink[0]] }, + }, + ], + }) + .exec(); + if (userInDb.length > 0) { + throw new HttpException('User already linked', HttpStatus.UNPROCESSABLE_ENTITY); + } + return this.tempUserModel + .updateOne( + { email: createTempUser.email }, + { $push: { pendingStructuresLink: createTempUser.pendingStructuresLink[0] } } + ) + .exec(); + } + + /** + * Send email in order to tell the user that an account is alreday fill with his structure info. + * @param user User + */ + private async sendUserMail(user: ITempUser, structureName: string): Promise<any> { + const config = this.mailerService.config; + const ejsPath = this.mailerService.getTemplateLocation(config.templates.tempUserRegistration.ejs); + const jsonConfig = this.mailerService.loadJsonConfig(config.templates.tempUserRegistration.json); + + const html = await ejs.renderFile(ejsPath, { + config, + id: user._id, + name: structureName, + }); + this.mailerService.send(user.email, jsonConfig.subject, html); + } +} diff --git a/src/users/users.controller.ts b/src/users/users.controller.ts index a3d450e51a4c4fdc4bbb6bce6e46234165bf65a8..a16fca1915a89d913e885dd7772e321040f75ff9 100644 --- a/src/users/users.controller.ts +++ b/src/users/users.controller.ts @@ -7,10 +7,11 @@ 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'; +import { TempUserService } from '../temp-user/temp-user.service'; @Controller('users') export class UsersController { - constructor(private usersService: UsersService) {} + constructor(private usersService: UsersService, private tempUserService: TempUserService) {} @UseGuards(JwtAuthGuard) @ApiBearerAuth('JWT') @@ -33,7 +34,12 @@ export class UsersController { } const user = await this.usersService.create(createUserDto); if (structureId) { - this.usersService.updateStructureLinked(createUserDto.email, structureId); + this.usersService.updateStructureLinkedClaim(createUserDto.email, structureId); + } + // Remove temp user if exist + const tempUser = await this.tempUserService.findOne(createUserDto.email); + if (tempUser) { + this.tempUserService.delete(createUserDto.email); } return user; } diff --git a/src/users/users.module.ts b/src/users/users.module.ts index ae73bfe01263691656b98892941287ea85642e23..2741bbd41a798f060a82c203249b9064dabbaa9b 100644 --- a/src/users/users.module.ts +++ b/src/users/users.module.ts @@ -1,12 +1,18 @@ -import { Module } from '@nestjs/common'; +import { HttpModule, Module } from '@nestjs/common'; import { MongooseModule } from '@nestjs/mongoose'; import { UsersService } from './users.service'; import { UsersController } from './users.controller'; import { User, UserSchema } from './schemas/user.schema'; import { MailerModule } from '../mailer/mailer.module'; +import { TempUserModule } from '../temp-user/temp-user.module'; @Module({ - imports: [MongooseModule.forFeature([{ name: User.name, schema: UserSchema }]), MailerModule], + imports: [ + MongooseModule.forFeature([{ name: User.name, schema: UserSchema }]), + MailerModule, + HttpModule, + TempUserModule, + ], providers: [UsersService], exports: [UsersService], controllers: [UsersController], diff --git a/src/users/users.service.ts b/src/users/users.service.ts index 6cb1ca3c38118ced008f183126491bf9af615e93..7aa2e1c552ed0fae2879dfe0fce22ae53ec879be 100644 --- a/src/users/users.service.ts +++ b/src/users/users.service.ts @@ -312,6 +312,10 @@ export class UsersService { return this.userModel.findOne({ structuresLink: Types.ObjectId(structureId) }).exec(); } + public getStructureOwners(structureId: string): Promise<IUser[]> { + return this.userModel.find({ structuresLink: Types.ObjectId(structureId) }).exec(); + } + public async isUserAlreadyClaimedStructure(structureId: string, userEmail: string): Promise<boolean> { const user = await this.findOne(userEmail, true); if (user) { @@ -320,13 +324,18 @@ export class UsersService { return false; } - public async updateStructureLinked(userEmail: string, idStructure: string): Promise<Types.ObjectId[]> { + public async updateStructureLinkedClaim(userEmail: string, idStructure: string): Promise<Types.ObjectId[]> { + const stucturesLinked = this.updatePendingStructureLinked(userEmail, idStructure); + this.sendAdminStructureValidationMail(); + return stucturesLinked; + } + + public async updatePendingStructureLinked(userEmail: string, idStructure: string): Promise<Types.ObjectId[]> { const user = await this.findOne(userEmail, true); if (user) { if (!user.pendingStructuresLink.includes(Types.ObjectId(idStructure))) { user.pendingStructuresLink.push(Types.ObjectId(idStructure)); - user.save(); - this.sendAdminStructureValidationMail(); + await user.save(); return user.pendingStructuresLink; } throw new HttpException('User already claimed this structure', HttpStatus.NOT_FOUND); @@ -334,6 +343,49 @@ export class UsersService { throw new HttpException('Invalid user', HttpStatus.NOT_FOUND); } + public async removeFromPendingStructureLinked(userEmail: string, idStructure: string): Promise<Types.ObjectId[]> { + const user = await this.findOne(userEmail, true); + if (user) { + if (user.pendingStructuresLink.includes(Types.ObjectId(idStructure))) { + user.pendingStructuresLink = user.pendingStructuresLink.filter((structureId) => { + return structureId === Types.ObjectId(idStructure); + }); + await user.save(); + return user.pendingStructuresLink; + } + throw new HttpException('User already belong to this structure', HttpStatus.NOT_FOUND); + } + throw new HttpException('Invalid user', HttpStatus.NOT_FOUND); + } + + public async updateStructureLinked(userEmail: string, idStructure: string): Promise<Types.ObjectId[]> { + const user = await this.findOne(userEmail, true); + if (user) { + if (!user.structuresLink.includes(Types.ObjectId(idStructure))) { + user.structuresLink.push(Types.ObjectId(idStructure)); + await user.save(); + return user.structuresLink; + } + throw new HttpException('User already belong to this structure', HttpStatus.NOT_FOUND); + } + throw new HttpException('Invalid user', HttpStatus.NOT_FOUND); + } + + public async removeFromStructureLinked(userEmail: string, idStructure: string): Promise<Types.ObjectId[]> { + const user = await this.findOne(userEmail, true); + if (user) { + if (user.structuresLink.includes(Types.ObjectId(idStructure))) { + user.structuresLink = user.structuresLink.filter((structureId) => { + return structureId == Types.ObjectId(idStructure); + }); + await user.save(); + return user.structuresLink; + } + throw new HttpException('User already belong to this structure', HttpStatus.NOT_FOUND); + } + throw new HttpException('Invalid user', HttpStatus.NOT_FOUND); + } + /** * Return all pending attachments of all profiles */