Skip to content
Snippets Groups Projects
Commit 95608291 authored by Hugo SUBTIL's avatar Hugo SUBTIL
Browse files

feat: add temp-user handling for structure join

parent fd217907
No related branches found
No related tags found
3 merge requests!38Recette,!37Dev,!34Feat/add user to structure
Showing
with 282 additions and 8 deletions
...@@ -11,6 +11,7 @@ import { MailerModule } from './mailer/mailer.module'; ...@@ -11,6 +11,7 @@ import { MailerModule } from './mailer/mailer.module';
import { TclModule } from './tcl/tcl.module'; import { TclModule } from './tcl/tcl.module';
import { AdminModule } from './admin/admin.module'; import { AdminModule } from './admin/admin.module';
import { PostsModule } from './posts/posts.module'; import { PostsModule } from './posts/posts.module';
import { TempUserModule } from './temp-user/temp-user.module';
@Module({ @Module({
imports: [ imports: [
ConfigurationModule, ConfigurationModule,
...@@ -26,6 +27,7 @@ import { PostsModule } from './posts/posts.module'; ...@@ -26,6 +27,7 @@ import { PostsModule } from './posts/posts.module';
TclModule, TclModule,
AdminModule, AdminModule,
PostsModule, PostsModule,
TempUserModule,
], ],
controllers: [AppController], controllers: [AppController],
}) })
......
...@@ -37,5 +37,9 @@ export const config = { ...@@ -37,5 +37,9 @@ export const config = {
ejs: 'apticStructureDuplication.ejs', ejs: 'apticStructureDuplication.ejs',
json: 'apticStructureDuplication.json', json: 'apticStructureDuplication.json',
}, },
tempUserRegistration: {
ejs: 'tempUserRegistration.ejs',
json: 'tempUserRegistration.json',
},
}, },
}; };
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.
{
"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"
}
...@@ -2,6 +2,9 @@ import { Body, Controller, Delete, Get, Param, ParseIntPipe, Post, Put, Query, U ...@@ -2,6 +2,9 @@ import { Body, Controller, Delete, Get, Param, ParseIntPipe, Post, Put, Query, U
import { ApiParam } from '@nestjs/swagger'; import { ApiParam } from '@nestjs/swagger';
import { Types } from 'mongoose'; import { Types } from 'mongoose';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; 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 { Roles } from '../users/decorators/roles.decorator';
import { IsStructureOwnerGuard } from '../users/guards/isStructureOwner.guard'; import { IsStructureOwnerGuard } from '../users/guards/isStructureOwner.guard';
import { RolesGuard } from '../users/guards/roles.guard'; import { RolesGuard } from '../users/guards/roles.guard';
...@@ -15,7 +18,11 @@ import { StructuresService } from './services/structures.service'; ...@@ -15,7 +18,11 @@ import { StructuresService } from './services/structures.service';
@Controller('structures') @Controller('structures')
export class StructuresController { 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() @Post()
public async create(@Body() createStructureDto: CreateStructureDto): Promise<Structure> { public async create(@Body() createStructureDto: CreateStructureDto): Promise<Structure> {
...@@ -54,7 +61,7 @@ export class StructuresController { ...@@ -54,7 +61,7 @@ export class StructuresController {
@Post(':id/claim') @Post(':id/claim')
public async claim(@Param('id') idStructure: string, @Body() user: User): Promise<Types.ObjectId[]> { 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') @Get('count')
...@@ -95,4 +102,32 @@ export class StructuresController { ...@@ -95,4 +102,32 @@ export class StructuresController {
public async delete(@Param('id') id: string) { public async delete(@Param('id') id: string) {
return this.structureService.deleteOne(id); 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) {
// Get structure name
const structure = await this.structureService.findOne(id);
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);
}
@Delete(':id/removeOwner')
@UseGuards(JwtAuthGuard, IsStructureOwnerGuard)
@Roles('admin')
@ApiParam({ name: 'id', type: String, required: true })
public async removeOwner(@Param('id') id: string, @Body() user: User) {
//TODO: remove stucture from user structure list
}
} }
import { HttpModule, Module } from '@nestjs/common'; import { HttpModule, Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose'; import { MongooseModule } from '@nestjs/mongoose';
import { TempUserModule } from '../temp-user/temp-user.module';
import { MailerModule } from '../mailer/mailer.module'; import { MailerModule } from '../mailer/mailer.module';
import { UsersModule } from '../users/users.module'; import { UsersModule } from '../users/users.module';
import { Structure, StructureSchema } from './schemas/structure.schema'; import { Structure, StructureSchema } from './schemas/structure.schema';
...@@ -19,6 +20,7 @@ import { StructureType, StructureTypeSchema } from './structure-type/structure-t ...@@ -19,6 +20,7 @@ import { StructureType, StructureTypeSchema } from './structure-type/structure-t
HttpModule, HttpModule,
MailerModule, MailerModule,
UsersModule, UsersModule,
TempUserModule,
], ],
controllers: [StructuresController, StructureTypeController], controllers: [StructuresController, StructureTypeController],
exports: [StructuresService, StructureTypeService], exports: [StructuresService, StructureTypeService],
......
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[];
}
import { ApiProperty } from '@nestjs/swagger';
import { IsEmail, IsNotEmpty } from 'class-validator';
export class DeleteTempUserDto {
@IsNotEmpty()
@IsEmail()
@ApiProperty({ type: String })
email: string;
}
import { Controller, Get, HttpException, HttpStatus, Param } from '@nestjs/common';
import { ApiParam } from '@nestjs/swagger';
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) {
const user = await this.tempUserSercice.findById(id);
if (!user) {
throw new HttpException('User does not exists', HttpStatus.BAD_REQUEST);
}
return user;
}
}
import { Document, Types } from 'mongoose';
export interface ITempUser extends Document {
readonly _id: string;
email: string;
name: string;
surname: string;
pendingStructuresLink: Types.ObjectId[];
}
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 {}
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);
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);
}
}
...@@ -7,10 +7,11 @@ import { CreateUserDto } from './dto/create-user.dto'; ...@@ -7,10 +7,11 @@ import { CreateUserDto } from './dto/create-user.dto';
import { PasswordResetApplyDto } from './dto/reset-password-apply.dto'; import { PasswordResetApplyDto } from './dto/reset-password-apply.dto';
import { PasswordResetDto } from './dto/reset-password.dto'; import { PasswordResetDto } from './dto/reset-password.dto';
import { UsersService } from './users.service'; import { UsersService } from './users.service';
import { TempUserService } from '../temp-user/temp-user.service';
@Controller('users') @Controller('users')
export class UsersController { export class UsersController {
constructor(private usersService: UsersService) {} constructor(private usersService: UsersService, private tempUserService: TempUserService) {}
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@ApiBearerAuth('JWT') @ApiBearerAuth('JWT')
...@@ -33,7 +34,12 @@ export class UsersController { ...@@ -33,7 +34,12 @@ export class UsersController {
} }
const user = await this.usersService.create(createUserDto); const user = await this.usersService.create(createUserDto);
if (structureId) { 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; return user;
} }
......
import { Module } from '@nestjs/common'; import { HttpModule, Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose'; import { MongooseModule } from '@nestjs/mongoose';
import { UsersService } from './users.service'; import { UsersService } from './users.service';
import { UsersController } from './users.controller'; import { UsersController } from './users.controller';
import { User, UserSchema } from './schemas/user.schema'; import { User, UserSchema } from './schemas/user.schema';
import { MailerModule } from '../mailer/mailer.module'; import { MailerModule } from '../mailer/mailer.module';
import { TempUserModule } from '../temp-user/temp-user.module';
@Module({ @Module({
imports: [MongooseModule.forFeature([{ name: User.name, schema: UserSchema }]), MailerModule], imports: [
MongooseModule.forFeature([{ name: User.name, schema: UserSchema }]),
MailerModule,
HttpModule,
TempUserModule,
],
providers: [UsersService], providers: [UsersService],
exports: [UsersService], exports: [UsersService],
controllers: [UsersController], controllers: [UsersController],
......
...@@ -320,13 +320,18 @@ export class UsersService { ...@@ -320,13 +320,18 @@ export class UsersService {
return false; 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); const user = await this.findOne(userEmail, true);
if (user) { if (user) {
if (!user.pendingStructuresLink.includes(Types.ObjectId(idStructure))) { if (!user.pendingStructuresLink.includes(Types.ObjectId(idStructure))) {
user.pendingStructuresLink.push(Types.ObjectId(idStructure)); user.pendingStructuresLink.push(Types.ObjectId(idStructure));
user.save(); user.save();
this.sendAdminStructureValidationMail();
return user.pendingStructuresLink; return user.pendingStructuresLink;
} }
throw new HttpException('User already claimed this structure', HttpStatus.NOT_FOUND); throw new HttpException('User already claimed this structure', HttpStatus.NOT_FOUND);
...@@ -334,6 +339,22 @@ export class UsersService { ...@@ -334,6 +339,22 @@ export class UsersService {
throw new HttpException('Invalid user', 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.pendingStructuresLink.includes(Types.ObjectId(idStructure)) &&
!user.structuresLink.includes(Types.ObjectId(idStructure))
) {
user.structuresLink.push(Types.ObjectId(idStructure));
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 * Return all pending attachments of all profiles
*/ */
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment