From 8f1bc01010e3575a5fd53027be7bb9e680e8fc7c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20BRISON?=
 <ext.sopra.jbrison@grandlyon.com>
Date: Mon, 11 Jan 2021 12:04:29 +0100
Subject: [PATCH] feat: claim structure

---
 src/structures/structures.controller.ts | 18 +++++++++++++++---
 src/structures/structures.service.ts    | 22 ++++++++++++++++------
 src/users/create-user.dto.ts            |  6 +++++-
 src/users/user.schema.ts                |  1 +
 src/users/users.controller.ts           | 12 +++++++++++-
 src/users/users.service.spec.ts         | 16 ++++++++++++++++
 src/users/users.service.ts              | 15 +++++++++++++++
 7 files changed, 79 insertions(+), 11 deletions(-)

diff --git a/src/structures/structures.controller.ts b/src/structures/structures.controller.ts
index 5178d6376..eaa572291 100644
--- a/src/structures/structures.controller.ts
+++ b/src/structures/structures.controller.ts
@@ -1,4 +1,6 @@
-import { Body, Controller, Get, Param, Post, Query } from '@nestjs/common';
+import { Body, Controller, Get, Param, ParseIntPipe, Post, Put, Query } from '@nestjs/common';
+import { User } from '../users/user.schema';
+import { UsersService } from '../users/users.service';
 import { CreateStructureDto } from './dto/create-structure.dto';
 import { QueryStructure } from './dto/query-structure.dto';
 import { structureDto } from './dto/structure.dto';
