From 6b6df1fb16be381383b007a54a335f1d0db2c8a6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Marl=C3=A8ne=20SIMONDANT?= <msimondant@grandlyon.com>
Date: Mon, 24 Oct 2022 12:53:34 +0000
Subject: [PATCH] feat : add unattachedSince date to users without any
 structure

---
 scripts/init-db.js                            |  1 +
 src/admin/admin.controller.ts                 |  1 +
 .../1666104879255-add-unattachedSince.ts      | 29 +++++++++++++++++++
 src/structures/services/structures.service.ts |  7 +++--
 src/structures/structures.controller.spec.ts  |  2 ++
 src/structures/structures.controller.ts       |  2 +-
 src/users/dto/create-user.dto.ts              |  3 ++
 src/users/schemas/user.schema.ts              |  3 ++
 src/users/services/users.service.spec.ts      |  3 ++
 src/users/services/users.service.ts           | 16 +++++-----
 10 files changed, 55 insertions(+), 12 deletions(-)
 create mode 100644 src/migrations/scripts/1666104879255-add-unattachedSince.ts

diff --git a/scripts/init-db.js b/scripts/init-db.js
index 2041dc256..910179178 100644
--- a/scripts/init-db.js
+++ b/scripts/init-db.js
@@ -58,6 +58,7 @@ const usersSchema = mongoose.Schema({
   structuresLink: [],
   structureOutdatedMailSent: [],
   pendingStructuresLink: [],
+  unattachedSince: Date,
   password: String,
   phone: String,
   employer: String,
diff --git a/src/admin/admin.controller.ts b/src/admin/admin.controller.ts
index f803d1f6b..0369680c3 100644
--- a/src/admin/admin.controller.ts
+++ b/src/admin/admin.controller.ts
@@ -219,6 +219,7 @@ export class AdminController {
         return {
           id: user._id,
           createdAt: user.createdAt ? user.createdAt.toLocaleDateString() : '',
+          unattachedSince: user.unattachedSince ? user.unattachedSince.toLocaleDateString() : '',
           surname: user.surname,
           name: user.name,
           email: user.email,
diff --git a/src/migrations/scripts/1666104879255-add-unattachedSince.ts b/src/migrations/scripts/1666104879255-add-unattachedSince.ts
new file mode 100644
index 000000000..5d75bf607
--- /dev/null
+++ b/src/migrations/scripts/1666104879255-add-unattachedSince.ts
@@ -0,0 +1,29 @@
+import { Db } from 'mongodb';
+import { getDb } from '../migrations-utils/db';
+import { DateTime } from 'luxon';
+
+export const up = async () => {
+  const db: Db = await getDb();
+  const cursor = db.collection('users').find({});
+  let document;
+  while ((document = await cursor.next())) {
+    let value: Date;
+    if (!document.structuresLink || document.structuresLink.length == 0) {
+      value = DateTime.local();
+    } else {
+      value = null;
+    }
+    await db.collection('users').updateOne({ _id: document._id }, [{ $set: { unattachedSince: value } }]);
+  }
+  console.log(`Update done : unattachedSince added to users`);
+};
+
+export const down = async () => {
+  const db: Db = await getDb();
+  const cursor = db.collection('users').find({});
+  let document;
+  while ((document = await cursor.next())) {
+    await db.collection('users').updateOne({ _id: document._id }, [{ $unset: 'unattachedSince' }]);
+  }
+  console.log(`Downgrade done : unattachedSince removed from users`);
+};
diff --git a/src/structures/services/structures.service.ts b/src/structures/services/structures.service.ts
index 3407a3d5f..f638e0edf 100644
--- a/src/structures/services/structures.service.ts
+++ b/src/structures/services/structures.service.ts
@@ -583,8 +583,11 @@ export class StructuresService {
     structure.structureType = null;
     structure.deletedAt = DateTime.local().setZone('Europe/Paris').toString();
     this.anonymizeStructure(structure).save();
-    // Remove structure from userModel
-    this.userService.removeStructureIdFromUsers(structure._id);
+    // Remove structure from owners (and check if there is a newly unattached user)
+    const owners = await this.getOwners(structure._id);
+    owners.forEach((owner) => {
+      this.userService.removeFromStructureLinked(owner, id);
+    });
     this.sendAdminStructureNotification(
       structure,
       this.mailerService.config.templates.structureDeletionNotification.ejs,
diff --git a/src/structures/structures.controller.spec.ts b/src/structures/structures.controller.spec.ts
index 2e77f0918..8338823da 100644
--- a/src/structures/structures.controller.spec.ts
+++ b/src/structures/structures.controller.spec.ts
@@ -151,6 +151,7 @@ describe('AuthController', () => {
         validated: true,
         hasPersonalOffer: false,
       },
+      unattachedSince: null,
     });
     expect(res).toBeTruthy();
   });
@@ -229,6 +230,7 @@ describe('AuthController', () => {
         validated: true,
         hasPersonalOffer: false,
       },
+      unattachedSince: null,
     });
     expect(res).toBeTruthy();
     res = controller.join('', null);
diff --git a/src/structures/structures.controller.ts b/src/structures/structures.controller.ts
index d62bf74ab..73ddd47df 100644
--- a/src/structures/structures.controller.ts
+++ b/src/structures/structures.controller.ts
@@ -221,7 +221,7 @@ export class StructuresController {
     if (!userFromDb || !userFromDb.structuresLink.includes(Types.ObjectId(id))) {
       throw new HttpException('Invalid User', HttpStatus.NOT_FOUND);
     }
-    this.userService.removeFromStructureLinked(userFromDb.email, id);
+    this.userService.removeFromStructureLinked(userFromDb, id);
   }
 
   @Delete(':id/tempUser/:userId')
diff --git a/src/users/dto/create-user.dto.ts b/src/users/dto/create-user.dto.ts
index b0bbda7ad..f304639c2 100644
--- a/src/users/dto/create-user.dto.ts
+++ b/src/users/dto/create-user.dto.ts
@@ -33,6 +33,9 @@ export class CreateUserDto {
   @IsOptional()
   structuresLink?: Array<string>;
 
+  @IsOptional()
+  unattachedSince?: Date;
+
   @IsArray()
   @IsOptional()
   personalOffers?: PersonalOffer[];
diff --git a/src/users/schemas/user.schema.ts b/src/users/schemas/user.schema.ts
index 3c1b9da92..08c9213fa 100644
--- a/src/users/schemas/user.schema.ts
+++ b/src/users/schemas/user.schema.ts
@@ -52,6 +52,9 @@ export class User {
   @Prop({ default: null })
   structureOutdatedMailSent: Types.ObjectId[];
 
+  @Prop({ default: null })
+  unattachedSince: Date;
+
   @Prop({ type: [{ type: Types.ObjectId, ref: 'PersonalOffer' }] })
   personalOffers: PersonalOfferDocument[];
 
diff --git a/src/users/services/users.service.spec.ts b/src/users/services/users.service.spec.ts
index 49d9bb9d4..9fbaf7c55 100644
--- a/src/users/services/users.service.spec.ts
+++ b/src/users/services/users.service.spec.ts
@@ -108,6 +108,7 @@ const mockUser: User = {
     validated: true,
     hasPersonalOffer: false,
   },
+  unattachedSince: null,
 };
 
 describe('UsersService', () => {
@@ -311,6 +312,7 @@ describe('UsersService', () => {
           validated: true,
           hasPersonalOffer: false,
         },
+        unattachedSince: null,
       };
       const emailDto: EmailChangeDto = { newEmail: 'test.dupont@mail.com', oldEmail: 'jacques.dupont@mii.com' }; //NOSONAR
       jest.spyOn(service, 'changeUserEmail').mockImplementation(async (): Promise<User> => result);
@@ -355,6 +357,7 @@ describe('UsersService', () => {
           validated: true,
           hasPersonalOffer: false,
         },
+        unattachedSince: null,
       };
       const token =
         '9bb3542bdc5ca8801ad4cee00403c1052bc95dee768dcbb65b1f719870578ed79f71f52fdc3e6bf02fd200a72b8b6f56fc26950df30c8cd7e427a485f80181b9'; //NOSONAR
diff --git a/src/users/services/users.service.ts b/src/users/services/users.service.ts
index edfc5eda6..d2d5b80b5 100644
--- a/src/users/services/users.service.ts
+++ b/src/users/services/users.service.ts
@@ -20,6 +20,7 @@ import { UpdateDetailsDto } from '../dto/update-details.dto';
 import { DescriptionDto } from '../dto/description.dto';
 import { IUserRegistry } from '../interfaces/userRegistry.interface';
 import { UserRegistrySearchService } from './userRegistry-search.service';
+import { DateTime } from 'luxon';
 
 @Injectable()
 export class UsersService {
@@ -55,7 +56,7 @@ export class UsersService {
       });
     }
     createUser.password = await this.hashPassword(createUser.password);
-
+    createUser.unattachedSince = DateTime.local();
     // Send verification email
     createUser = await this.verifyUserMail(createUser);
     await createUser.save();
@@ -150,12 +151,6 @@ export class UsersService {
     return this.userModel.findById(id).populate('employer').populate('job').select('-password').exec();
   }
 
-  public async removeStructureIdFromUsers(structureId: Types.ObjectId): Promise<IUser[] | undefined> {
-    return this.userModel
-      .updateMany({ structuresLink: { $in: [structureId] } }, { $pull: { structuresLink: structureId } })
-      .exec();
-  }
-
   public async replaceEmployers(
     sourceEmployer: EmployerDocument,
     targetEmployer: EmployerDocument
@@ -543,6 +538,7 @@ export class UsersService {
     if (user) {
       if (!user.structuresLink.includes(Types.ObjectId(idStructure))) {
         user.structuresLink.push(Types.ObjectId(idStructure));
+        user.unattachedSince = null;
         await user.save();
         return user.structuresLink;
       }
@@ -551,8 +547,7 @@ export class UsersService {
     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);
+  public async removeFromStructureLinked(user: IUser, idStructure: string): Promise<Types.ObjectId[]> {
     if (!user) {
       throw new HttpException('Invalid user', HttpStatus.NOT_FOUND);
     }
@@ -562,6 +557,9 @@ export class UsersService {
     user.structuresLink = user.structuresLink.filter((structureId) => {
       return !structureId.equals(idStructure);
     });
+    if (user.structuresLink.length == 0) {
+      user.unattachedSince = DateTime.local();
+    }
     await user.save();
     return user.structuresLink;
   }
-- 
GitLab