diff --git a/devops/ppd.values.yaml b/devops/ppd.values.yaml index 3300e6cf..5fe92b13 100644 --- a/devops/ppd.values.yaml +++ b/devops/ppd.values.yaml @@ -1,6 +1,6 @@ dockerPullSecret: docker-pull-secret -scwSecretKey: AgChoEnPitXp4Ny/rVMEcevaWKNVpyj2cJYAcq+yFqKwVwnLB+ffDvwqz9XBHu+6d4Nyyjkf37zUAMoaM21lEDWA7x3zfG2/D/j+rvX1qxzZgLD0mjBk7fGElVm332I6JA83oInes8AMMYEDPLElzHnpKRb9KtkIP4NzgOcCeW0ijft3N7Vroez6LEHsBPCA1I9XjKSkGEDvrO0MhWX3iJOlfz+SPMfJAV7rPawOs0ZmohTHrPW8qIvGDn8HCzKyU8zRBoMt+Ogpf5pH4U3JryEFuqD61KAQgablAM8edPIvsgNno9HAEuC2QtRLYA9aUhuKdaKuS58c9P2E80PHWXIlbpFCg6EugQTgNfnYp+3qDUNz8edeCfapYLvF4s9eCMGyMsGnpDR8EDNOyuGy7Y3l7okX8Xqu464gMp9E+hX7bHkcD6a4xfyIgJcWxsku0tm1TH1dpn4M1UXRuyZZif8P08nuE6MTUL67sAR9J1lpn4lVEL4kflk0pP2tZ5ncgPQFafJrRz05krMb0eU5tb2H4gs7ao/LL6idWo8MM9K1yr8lIuT5x2WW5CX+RjA+i50ex114V6vX3PNP5oVyt+DynTUB9QmXzVm3oLfDc3Cae1uqh7X0CFd+xiztJBtg0VtJaD/xUJcuWfY4cV2lERo9fRrykltzlJqiXHO4nowt8OtN0BcViVV8NJhPhYFzyb4ympxpOlTjm3GETuT2TYhUqdgS9nzleEAbOmOHZdIO2COunPE= +scwSecretKey: AgCgjF5QEzxT3GYTS5B6cmQ0e+0/qFWzKaUDSi+Vjc7RoameuvaIJvTXMBkS3he1oy1ulbB34v6vpZI2kxnGNqERA/U5BaYDAyfKSBwMAy4br7HVKhhuwkoF5qoG5JzJXseSmqB1U9vncVIGOZWzJc1Y4/eGlWcvLcLyfw2z/WEpyeNiWJfEhTYpJOB7gv0XnRb2U/JM3jRy1QgEUIk1WR6kgBalF+xaczPQ6uKh+PR2pqkbZa3WaKUrddmzNsgEz4d8PZMWt8IBwR2JOQEHUqCd34p/pJNyLdUgcdDhg02DKwn1oRoAxKTbAio/a7WrMbodjCb3TNWIYGal5mFmItZ7Ok/EBmUf4E85eOkTR+j8ynuuiexld3Q5Kw3o8LsHjgzVL9uP+T2rYaKkjtVt+YQRX1U8l9CrsdUEz0/wEBA0jwCWMfnh1qhD5pM/xwwjsEEAcK4rYV+Q7iAgGZZvZBCQ5aEHzrtn5D95tr1GZCV2hmrW6Seu+LKKLVBS1JmsuEsOuhudYsEK9m2RYVcxbjuS5eokKEjNrGobf2oB8rhBByavfw1JTBixR5JrI8lcYlnCa+oEhxXKJY+4Fx5SAB4YaLCMSo5vw6zsFQ3WKQzlEmCFt+EnapS+a+MGrdlwq07OHTDpvgk/1z39hopoCuhhKckGGfErLXsTYQvDOkFu+EPzgY7m7qDw/d9pSiht5tuSOkAqeOgm7tpNkUufZhaXmP+1aT7i+H5gq1JILGAmXzTI5Wc= lecoffreBack: serviceAccountName: lecoffre-back-sa diff --git a/src/app/api/franceConnect/CustomerController.ts b/src/app/api/franceConnect/CustomerController.ts index 633bed86..f4a4ed20 100644 --- a/src/app/api/franceConnect/CustomerController.ts +++ b/src/app/api/franceConnect/CustomerController.ts @@ -54,7 +54,7 @@ export default class CustomerController extends ApiController { }); //success - this.httpSuccess(response, accessToken); + this.httpSuccess(response, {accessToken}); } catch (error) { this.httpInternalError(response); return; diff --git a/src/app/api/idnot/UserController.ts b/src/app/api/idnot/UserController.ts index b63d3fec..e15b0c47 100644 --- a/src/app/api/idnot/UserController.ts +++ b/src/app/api/idnot/UserController.ts @@ -5,10 +5,13 @@ import { Service } from "typedi"; import AuthService from "@Services/common/AuthService/AuthService"; import { JwtPayload } from "jsonwebtoken"; +import IdNotService from "@Services/common/IdNotService/IdNotService"; + + @Controller() @Service() export default class UserController extends ApiController { - constructor(private authService: AuthService) { + constructor(private authService: AuthService, private idNotService: IdNotService) { super(); } @@ -19,16 +22,21 @@ export default class UserController extends ApiController { */ @Post("/api/v1/idnot/user/:code") protected async getUserInfosFromIdnot(req: Request, response: Response) { - console.warn("/api/v1/idnot/user/:code used for test, should be removed"); - try { const code = req.params["code"]; if (!code) throw new Error("code is required"); - const token = await fetch("https://qual-connexion.idnot.fr/IdPOAuth2/token/idnot_idp_v1", { method: "POST" }); - console.log(token); - //const user = await this.authService.getUserFromIdNotTokens(code!); - //success - this.httpSuccess(response); + + const idNotToken = await this.idNotService.getIdNotToken(code); + const user = await this.idNotService.getOrCreateUser(idNotToken); + + await this.idNotService.updateUser(user.uid); + await this.idNotService.updateOffice(user.office_uid); + + const payload = await this.authService.getUserJwtPayload(user.idNot); + const accessToken = this.authService.generateAccessToken(payload); + const refreshToken = this.authService.generateRefreshToken(payload); + + this.httpSuccess(response, {accessToken, refreshToken}); } catch (error) { console.log(error); this.httpInternalError(response); @@ -55,7 +63,7 @@ export default class UserController extends ApiController { } } - @Post("/api/v1/idnot/user/refresh-token") + @Post("/api/v1/idnot/user/auth/refresh-token") protected async refreshToken(req: Request, response: Response) { try { const authHeader = req.headers["authorization"]; @@ -69,6 +77,7 @@ export default class UserController extends ApiController { let accessToken; this.authService.verifyRefreshToken(token, (err, userPayload) => { if (err) { + console.log(err) this.httpUnauthorized(response); return; } @@ -80,7 +89,7 @@ export default class UserController extends ApiController { }); //success - this.httpSuccess(response, accessToken); + this.httpSuccess(response, {accessToken}); } catch (error) { this.httpInternalError(response); return; diff --git a/src/app/api/notary/UsersController.ts b/src/app/api/notary/UsersController.ts index 49807103..edb6b071 100644 --- a/src/app/api/notary/UsersController.ts +++ b/src/app/api/notary/UsersController.ts @@ -3,7 +3,7 @@ import { Controller, Get } from "@ControllerPattern/index"; import ApiController from "@Common/system/controller-pattern/ApiController"; import UsersService from "@Services/notary/UsersService/UsersService"; import { Service } from "typedi"; -import User from "le-coffre-resources/dist/Notary"; +import User from "le-coffre-resources/dist/Notary/User"; import { Prisma } from "@prisma/client"; import authHandler from "@App/middlewares/AuthHandler"; import ruleHandler from "@App/middlewares/RulesHandler"; @@ -34,7 +34,7 @@ export default class UsersController extends ApiController { //call service to get prisma entity const usersEntities = await this.usersService.get(query); - + //Hydrate ressource with prisma entity const users = User.hydrateArray(usersEntities, { strategy: "excludeAll" }); diff --git a/src/common/config/variables/Variables.ts b/src/common/config/variables/Variables.ts index a7b3cf3d..d865df6e 100644 --- a/src/common/config/variables/Variables.ts +++ b/src/common/config/variables/Variables.ts @@ -34,6 +34,12 @@ export class BackendVariables { @IsNotEmpty() public readonly APP_ROOT_URL!: string; + @IsNotEmpty() + public readonly IDNOT_BASE_URL!: string; + + @IsNotEmpty() + public readonly IDNOT_API_BASE_URL!: string; + @IsNotEmpty() public readonly IDNOT_CONNEXION_URL!: string; @@ -46,6 +52,9 @@ export class BackendVariables { @IsNotEmpty() public readonly IDNOT_REDIRECT_URL!: string; + @IsNotEmpty() + public readonly IDNOT_API_KEY!: string; + @IsNotEmpty() public readonly PINATA_API_KEY!: string; @@ -80,10 +89,13 @@ export class BackendVariables { this.APP_PORT = process.env["APP_PORT"]!; this.APP_ROOT_URL = process.env["APP_ROOT_URL"]!; this.APP_LABEL = process.env["APP_LABEL"]!; + this.IDNOT_BASE_URL = process.env["IDNOT_BASE_URL"]!; + this.IDNOT_API_BASE_URL = process.env["IDNOT_API_BASE_URL"]!; this.IDNOT_CONNEXION_URL = process.env["IDNOT_CONNEXION_URL"]!; this.IDNOT_CLIENT_ID = process.env["IDNOT_CLIENT_ID"]!; this.IDNOT_CLIENT_SECRET = process.env["IDNOT_CLIENT_SECRET"]!; this.IDNOT_REDIRECT_URL = process.env["IDNOT_REDIRECT_URL"]!; + this.IDNOT_API_KEY = process.env["IDNOT_API_KEY"]!; this.PINATA_API_KEY = process.env["PINATA_API_KEY"]!; this.PINATA_API_SECRET = process.env["PINATA_API_SECRET"]!; this.PINATA_GATEWAY = process.env["PINATA_GATEWAY"]!; diff --git a/src/common/databases/migrations/20230915113328_v26/migration.sql b/src/common/databases/migrations/20230915113328_v26/migration.sql new file mode 100644 index 00000000..bcd32f0a --- /dev/null +++ b/src/common/databases/migrations/20230915113328_v26/migration.sql @@ -0,0 +1,5 @@ +-- AlterTable +ALTER TABLE "offices" ADD COLUMN "checked_at" TIMESTAMP(3); + +-- AlterTable +ALTER TABLE "users" ADD COLUMN "checked_at" TIMESTAMP(3); diff --git a/src/common/databases/schema.prisma b/src/common/databases/schema.prisma index 858709ed..317bf91d 100644 --- a/src/common/databases/schema.prisma +++ b/src/common/databases/schema.prisma @@ -6,8 +6,8 @@ generator client { } datasource db { - provider = "postgresql" - url = env("DEV_PRISMA_STUDIO_DB_URL") + provider = "postgresql" + url = env("DEV_PRISMA_STUDIO_DB_URL") shadowDatabaseUrl = env("DEV_PRISMA_STUDIO_SHADOW_URL") } @@ -61,6 +61,7 @@ model Users { office_role_uid String? @db.VarChar(255) created_at DateTime? @default(now()) updated_at DateTime? @updatedAt + checked_at DateTime? office_membership Offices @relation(fields: [office_uid], references: [uid], onDelete: Cascade) office_uid String @db.VarChar(255) notifications Notifications[] @relation("UserHasNotifications") @@ -81,6 +82,7 @@ model Offices { office_status EOfficeStatus @default(DESACTIVATED) created_at DateTime? @default(now()) updated_at DateTime? @updatedAt + checked_at DateTime? deed_types DeedTypes[] users Users[] office_folders OfficeFolders[] diff --git a/src/common/databases/seeders/seeder.ts b/src/common/databases/seeders/seeder.ts index 5cd50974..89b9aebe 100644 --- a/src/common/databases/seeders/seeder.ts +++ b/src/common/databases/seeders/seeder.ts @@ -598,20 +598,8 @@ export default async function main() { updated_at: new Date(), }, { - name: "PUT users", - label: "Modification des utilisateurs", - created_at: new Date(), - updated_at: new Date(), - }, - { - name: "PUT offices", - label: "Modification des offices", - created_at: new Date(), - updated_at: new Date(), - }, - { - name: "PUT customers", - label: "Modification des clients", + name: "POST deeds", + label: "Création des actes", created_at: new Date(), updated_at: new Date(), }, @@ -622,38 +610,8 @@ export default async function main() { updated_at: new Date(), }, { - name: "PUT deed-types", - label: "Modification des types d'actes", - created_at: new Date(), - updated_at: new Date(), - }, - { - name: "PUT documents", - label: "Modification des documents", - created_at: new Date(), - updated_at: new Date(), - }, - { - name: "PUT document-types", - label: "Modification des types de documents", - created_at: new Date(), - updated_at: new Date(), - }, - { - name: "PUT files", - label: "Modification des fichiers", - created_at: new Date(), - updated_at: new Date(), - }, - { - name: "PUT folders", - label: "Modification des dossiers", - created_at: new Date(), - updated_at: new Date(), - }, - { - name: "PUT roles", - label: "Modification des rôles", + name: "DELETE deeds", + label: "Suppression des actes", created_at: new Date(), updated_at: new Date(), }, @@ -664,128 +622,8 @@ export default async function main() { updated_at: new Date(), }, { - name: "PUT rules", - label: "Modification des droits", - created_at: new Date(), - updated_at: new Date(), - }, - { - name: "PUT office-roles", - label: "Modification des rôles d'office", - created_at: new Date(), - updated_at: new Date(), - }, - { - name: "POST users", - label: "Création des utilisateurs", - created_at: new Date(), - updated_at: new Date(), - }, - { - name: "POST offices", - label: "Création des offices", - created_at: new Date(), - updated_at: new Date(), - }, - { - name: "POST customers", - label: "Création des clients", - created_at: new Date(), - updated_at: new Date(), - }, - { - name: "POST deeds", - label: "Création des actes", - created_at: new Date(), - updated_at: new Date(), - }, - { - name: "POST deed-types", - label: "Création des types d'actes", - created_at: new Date(), - updated_at: new Date(), - }, - { - name: "POST documents", - label: "Création des documents", - created_at: new Date(), - updated_at: new Date(), - }, - { - name: "POST document-types", - label: "Création des types de documents", - created_at: new Date(), - updated_at: new Date(), - }, - { - name: "POST files", - label: "Création des fichiers", - created_at: new Date(), - updated_at: new Date(), - }, - { - name: "POST roles", - label: "Création des rôles", - created_at: new Date(), - updated_at: new Date(), - }, - { - name: "POST rules", - label: "Création des droits", - created_at: new Date(), - updated_at: new Date(), - }, - { - name: "POST office-roles", - label: "Création des rôles d'office", - created_at: new Date(), - updated_at: new Date(), - }, - { - name: "DELETE users", - label: "Suppression des utilisateurs", - created_at: new Date(), - updated_at: new Date(), - }, - { - name: "DELETE offices", - label: "Suppression des offices", - created_at: new Date(), - updated_at: new Date(), - }, - { - name: "DELETE customers", - label: "Suppression des clients", - created_at: new Date(), - updated_at: new Date(), - }, - { - name: "DELETE deeds", - label: "Suppression des actes", - created_at: new Date(), - updated_at: new Date(), - }, - { - name: "DELETE deed-types", - label: "Suppression des types d'actes", - created_at: new Date(), - updated_at: new Date(), - }, - { - name: "DELETE documents", - label: "Suppression des documents", - created_at: new Date(), - updated_at: new Date(), - }, - { - name: "DELETE document-types", - label: "Suppression des types de documents", - created_at: new Date(), - updated_at: new Date(), - }, - { - name: "DELETE files", - label: "Suppression des fichiers", + name: "PUT folders", + label: "Modification des dossiers", created_at: new Date(), updated_at: new Date(), }, @@ -796,14 +634,86 @@ export default async function main() { updated_at: new Date(), }, { - name: "DELETE roles", - label: "Suppression des rôles", + name: "POST documents", + label: "Création des documents", created_at: new Date(), updated_at: new Date(), }, { - name: "DELETE rules", - label: "Suppression des droits", + name: "PUT documents", + label: "Modification des documents", + created_at: new Date(), + updated_at: new Date(), + }, + { + name: "DELETE documents", + label: "Suppression des documents", + created_at: new Date(), + updated_at: new Date(), + }, + { + name: "POST customers", + label: "Création des clients", + created_at: new Date(), + updated_at: new Date(), + }, + { + name: "PUT customers", + label: "Modification des clients", + created_at: new Date(), + updated_at: new Date(), + }, + { + name: "DELETE customers", + label: "Suppression des clients", + created_at: new Date(), + updated_at: new Date(), + }, + { + name: "POST deed-types", + label: "Création des types d'actes", + created_at: new Date(), + updated_at: new Date(), + }, + { + name: "PUT deed-types", + label: "Modification des types d'actes", + created_at: new Date(), + updated_at: new Date(), + }, + { + name: "DELETE deed-types", + label: "Suppression des types d'actes", + created_at: new Date(), + updated_at: new Date(), + }, + { + name: "POST document-types", + label: "Création des types de documents", + created_at: new Date(), + updated_at: new Date(), + }, + { + name: "PUT document-types", + label: "Modification des types de documents", + created_at: new Date(), + updated_at: new Date(), + }, + { + name: "DELETE document-types", + label: "Suppression des types de documents", + created_at: new Date(), + updated_at: new Date(), + }, + { + name: "POST office-roles", + label: "Création des rôles d'office", + created_at: new Date(), + updated_at: new Date(), + }, + { + name: "PUT office-roles", + label: "Modification des rôles d'office", created_at: new Date(), updated_at: new Date(), }, @@ -813,6 +723,24 @@ export default async function main() { created_at: new Date(), updated_at: new Date(), }, + { + name: "DELETE office-roles", + label: "Suppression des rôles d'office", + created_at: new Date(), + updated_at: new Date(), + }, + { + name: "PUT users", + label: "Modification des utilisateurs", + created_at: new Date(), + updated_at: new Date(), + }, + { + name: "PUT offices", + label: "Modification des offices", + created_at: new Date(), + updated_at: new Date(), + }, ]; const roles: Role[] = [ @@ -842,7 +770,7 @@ export default async function main() { label: "Utilisateur", created_at: new Date(), updated_at: new Date(), - rules: rules.slice(0, 11), + rules: rules.slice(0,1), }, ]; @@ -1378,7 +1306,7 @@ export default async function main() { city: office.address!.city, }, }, - office_status: EOfficeStatus.DESACTIVATED, + office_status: EOfficeStatus.ACTIVATED, }, }); office.uid = officeCreated.uid; diff --git a/src/common/repositories/OfficesRepository.ts b/src/common/repositories/OfficesRepository.ts index 0e0c83ed..fb763880 100644 --- a/src/common/repositories/OfficesRepository.ts +++ b/src/common/repositories/OfficesRepository.ts @@ -72,6 +72,31 @@ export default class OfficesRepository extends BaseRepository { return this.model.update(updateArgs); } + /** + * @description : Update check date of an office + */ + public async updateCheckedAt(uid: string) { + return this.model.update({ + where: { + uid: uid, + }, + data: { + checked_at: new Date(), + }, + }); + } + + /** + * @description : Delete an office + */ + public async delete(uid: string): Promise { + return this.model.delete({ + where: { + uid: uid, + }, + }); + } + /** * @description : Find one office */ @@ -93,4 +118,25 @@ export default class OfficesRepository extends BaseRepository { include: query, }); } + + /** + * @description : Find offices which need to be checked with idNot API + */ + public async findManyToCheck() { + return this.model.findMany({ + where: { + OR: [ + { + checked_at: null, + }, + { + checked_at: { lt: new Date(Date.now() - 3 * 60 * 1000) }, // less than 24h ago + }, + ], + }, + include: { + address: true, + }, + }); + } } diff --git a/src/common/repositories/UsersRepository.ts b/src/common/repositories/UsersRepository.ts index 6df40fb6..718f6066 100644 --- a/src/common/repositories/UsersRepository.ts +++ b/src/common/repositories/UsersRepository.ts @@ -87,7 +87,7 @@ export default class UsersRepository extends BaseRepository { /** * @description : Update data from a user */ - + public async update(uid: string, user: User): Promise { const updateArgs: Prisma.UsersUpdateArgs = { where: { @@ -108,7 +108,7 @@ export default class UsersRepository extends BaseRepository { }, }; - if(user.office_membership) { + if (user.office_membership) { updateArgs.data.office_membership = { connect: { idNot: user.office_membership?.idNot, @@ -124,7 +124,7 @@ export default class UsersRepository extends BaseRepository { }; } - if(user.office_role) { + if (user.office_role) { updateArgs.data.office_role = { connect: { uid: user.office_role.uid, @@ -132,7 +132,7 @@ export default class UsersRepository extends BaseRepository { }; } - if(user.role) { + if (user.role) { updateArgs.data.role = { connect: { uid: user.role.uid, @@ -144,17 +144,31 @@ export default class UsersRepository extends BaseRepository { } /** - * @description : Update role from a user + * @description : Update check date of a user */ - public async updateRole(uid: string, user: User): Promise { + public async updateCheckedAt(uid: string) { + return this.model.update({ + where: { + uid: uid, + }, + data: { + checked_at: new Date(), + }, + }); + } + + /** + * @description : Update office role from a user + */ + public async updateOfficeRole(uid: string, user: User): Promise { const updateArgs: Prisma.UsersUpdateArgs = { where: { uid: uid, }, - data: {} + data: {}, }; - if(user.office_role) { + if (user.office_role) { updateArgs.data.office_role = { connect: { uid: user.office_role.uid, @@ -162,17 +176,20 @@ export default class UsersRepository extends BaseRepository { }; } - if(user.role) { - updateArgs.data.role = { - connect: { - uid: user.role.uid, - }, - }; - } - return this.model.update({ ...updateArgs, include: { contact: true, office_membership: { include: { address: true } } } }); } + /** + * @description : Delete one user + */ + public async delete(uid: string): Promise { + return this.model.delete({ + where: { + uid: uid, + }, + }); + } + /** * @description : Find one user */ @@ -200,7 +217,7 @@ export default class UsersRepository extends BaseRepository { /** * @description : Find one user with office */ - public async findOneByUidWithRole(uid: string): Promise<((Users & {role: Role} )| null)> { + public async findOneByUidWithRole(uid: string): Promise<(Users & { role: Role }) | null> { return this.model.findUnique({ where: { uid: uid, @@ -222,4 +239,27 @@ export default class UsersRepository extends BaseRepository { }, }); } + + /** + * @description : Find users which need to be checked with idNot API + */ + public async findManyToCheck() { + return this.model.findMany({ + where: { + OR: [ + { + checked_at: null, + }, + { + checked_at: { lt: new Date(Date.now() - 3 * 60 * 1000) }, // less than 24h ago + }, + ], + }, + include: { + contact: true, + role: true, + office_membership: true, + }, + }); + } } diff --git a/src/entries/Cron.ts b/src/entries/Cron.ts index 25d0d4eb..0b97bcec 100644 --- a/src/entries/Cron.ts +++ b/src/entries/Cron.ts @@ -8,6 +8,7 @@ import CronService from "@Services/common/CronService/CronService"; try { const variables = await Container.get(BackendVariables).validate(); Container.get(CronService).archiveFiles(); + await Container.get(CronService).updateUsers(); if(variables.ENV !== "dev"){ Container.get(CronService).sendMails(); } diff --git a/src/services/admin/UsersService/UsersService.ts b/src/services/admin/UsersService/UsersService.ts index 6167352e..934e1341 100644 --- a/src/services/admin/UsersService/UsersService.ts +++ b/src/services/admin/UsersService/UsersService.ts @@ -19,20 +19,12 @@ export default class UsersService extends BaseService { return this.userRepository.findMany(query); } - /** - * @description : Create a user - * @throws {Error} If user couldn't be created - */ - public create(userEntity: User): Promise { - return this.userRepository.create(userEntity); - } - /** * @description : Modify a user * @throws {Error} If user modification failed */ public async update(uid: string, userEntity: User): Promise { - return this.userRepository.updateRole(uid, userEntity); + return this.userRepository.updateOfficeRole(uid, userEntity); } /** diff --git a/src/services/common/AuthService/AuthService.ts b/src/services/common/AuthService/AuthService.ts index 4410cd35..fe8d4ded 100644 --- a/src/services/common/AuthService/AuthService.ts +++ b/src/services/common/AuthService/AuthService.ts @@ -15,7 +15,12 @@ interface ICustomerJwtPayload { customerId: string; email: string; } - + +export interface IdNotJwtPayload { + sub: string, + profile_idn: string, + entity_idn: string, +} interface IUserJwtPayload { userId: string; @@ -79,7 +84,7 @@ export default class AuthService extends BaseService { }; } public generateAccessToken(user: any): string { - return jwt.sign({ ...user }, this.variables.ACCESS_TOKEN_SECRET, { expiresIn: "1h" }); + return jwt.sign({ ...user }, this.variables.ACCESS_TOKEN_SECRET, { expiresIn: "15m" }); } public generateRefreshToken(user: any): string { diff --git a/src/services/common/CronService/CronService.ts b/src/services/common/CronService/CronService.ts index 80e86255..d8c6cd38 100644 --- a/src/services/common/CronService/CronService.ts +++ b/src/services/common/CronService/CronService.ts @@ -2,11 +2,12 @@ import { Service } from "typedi"; import { CronJob } from "cron"; import MailchimpService from "../MailchimpService/MailchimpService"; import FilesService from "../FilesService/FilesService"; +import IdNotService from "../IdNotService/IdNotService"; @Service() export default class CronService { - constructor(private mailchimpService: MailchimpService, private filesService: FilesService) {} + constructor(private mailchimpService: MailchimpService, private filesService: FilesService, private idNotService: IdNotService) {} public async sendMails() { const cronJob = new CronJob("*/15 * * * *", async () => { // Every 15 minutes @@ -37,4 +38,18 @@ export default class CronService { cronJob.start(); } } + public async updateUsers() { + const cronJob = new CronJob("*/15 * * * *", async () => { // Every 15 minutes + try { + await this.idNotService.updateOffices(); + await this.idNotService.updateUsers(); + } catch (e) { + console.error(e); + } + }); + // Start job + if (!cronJob.running) { + cronJob.start(); + } + } } diff --git a/src/services/common/IdNotService/IdNotService.ts b/src/services/common/IdNotService/IdNotService.ts new file mode 100644 index 00000000..17c3378b --- /dev/null +++ b/src/services/common/IdNotService/IdNotService.ts @@ -0,0 +1,332 @@ +import BaseService from "@Services/BaseService"; +import { BackendVariables } from "@Common/config/variables/Variables"; +import { Service } from "typedi"; +import jwt from "jsonwebtoken"; +import UsersService from "@Services/super-admin/UsersService/UsersService"; +import { IdNotJwtPayload } from "../AuthService/AuthService"; +import User, { Office, Role } from "le-coffre-resources/dist/SuperAdmin"; +import RolesService from "@Services/super-admin/RolesService/RolesService"; +import OfficesService from "@Services/super-admin/OfficesService/OfficesService"; +import { EOfficeStatus } from "@prisma/client"; + +interface IIdNotToken { + access_token: string; + expires_in: number; + id_token: string; + token_type: string; +} + +interface IRattachementData { + entiteUrl: string; + personneUrl: string; + entite: { + codeCrpcen: string; + typeEntite: { + name: string; + }; + ou: string; + denominationSociale: string; + statutEntite: { + name: string; + }; + locationsUrl: string; + } + personne: { + numeroTelephonePro: string; + prenom: string; + nomUsuel: string; + mobilePro: string; + numeroAdherentCrpcen: string; + civilite: string; + }; + typeLien: { + name: string; + }; + numeroMobile: string; + numeroTelephone: string; + statutDuRattachement: boolean; + mailRattachement: string; +} + +interface IOfficeData { + ou: string; + intitule: string; + denominationSociale: string; + departementResidence: { + libelle: string; + code: string; + }[]; + libelle: string; + codeCrpcen: string; + locationsUrl: string; + statutEntite: { + name: string; + }; +} + +interface IOfficeLocation { + totalResultCount: number; + page: number; + size: number; + result: { + mail: string; + numeroTelephone: string; + adrGeo4: string; + adrGeoCodePostal: string; + adrGeoVille: string; + ou: string; + lastModified: string; + deleted: boolean; + }[]; +} + +enum EIdnotRole { + DIRECTEUR = "Directeur général du CSN", + NOTAIRE_TITULAIRE = "Notaire titulaire", + NOTAIRE_ASSOCIE = "Notaire associé", + NOTAIRE_SALARIE = "Notaire salarié", + COLLABORATEUR = "Collaborateur", + SECRETAIRE_GENERAL = "Secrétaire général", + SUPPLEANT = "Suppléant", + ADMINISTRATEUR = "Administrateur", + RESPONSABLE = "Responsable", +} + +@Service() +export default class IdNotService extends BaseService { + constructor( + protected variables: BackendVariables, + private userService: UsersService, + private officeService: OfficesService, + private rolesService: RolesService, + ) { + super(); + } + + public async getIdNotToken(code: string) { + const query = new URLSearchParams({ + client_id: this.variables.IDNOT_CLIENT_ID, + client_secret: this.variables.IDNOT_CLIENT_SECRET, + redirect_uri: `http://0.0.0.0:3000/authorized-client`, + code: code, + grant_type: "authorization_code", + }); + const token = await fetch(this.variables.IDNOT_BASE_URL + this.variables.IDNOT_CONNEXION_URL + "?" + query, { method: "POST" }); + const decodedToken = (await token.json()) as IIdNotToken; + const decodedIdToken = jwt.decode(decodedToken.id_token) as IdNotJwtPayload; + return decodedIdToken; + } + + public async getRole(roleName: string): Promise { + switch (roleName) { + case EIdnotRole.DIRECTEUR: + return (await this.rolesService.get({ where: { name: "admin" } }))[0]!; + case EIdnotRole.NOTAIRE_TITULAIRE: + return (await this.rolesService.get({ where: { name: "notary" } }))[0]!; + case EIdnotRole.NOTAIRE_ASSOCIE: + return (await this.rolesService.get({ where: { name: "notary" } }))[0]!; + case EIdnotRole.NOTAIRE_SALARIE: + return (await this.rolesService.get({ where: { name: "notary" } }))[0]!; + case EIdnotRole.COLLABORATEUR: + return (await this.rolesService.get({ where: { name: "collaborator" } }))[0]!; + case EIdnotRole.SECRETAIRE_GENERAL: + return (await this.rolesService.get({ where: { name: "notary" } }))[0]!; + case EIdnotRole.SUPPLEANT: + return (await this.rolesService.get({ where: { name: "notary" } }))[0]!; + case EIdnotRole.ADMINISTRATEUR: + return (await this.rolesService.get({ where: { name: "admin" } }))[0]!; + case EIdnotRole.RESPONSABLE: + return (await this.rolesService.get({ where: { name: "collaborator" } }))[0]!; + default: + return (await this.rolesService.get({ where: { name: "default" } }))[0]!; + } + } + + public getOfficeStatus(statusName: string) { + switch (statusName) { + case "Pourvu": + return EOfficeStatus.ACTIVATED; + case "Pourvu mais décédé": + return EOfficeStatus.ACTIVATED; + case "Sans titulaire": + return EOfficeStatus.ACTIVATED; + case "Vacance": + return EOfficeStatus.ACTIVATED; + case "En activité": + return EOfficeStatus.ACTIVATED; + default: + return EOfficeStatus.DESACTIVATED; + } + } + + public async updateUser(userId: string) { + const userInfos = await this.userService.getByUid(userId, { contact: true, role: true, office_membership: true }); + const user = User.hydrate(userInfos!); + const searchParams = new URLSearchParams({ + key: this.variables.IDNOT_API_KEY, + }); + const userRawData = await (await fetch( + `${this.variables.IDNOT_API_BASE_URL}/api/pp/v2/personnes/${user.idNot}/rattachements?` + + searchParams, + { + method: "GET", + }, + )).json() as any; + if (userRawData.totalResultCount === 0) { + await this.userService.updateCheckedAt(user.uid!); + //await this.userService.delete(user.uid!); + return; + } + const userData = userRawData.result[0] as IRattachementData; + const roleFromIdNot = await this.getRole(userData.typeLien.name); + let updates = 0; + + if (user.role!.uid !== roleFromIdNot.uid) { + updates++; + user.role = roleFromIdNot; + } + + if (user.office_membership!.idNot !== userData.entiteUrl.split("/")[5]!) { + updates++; + let officeData = (await this.officeService.get({ where: { idNot: userData.entiteUrl.split("/")[5]! } }))[0]; + if (!officeData) { + const officeIdNotData = (await ( + await fetch(`${this.variables.IDNOT_API_BASE_URL + userData.entiteUrl}?` + searchParams, { method: "GET" }) + ).json()) as IOfficeData; + const office = { + idNot: userData.entiteUrl.split("/")[5]!, + name: officeIdNotData.denominationSociale + ? officeIdNotData.denominationSociale + : `office ${userData.entiteUrl.split("/")[5]!}`, + crpcen: officeIdNotData.codeCrpcen, + office_status: this.getOfficeStatus(officeIdNotData.statutEntite.name), + address: { + address: officeIdNotData.departementResidence[0]!.libelle, //officeLocationData.result[0]!.adrPostale4, + city: "city", //officeLocationData.result[0]!.adrPostaleVille, + zip_code: Number(officeIdNotData.departementResidence[0]!.code), + created_at: null, + updated_at: null, + }, + created_at: null, + updated_at: null, + }; + officeData = await this.officeService.create(office); + } + user.office_membership = officeData; + } + + if (user.contact!.email !== userData.mailRattachement) { + updates++; + user.contact!.email = userData.mailRattachement; + } + if (user.contact!.cell_phone_number !== userData.numeroMobile) { + updates++; + user.contact!.cell_phone_number = userData.numeroMobile; + } + if (updates != 0) await this.userService.update(user.uid!, user); + await this.userService.updateCheckedAt(user.uid!); + } + + public async updateOffice(officeId: string) { + const officeInfos = await this.officeService.getByUid(officeId); + const office = Office.hydrate(officeInfos!); + const searchParams = new URLSearchParams({ + key: this.variables.IDNOT_API_KEY, + }); + const officeRawData = await fetch( + `${this.variables.IDNOT_API_BASE_URL}/api/pp/v2/entities/${office.idNot}?` + + searchParams, + { + method: "GET", + }, + ); + if (officeRawData.status === 404) { + await this.officeService.updateCheckedAt(office.uid!); + //await this.officeService.delete(office.uid!); + return; + } + const officeData = (await officeRawData.json()) as IOfficeData; + let updates = 0; + if(office.name !== officeData.denominationSociale) { + updates++; + office.name = officeData.denominationSociale; + } + if(office.office_status !== this.getOfficeStatus(officeData.statutEntite.name)) { + updates++; + office.office_status = this.getOfficeStatus(officeData.statutEntite.name); + } + if(updates != 0) await this.officeService.update(office.uid!, office); + await this.officeService.updateCheckedAt(office.uid!); + } + + public async getOrCreateUser(decodedToken: IdNotJwtPayload) { + let user = await this.userService.getByProvider("idNot", decodedToken.sub); + if (!user) { + const searchParams = new URLSearchParams({ + key: this.variables.IDNOT_API_KEY, + }); + + const userData = (await ( + await fetch(`${this.variables.IDNOT_API_BASE_URL}/api/pp/v2/rattachements/${decodedToken.profile_idn}?` + searchParams, { + method: "GET", + }) + ).json()) as IRattachementData; + + const officeLocationData = (await ( + await fetch(`${this.variables.IDNOT_API_BASE_URL + userData.entite.locationsUrl}?` + searchParams, { method: "GET" }) + ).json()) as IOfficeLocation; + + const role = await this.getRole(userData.typeLien.name); + + const userToAdd = { + idNot: decodedToken.sub, + office_membership: { + idNot: decodedToken.entity_idn, + name: userData.entite.denominationSociale, + crpcen: userData.entite.codeCrpcen, + office_status: this.getOfficeStatus(userData.entite.statutEntite.name), + address: { + address: officeLocationData.result[0]!.adrGeo4, + city: officeLocationData.result[0]!.adrGeoVille.split(" ")[0] ?? officeLocationData.result[0]!.adrGeoVille, //officeLocationData.result[0]!.adrPostaleVille, + zip_code: Number(officeLocationData.result[0]!.adrGeoCodePostal), + created_at: null, + updated_at: null, + }, + created_at: null, + updated_at: null, + }, + role: role, + contact: { + first_name: userData.personne.prenom, + last_name: userData.personne.nomUsuel, + email: userData.mailRattachement, + phone_number: userData.numeroTelephone, + cell_phone_number: userData.numeroMobile ?? userData.numeroTelephone, + civility: userData.personne.civilite, + created_at: null, + updated_at: null, + }, + }; + const userToHydrate = User.hydrate(userToAdd); + return await this.userService.create(userToHydrate); + } + return user; + } + + public async updateUsers() { + const usersReq = await this.userService.getUsersToBeChecked(); + const users = User.hydrateArray(usersReq); + users.forEach(async (user) => { + await this.updateUser(user.uid!); + + }); + } + + public async updateOffices() { + const officesReq = await this.officeService.getOfficesToBeChecked(); + const offices = Office.hydrateArray(officesReq); + offices.forEach(async (office) => { + await this.updateOffice(office.uid!); + }); + } +} diff --git a/src/services/super-admin/OfficesService/OfficesService.ts b/src/services/super-admin/OfficesService/OfficesService.ts index 441da71b..de1e5391 100644 --- a/src/services/super-admin/OfficesService/OfficesService.ts +++ b/src/services/super-admin/OfficesService/OfficesService.ts @@ -34,6 +34,23 @@ export default class OfficesService extends BaseService { return this.officeRepository.update(uid, officeEntity); } + /** + * @description : Modify an office check date + * @throws {Error} If user modification failed + * @deprecate + */ + public updateCheckedAt(uid: string): Promise { + return this.officeRepository.updateCheckedAt(uid); + } + + /** + * @description : delete an office + * @throws {Error} If office cannot be modified + */ + public async delete(uid: string): Promise { + return this.officeRepository.delete(uid); + } + /** * @description : Get a office by uid * @throws {Error} If office cannot be get @@ -41,4 +58,11 @@ export default class OfficesService extends BaseService { public async getByUid(uid: string, query?: any): Promise { return this.officeRepository.findOneByUid(uid, query); } + + /** + * @description : Get users to be checked with IdNot API + */ + public getOfficesToBeChecked() { + return this.officeRepository.findManyToCheck(); + } } diff --git a/src/services/super-admin/UsersService/UsersService.ts b/src/services/super-admin/UsersService/UsersService.ts index 241cb1e4..3598e945 100644 --- a/src/services/super-admin/UsersService/UsersService.ts +++ b/src/services/super-admin/UsersService/UsersService.ts @@ -35,6 +35,23 @@ export default class UsersService extends BaseService { return this.userRepository.update(uid, userEntity); } + /** + * @description : Modify a user check date + * @throws {Error} If user modification failed + * @deprecate + */ + public updateCheckedAt(uid: string): Promise { + return this.userRepository.updateCheckedAt(uid); + } + + /** + * @description : Delete a user + * @throws {Error} If user modification failed + */ + public delete(uid: string): Promise { + return this.userRepository.delete(uid); + } + /** * @description : Get a user by uid * @throws {Error} If user cannot be get by uid @@ -66,4 +83,12 @@ export default class UsersService extends BaseService { public getByProvider(providerName: string, id: string) { return this.userRepository.findOneByProvider(providerName, id); } + + /** + * @description : Get users to be checked with IdNot API + */ + public getUsersToBeChecked() { + return this.userRepository.findManyToCheck(); + } + }