@@ -7,7 +9,7 @@ import { StructuresService } from './structures.service';
 
 @Controller('structures')
 export class StructuresController {
-  constructor(private readonly structureService: StructuresService) {}
+  constructor(private readonly structureService: StructuresService, private readonly userService: UsersService) {}
 
   @Post()
   public async create(@Body() createStructureDto: CreateStructureDto): Promise<Structure> {
@@ -19,7 +21,7 @@ export class StructuresController {
     return this.structureService.search(query.query, body ? body.filters : null);
   }
 
-  @Post(':id')
+  @Put(':id')
   public async update(@Param('id') id: number, @Body() body: structureDto) {
     return this.structureService.update(id, body);
   }
@@ -29,6 +31,16 @@ export class StructuresController {
     return this.structureService.findAll();
   }
 
+  @Get(':id/isClaimed')
+  public async isClaimed(@Param('id') id: number): Promise<boolean> {
+    return this.structureService.isClaimed(id);
+  }
+
+  @Post(':id/claim')
+  public async claim(@Param('id', new ParseIntPipe()) idStructure: number, @Body() user: User) {
+    return this.userService.updateStructureLinked(user.email, idStructure);
+  }
+
   @Get('count')
   public async countCategories(): Promise<Array<{ id: string; count: number }>> {
     const data = await Promise.all([
diff --git a/src/structures/structures.service.ts b/src/structures/structures.service.ts
index 3de8c1002..85d241a5f 100644
--- a/src/structures/structures.service.ts
+++ b/src/structures/structures.service.ts
@@ -12,16 +12,16 @@ import { UsersService } from '../users/users.service';
 export class StructuresService {
   constructor(
     private readonly httpService: HttpService,
-    private readonly usersService: UsersService,
+    private readonly userService: UsersService,
     @InjectModel(Structure.name) private structureModel: Model<StructureDocument>
   ) {}
 
   public async create(idUser: string, structureDto: structureDto): Promise<Structure> {
-    const user = await this.usersService.findOne(idUser);
+    const user = await this.userService.findOne(idUser);
     if (!user) {
       throw new HttpException('Invalid profile', HttpStatus.NOT_FOUND);
     }
-    let createdStructure = new this.structureModel(structureDto);
+    const createdStructure = new this.structureModel(structureDto);
     createdStructure.id = (await this.getNumberStructures()) + 1;
     createdStructure.save();
     user.structuresLink.push(createdStructure.id);
@@ -87,11 +87,11 @@ export class StructuresService {
   }
 
   public async update(idStructure: number, structure: structureDto): Promise<Structure> {
-    const result = await this.structureModel.update({ id: idStructure }, structure);
+    const result = await this.structureModel.updateOne({ id: idStructure }, structure); //NOSONAR
     if (!result) {
       throw new HttpException('Invalid structure id', HttpStatus.BAD_REQUEST);
     }
-    return structure;
+    return this.structureModel.findOne({ id: idStructure }).exec();
   }
 
   public findOne(idParam: number): Promise<Structure> {
@@ -116,6 +116,16 @@ 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;
+      }
+    });
+    return false;
+  }
+
   /**
    * Count every value occurence of a given key
    * @param key structure key
@@ -141,6 +151,6 @@ export class StructuresService {
   }
 
   public async getNumberStructures(): Promise<number> {
-    return await this.structureModel.countDocuments();
+    return await this.structureModel.countDocuments(); //NOSONAR
   }
 }
diff --git a/src/users/create-user.dto.ts b/src/users/create-user.dto.ts
index 477d79a2a..acb92be7f 100644
--- a/src/users/create-user.dto.ts
+++ b/src/users/create-user.dto.ts
@@ -1,5 +1,5 @@
 import { ApiProperty } from '@nestjs/swagger';
-import { IsEmail, IsNotEmpty, IsString } from 'class-validator';
+import { IsArray, IsEmail, IsNotEmpty, IsOptional, IsString } from 'class-validator';
 
 export class CreateUserDto {
   @ApiProperty({ type: String })
@@ -11,4 +11,8 @@ export class CreateUserDto {
   @IsEmail()
   @ApiProperty({ type: String })
   email: string;
+
+  @IsArray()
+  @IsOptional()
+  structuresLink?: Array<number>;
 }
diff --git a/src/users/user.schema.ts b/src/users/user.schema.ts
index 0c04dba82..490f7b424 100644
--- a/src/users/user.schema.ts
+++ b/src/users/user.schema.ts
@@ -25,6 +25,7 @@ export class User {
 
   @Prop({ default: null })
   newEmail: string;
+
   @Prop({ default: null })
   structuresLink: number[];
 }
diff --git a/src/users/users.controller.ts b/src/users/users.controller.ts
index e37378c59..1585ae1a3 100644
--- a/src/users/users.controller.ts
+++ b/src/users/users.controller.ts
@@ -24,7 +24,17 @@ export class UsersController {
   @Post()
   @ApiResponse({ status: 201, description: 'User created' })
   public async create(@Body() createUserDto: CreateUserDto) {
-    return this.usersService.create(createUserDto);
+    // remove structureId for creation and add structure after
+    let structureId = null;
+    if (createUserDto.structuresLink.length > 0) {
+      structureId = createUserDto.structuresLink[0];
+      delete createUserDto.structuresLink;
+    }
+    const user = await this.usersService.create(createUserDto);
+    if (structureId) {
+      this.usersService.updateStructureLinked(createUserDto.email, structureId);
+    }
+    return user;
   }
 
   @Post('verify/:id')
diff --git a/src/users/users.service.spec.ts b/src/users/users.service.spec.ts
index c8e1e0c15..e797c49a4 100644
--- a/src/users/users.service.spec.ts
+++ b/src/users/users.service.spec.ts
@@ -42,6 +42,7 @@ describe('UsersService', () => {
         newEmail: '',
         changeEmailToken: '',
         resetPasswordToken: null,
+        structuresLink: [],
       };
       const userDto: CreateUserDto = { email: 'jacques.dupont@mii.com', password: 'test1A!!' }; //NOSONAR
       jest.spyOn(service, 'create').mockImplementation(async (): Promise<User> => result);
@@ -78,6 +79,7 @@ describe('UsersService', () => {
         newEmail: '',
         changeEmailToken: '',
         resetPasswordToken: null,
+        structuresLink: [],
       };
       const loginDto: LoginDto = { email: 'jacques.dupont@mii.com', password: 'test1A!!' }; //NOSONAR
       jest.spyOn(service, 'findByLogin').mockImplementation(async (): Promise<User> => result);
@@ -135,6 +137,7 @@ describe('UsersService', () => {
         role: 0,
         newEmail: 'test.dupont@mail.com',
         resetPasswordToken: '',
+        structuresLink: [],
         changeEmailToken:
           '9bb3542bdc5ca8801ad4cee00403c1052bc95dee768dcbb65b1f719870578ed79f71f52fdc3e6bf02fd200a72b8b6f56fc26950df30c8cd7e427a485f80181b9',
       };
@@ -164,6 +167,7 @@ describe('UsersService', () => {
         newEmail: '',
         resetPasswordToken: '',
         changeEmailToken: '',
+        structuresLink: [],
       };
       const token =
         '9bb3542bdc5ca8801ad4cee00403c1052bc95dee768dcbb65b1f719870578ed79f71f52fdc3e6bf02fd200a72b8b6f56fc26950df30c8cd7e427a485f80181b9'; //NOSONAR
@@ -228,5 +232,17 @@ describe('UsersService', () => {
         )
       ).toBe(result);
     });
+
+    it('should return structureLink tab ', async () => {
+      const result = [53];
+      jest.spyOn(service, 'updateStructureLinked').mockImplementation(async (): Promise<any> => result);
+      expect(await service.updateStructureLinked('test@mii.com', 53)).toBe(result);
+    });
+
+    it('should return invalid User ', async () => {
+      const result = new HttpException('Invalid user', HttpStatus.NOT_FOUND);
+      jest.spyOn(service, 'updateStructureLinked').mockImplementation(async (): Promise<any> => result);
+      expect(await service.updateStructureLinked('test@mii.com', 53)).toBe(result);
+    });
   });
 });
diff --git a/src/users/users.service.ts b/src/users/users.service.ts
index d05f8d5de..a14e86643 100644
--- a/src/users/users.service.ts
+++ b/src/users/users.service.ts
@@ -31,6 +31,7 @@ export class UsersService {
       );
     }
     let createUser = new this.userModel(createUserDto);
+    // createUser.email = createUserDto.email;
     createUser.password = await this.hashPassword(createUser.password);
     // Send verification email
     createUser = await this.verifyUserMail(createUser);
@@ -67,6 +68,10 @@ export class UsersService {
     return this.userModel.findOne({ email: mail }).select('-password').exec();
   }
 
+  public async findAll(): Promise<User[]> {
+    return await this.userModel.find().exec();
+  }
+
   public async findById(id: string, passwordQuery?: boolean): Promise<IUser | undefined> {
     if (passwordQuery) {
       return this.userModel.findById(id).exec();
@@ -235,4 +240,14 @@ export class UsersService {
     }
     throw new HttpException('Invalid token', HttpStatus.UNAUTHORIZED);
   }
+
+  public async updateStructureLinked(userEmail: string, idStructure: number): Promise<any> {
+    const user = await this.findOne(userEmail, true);
+    if (user) {
+      user.structuresLink.push(idStructure);
+      user.save();
+      return user.structuresLink;
+    }
+    throw new HttpException('Invalid user', HttpStatus.NOT_FOUND);
+  }
 }
-- 
GitLab