From 6b0763f2778ce267136a305cb3faa1ad9e46a64b Mon Sep 17 00:00:00 2001 From: Matthieu Haineault Date: Mon, 1 Dec 2025 14:44:25 -0500 Subject: [PATCH] refactor(module_access): changed the dto to use string instead of boolean for module_access and made a mapper to switch between boolean and string. --- src/common/mappers/module-access.mapper.ts | 43 +++++++++++++++++++ .../pipes/module-accessvalidation.pipe.ts | 29 ------------- .../controllers/employees.controller.ts | 10 ++--- .../employees/dtos/employee-detailed.dto.ts | 5 +-- .../employees/services/employees.service.ts | 35 +++++++-------- .../services/abstract-user.service.ts | 12 +----- 6 files changed, 67 insertions(+), 67 deletions(-) create mode 100644 src/common/mappers/module-access.mapper.ts delete mode 100644 src/common/pipes/module-accessvalidation.pipe.ts diff --git a/src/common/mappers/module-access.mapper.ts b/src/common/mappers/module-access.mapper.ts new file mode 100644 index 0000000..b6a666a --- /dev/null +++ b/src/common/mappers/module-access.mapper.ts @@ -0,0 +1,43 @@ +type Modules = + | 'timesheets' + | 'timesheets_approval' + | 'employee_list' + | 'employee_management' + | 'personal_profile' + | 'dashboard'; + +export const module_list = [ + 'timesheets', + 'timesheets_approval', + 'employee_list', + 'employee_management', + 'personal_profile', + 'dashboard', +] as const; + +const createDefaultModuleAccess = (): Record => + module_list.reduce((acc, mod) => { + acc[mod] = false; + return acc; + }, {} as Record); + + +export const toBooleanFromString = (arr?: readonly string[] | null): Record => { + const result = createDefaultModuleAccess(); + if (!arr || !Array.isArray(arr)) return result; + for (const item of arr) { + if (typeof item !== 'string') continue; + const trimmed = item.trim(); + if ((module_list as readonly string[]).includes(trimmed)) { + result[trimmed as Modules] = true; + } + } + return result; +} + +export const toStringFromBoolean = (map: Record): Record => { + return module_list.reduce((acc, mod) => { + acc[mod] = map[mod] ? mod : null; + return acc; + }, {} as Record); +} \ No newline at end of file diff --git a/src/common/pipes/module-accessvalidation.pipe.ts b/src/common/pipes/module-accessvalidation.pipe.ts deleted file mode 100644 index 47333bf..0000000 --- a/src/common/pipes/module-accessvalidation.pipe.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { PipeTransform } from "@nestjs/common"; -import { PrismaService } from "src/prisma/prisma.service"; - -export class ModuleAccessValidationPipe implements PipeTransform { - constructor(private readonly prisma: PrismaService) { } - - async transform(value: any) { - const { email, access } = value ?? {}; - const user = await this.prisma.users.findUnique({ - where: { email }, - select: { - user_module_access: { - select: { - dashboard: true, - employee_list: true, - employee_management: true, - personal_profile: true, - timesheets: true, - timesheets_approval: true, - }, - }, - }, - }); - - if(!Boolean(access)) { - - } - } -} \ No newline at end of file diff --git a/src/identity-and-account/employees/controllers/employees.controller.ts b/src/identity-and-account/employees/controllers/employees.controller.ts index 4c46665..2e2af4c 100644 --- a/src/identity-and-account/employees/controllers/employees.controller.ts +++ b/src/identity-and-account/employees/controllers/employees.controller.ts @@ -1,15 +1,13 @@ import { Controller, Get, Query, Body, Post } from "@nestjs/common"; import { Access } from "src/common/decorators/module-access.decorators"; -import { RolesAllowed } from "src/common/decorators/roles.decorators"; import { Result } from "src/common/errors/result-error.factory"; -import { GLOBAL_CONTROLLER_ROLES } from "src/common/shared/role-groupes"; import { EmployeeDetailedDto } from "src/identity-and-account/employees/dtos/employee-detailed.dto"; import { EmployeeDto } from "src/identity-and-account/employees/dtos/employee.dto"; import { EmployeesService } from "src/identity-and-account/employees/services/employees.service"; import { AccessGetService } from "src/identity-and-account/user-module-access/services/module-access-get.service"; + //TODO: create a custom decorator to replace the findModuleAcces call function -@RolesAllowed(...GLOBAL_CONTROLLER_ROLES) @Controller('employees') export class EmployeesController { constructor( @@ -18,7 +16,9 @@ export class EmployeesController { ) { } @Get('profile') - async findProfile(@Access('email') email: string, @Query('employee_email') employee_email?: string, + async findProfile( + @Access('email')email: string, + @Query('employee_email') employee_email?: string, ): Promise, string>> { const granted_access = await this.accessGetService.findModuleAccess(email); if (!granted_access.success) return { success: false, error: 'INVALID_USER' }; @@ -27,7 +27,7 @@ export class EmployeesController { } else if (granted_access.data.employee_management) { return await this.employeesService.findOneDetailedProfile(employee_email ?? email); } else { - return { success: false, error: 'INVALID_USER'} + return { success: false, error: 'INVALID_USER' } } } diff --git a/src/identity-and-account/employees/dtos/employee-detailed.dto.ts b/src/identity-and-account/employees/dtos/employee-detailed.dto.ts index 8e87d09..54cec6c 100644 --- a/src/identity-and-account/employees/dtos/employee-detailed.dto.ts +++ b/src/identity-and-account/employees/dtos/employee-detailed.dto.ts @@ -1,5 +1,4 @@ -import { IsBoolean, IsDateString, IsEmail, IsInt, IsNotEmpty, IsOptional, IsPositive, IsString } from 'class-validator'; -import { ModuleAccess } from 'src/identity-and-account/user-module-access/dtos/module-acces.dto'; +import { IsArray, IsBoolean, IsDateString, IsEmail, IsInt, IsNotEmpty, IsOptional, IsPositive, IsString } from 'class-validator'; import { Type } from 'class-transformer'; export class EmployeeDetailedDto { @@ -16,5 +15,5 @@ export class EmployeeDetailedDto { @IsDateString() @IsOptional() last_work_day?: string; @IsString() @IsOptional() residence?: string; @IsInt() @IsPositive() @Type(() => Number) external_payroll_id: number; - @Type(() => ModuleAccess) module_access: ModuleAccess; + @IsString() @IsArray() user_module_access: string[]; } diff --git a/src/identity-and-account/employees/services/employees.service.ts b/src/identity-and-account/employees/services/employees.service.ts index 7cb0795..7db5c86 100644 --- a/src/identity-and-account/employees/services/employees.service.ts +++ b/src/identity-and-account/employees/services/employees.service.ts @@ -2,6 +2,7 @@ import { Injectable } from "@nestjs/common"; import { Users } from "@prisma/client"; import { Result } from "src/common/errors/result-error.factory"; import { EmailToIdResolver } from "src/common/mappers/email-id.mapper"; +import { module_list, toBooleanFromString, toStringFromBoolean } from "src/common/mappers/module-access.mapper"; import { toStringFromDate } from "src/common/utils/date-utils"; import { EmployeeDetailedDto } from "src/identity-and-account/employees/dtos/employee-detailed.dto"; import { EmployeeDto } from "src/identity-and-account/employees/dtos/employee.dto"; @@ -50,10 +51,7 @@ export class EmployeesService { supervisor_full_name: `${r.supervisor?.user.first_name} ${r.supervisor?.user.last_name}`, })), ) - return { - success: true, - data: employee_list, - } + return { success: true, data: employee_list } } async findOwnProfile(email: string): Promise, string>> { @@ -157,11 +155,16 @@ export class EmployeesService { }); if (!employee) return { success: false, error: `EMPLOYEE_NOT_FOUND` }; if (!employee.user) return { success: false, error: 'USER_NOT_FOUND' }; + if (!employee.user.user_module_access) return { success: false, error: 'UNAUTHORIZED_ACCESS' }; let company_name = 'Solucom'; if (employee.company_code === 271583) { company_name = 'Targo'; } + const stringfy_module_access = toStringFromBoolean(employee.user.user_module_access); + const user_module_access_array = module_list + .map(mod => stringfy_module_access[mod]) + .filter((value): value is string => value !== null && value !== undefined && value !== ''); return { success: true, @@ -178,15 +181,8 @@ export class EmployeesService { employee_full_name: `${employee.user.first_name} ${employee.user.last_name}`, first_work_day: toStringFromDate(employee.first_work_day), last_work_day: employee.last_work_day ? toStringFromDate(employee.last_work_day) : undefined, - supervisor_full_name: `${employee.supervisor?.user.first_name}, ${employee.supervisor?.user.last_name}`, - module_access: { - dashboard: employee.user.user_module_access?.dashboard ?? false, - employee_list: employee.user.user_module_access?.employee_list ?? false, - employee_management: employee.user.user_module_access?.employee_management ?? false, - personal_profile: employee.user.user_module_access?.personal_profile ?? false, - timesheets: employee.user.user_module_access?.timesheets ?? false, - timesheets_approval: employee.user.user_module_access?.timesheets_approval ?? false, - }, + supervisor_full_name: employee.supervisor ? `${employee.supervisor?.user.first_name}, ${employee.supervisor?.user.last_name}` : '', + user_module_access: user_module_access_array }, }; } @@ -196,6 +192,7 @@ export class EmployeesService { if (dto.company_name === 'Targo') { company_code = 271583; } + const normalized_access = toBooleanFromString(dto.user_module_access) await this.prisma.$transaction(async (tx) => { const user: Users = await tx.users.create({ @@ -207,12 +204,12 @@ export class EmployeesService { residence: dto.residence, user_module_access: { create: { - dashboard: dto.module_access.dashboard, - employee_list: dto.module_access.employee_list, - employee_management: dto.module_access.employee_management, - personal_profile: dto.module_access.personal_profile, - timesheets: dto.module_access.timesheets, - timesheets_approval: dto.module_access.timesheets_approval, + dashboard: normalized_access.dashboard, + employee_list: normalized_access.employee_list, + employee_management: normalized_access.employee_management, + personal_profile: normalized_access.personal_profile, + timesheets: normalized_access.timesheets, + timesheets_approval: normalized_access.timesheets_approval, }, }, }, diff --git a/src/identity-and-account/users-management/services/abstract-user.service.ts b/src/identity-and-account/users-management/services/abstract-user.service.ts index 4664dcf..14092ce 100644 --- a/src/identity-and-account/users-management/services/abstract-user.service.ts +++ b/src/identity-and-account/users-management/services/abstract-user.service.ts @@ -26,22 +26,12 @@ export abstract class AbstractUserService { if (!user) { throw new NotFoundException(`No user with email #${email} exists`); } - - const user_module_access = user.user_module_access ?? { - dashboard: false, - employee_list: false, - employee_management: false, - personal_profile: false, - timesheets: false, - timesheets_approval: false, - }; - const clean_user = { first_name: user.first_name, last_name: user.last_name, email: user.email, role: user.role, - user_module_access, + user_module_access: user.user_module_access, } return clean_user;