diff --git a/docs/swagger/swagger-spec.json b/docs/swagger/swagger-spec.json index 640a199..52213ab 100644 --- a/docs/swagger/swagger-spec.json +++ b/docs/swagger/swagger-spec.json @@ -465,7 +465,7 @@ }, "/employees/profile": { "get": { - "operationId": "EmployeesController_findOneProfile", + "operationId": "EmployeesController_findProfile", "parameters": [ { "name": "employee_email", @@ -501,37 +501,15 @@ } }, "/employees": { - "patch": { - "operationId": "EmployeesController_updateOrArchiveOrRestore", - "parameters": [], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UpdateEmployeeDto" - } - } - } - }, - "responses": { - "200": { - "description": "" - } - }, - "tags": [ - "Employees" - ] - }, "post": { - "operationId": "EmployeesController_create", + "operationId": "EmployeesController_createEmployee", "parameters": [], "requestBody": { "required": true, "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/CreateEmployeeDto" + "$ref": "#/components/schemas/EmployeeDetailedDto" } } } @@ -639,29 +617,6 @@ "ModuleAccess" ] } - }, - "/module_access/revoke": { - "patch": { - "operationId": "ModuleAccessController_revokeModuleAccess", - "parameters": [ - { - "name": "employee_email", - "required": true, - "in": "query", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "" - } - }, - "tags": [ - "ModuleAccess" - ] - } } }, "info": { @@ -729,153 +684,9 @@ "type": "object", "properties": {} }, - "UpdateEmployeeDto": { + "EmployeeDetailedDto": { "type": "object", - "properties": { - "id": { - "type": "number", - "example": 1, - "description": "Unique ID of an employee(primary-key, auto-incremented)" - }, - "user_id": { - "type": "string", - "example": "0e6e2e1f-b157-4c7c-ae3f-999b3e4f914d", - "description": "UUID of the user linked to that employee" - }, - "first_name": { - "type": "string", - "example": "Frodo", - "description": "Employee`s first name" - }, - "last_name": { - "type": "string", - "example": "Baggins", - "description": "Employee`s last name" - }, - "email": { - "type": "string", - "example": "i_cant_do_this_sam@targointernet.com", - "description": "Employee`s email" - }, - "phone_number": { - "type": "string", - "example": "82538437464", - "description": "Employee`s phone number" - }, - "residence": { - "type": "string", - "example": "1 Bagshot Row, Hobbiton, The Shire, Middle-earth", - "description": "Employee`s residence" - }, - "external_payroll_id": { - "type": "number", - "example": 7464, - "description": "external ID for the pay system" - }, - "company_code": { - "type": "number", - "example": 335567447, - "description": "Employee`s company code" - }, - "job_title": { - "type": "string", - "example": "technicient", - "description": "employee`s job title" - }, - "first_work_day": { - "format": "date-time", - "type": "string", - "example": "23/09/3018", - "description": "New hire date or undefined" - }, - "last_work_day": { - "format": "date-time", - "type": "string", - "example": "25/03/3019", - "description": "Termination date (null to restore)" - }, - "supervisor_id": { - "type": "number", - "description": "Supervisor ID" - } - } - }, - "CreateEmployeeDto": { - "type": "object", - "properties": { - "id": { - "type": "number", - "example": 1, - "description": "Unique ID of an employee(primary-key, auto-incremented)" - }, - "user_id": { - "type": "string", - "example": "0e6e2e1f-b157-4c7c-ae3f-999b3e4f914d", - "description": "UUID of the user linked to that employee" - }, - "first_name": { - "type": "string", - "example": "Frodo", - "description": "Employee`s first name" - }, - "last_name": { - "type": "string", - "example": "Baggins", - "description": "Employee`s last name" - }, - "email": { - "type": "string", - "example": "i_cant_do_this_sam@targointernet.com", - "description": "Employee`s email" - }, - "phone_number": { - "type": "string", - "example": "82538437464", - "description": "Employee`s phone number" - }, - "residence": { - "type": "string", - "example": "1 Bagshot Row, Hobbiton, The Shire, Middle-earth", - "description": "Employee`s residence" - }, - "external_payroll_id": { - "type": "number", - "example": 7464, - "description": "external ID for the pay system" - }, - "company_code": { - "type": "number", - "example": 335567447, - "description": "Employee`s company code" - }, - "job_title": { - "type": "string", - "example": "technicient", - "description": "employee`s job title" - }, - "first_work_day": { - "type": "string", - "example": "23/09/3018", - "description": "Employee`s first working day" - }, - "last_work_day": { - "type": "string", - "example": "25/03/3019", - "description": "Employee`s last working day" - } - }, - "required": [ - "id", - "user_id", - "first_name", - "last_name", - "email", - "phone_number", - "external_payroll_id", - "company_code", - "job_title", - "first_work_day" - ] + "properties": {} }, "PreferencesDto": { "type": "object", diff --git a/prisma/migrations/20251201162936_added_autoincrements_to_payroll_id/migration.sql b/prisma/migrations/20251201162936_added_autoincrements_to_payroll_id/migration.sql new file mode 100644 index 0000000..e10fb97 --- /dev/null +++ b/prisma/migrations/20251201162936_added_autoincrements_to_payroll_id/migration.sql @@ -0,0 +1,4 @@ +-- AlterTable +CREATE SEQUENCE employees_external_payroll_id_seq; +ALTER TABLE "employees" ALTER COLUMN "external_payroll_id" SET DEFAULT nextval('employees_external_payroll_id_seq'); +ALTER SEQUENCE employees_external_payroll_id_seq OWNED BY "employees"."external_payroll_id"; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index db75d2b..3fc5fd0 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -53,7 +53,7 @@ model Employees { supervisor Employees? @relation("EmployeeSupervisor", fields: [supervisor_id], references: [id]) supervisor_id Int? - external_payroll_id Int + external_payroll_id Int @default(autoincrement()) company_code Int first_work_day DateTime @db.Date last_work_day DateTime? @db.Date diff --git a/src/common/decorators/module-access.decorators.ts b/src/common/decorators/module-access.decorators.ts new file mode 100644 index 0000000..b5cc71d --- /dev/null +++ b/src/common/decorators/module-access.decorators.ts @@ -0,0 +1,9 @@ +import { createParamDecorator, ExecutionContext } from "@nestjs/common"; + +export const Access = createParamDecorator( + (data:string, ctx: ExecutionContext) => { + const request = ctx.switchToHttp().getRequest(); + const user = request.user; + return data ? user?.[data] : user; + }, +); \ No newline at end of file diff --git a/src/common/decorators/ownership.decorator.ts b/src/common/decorators/ownership.decorator.ts deleted file mode 100644 index c93da9e..0000000 --- a/src/common/decorators/ownership.decorator.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { SetMetadata } from "@nestjs/common"; - -export const OWNER_KEY = 'ownership'; -export interface OwnershipMeta { - serviceToken: string; - idParam?: string; - ownerField?: string; -} - -export const CheckOwnership = (meta: OwnershipMeta) => - SetMetadata(OWNER_KEY, meta); \ No newline at end of file diff --git a/src/common/guards/ownership.guard.ts b/src/common/guards/ownership.guard.ts deleted file mode 100644 index 27928bc..0000000 --- a/src/common/guards/ownership.guard.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { - CanActivate, - Injectable, - ExecutionContext, - ForbiddenException, -} from "@nestjs/common"; -import { Reflector, ModuleRef } from "@nestjs/core"; -import { OWNER_KEY, OwnershipMeta } from "../decorators/ownership.decorator"; -import { Request } from 'express'; - -interface RequestWithUser extends Request { - user: { id: string, role: string }; -} - -@Injectable() -export class OwnershipGuard implements CanActivate { - constructor( - private reflector: Reflector, - private moduleRef: ModuleRef, - ) { } - - async canActivate(context: ExecutionContext): Promise { - const meta = this.reflector.get( - OWNER_KEY, context.getHandler(), - ); - if (!meta) - return true; - - const request = context.switchToHttp().getRequest(); - const user = request.user; - const resourceId = request.params[meta.idParam || 'id']; - - const service = this.moduleRef.get( - meta.serviceToken, - { strict: false }, - ); - const resource = await service.findOne(resourceId); - const ownerField = meta.ownerField || 'ownerId'; - - if (user.role === 'ADMIN') { - return true; - } - - if (!resource || resource[ownerField] !== user.id) { - throw new ForbiddenException( - `You do not own the rights to this resource.` - ); - } - return true; - } -} \ No newline at end of file diff --git a/src/common/mappers/timesheet.mapper.ts b/src/common/mappers/timesheet.mapper.ts index 7474bf9..ac1599d 100644 --- a/src/common/mappers/timesheet.mapper.ts +++ b/src/common/mappers/timesheet.mapper.ts @@ -13,19 +13,19 @@ export class EmployeeTimesheetResolver { constructor( private readonly prisma: PrismaService, private readonly emailResolver: EmailToIdResolver, - ) {} + ) { } - readonly findTimesheetIdByEmail = async (email: string, date: Date, client?: Tx): Promise> => { + readonly findTimesheetIdByEmail = async (email: string, date: Date, client?: Tx): Promise> => { const db = client ?? this.prisma; const employee_id = await this.emailResolver.findIdByEmail(email); - if(!employee_id.success) return { success: false, error: employee_id.error} + if (!employee_id.success) return { success: false, error: employee_id.error } const start_date = weekStartSunday(date); console.log('start date: ', start_date); const timesheet = await db.timesheets.findFirst({ - where: { employee_id : employee_id.data, start_date: start_date }, + where: { employee_id: employee_id.data, start_date: start_date }, select: { id: true }, }); - if(!timesheet) throw new NotFoundException(`TIMESHEET_NOT_FOUND`); - return { success: true, data: {id: timesheet.id} }; + if (!timesheet) return { success: false, error: 'TIMESHEET_NOT_FOUND' }; + return { success: true, data: { id: timesheet.id } }; } } \ No newline at end of file diff --git a/src/common/pipes/module-accessvalidation.pipe.ts b/src/common/pipes/module-accessvalidation.pipe.ts new file mode 100644 index 0000000..47333bf --- /dev/null +++ b/src/common/pipes/module-accessvalidation.pipe.ts @@ -0,0 +1,29 @@ +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 b5cb43e..4c46665 100644 --- a/src/identity-and-account/employees/controllers/employees.controller.ts +++ b/src/identity-and-account/employees/controllers/employees.controller.ts @@ -1,56 +1,65 @@ -import { Controller, Get, Patch, Param, Body, NotFoundException, Req, Post, Query } from "@nestjs/common"; -import { Employees } from "@prisma/client"; +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, MANAGER_ROLES } from "src/common/shared/role-groupes"; -import { CreateEmployeeDto } from "src/identity-and-account/employees/dtos/create-employee.dto"; -import { EmployeeListItemDto } from "src/identity-and-account/employees/dtos/list-employee.dto"; -import { EmployeeProfileItemDto } from "src/identity-and-account/employees/dtos/profil-employee.dto"; -import { UpdateEmployeeDto } from "src/identity-and-account/employees/dtos/update-employee.dto"; -import { EmployeesArchivalService } from "src/identity-and-account/employees/services/employees-archival.service"; +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( - private readonly employeesService: EmployeesService, - private readonly archiveService: EmployeesArchivalService, - ) { } + constructor( + private readonly employeesService: EmployeesService, + private readonly accessGetService: AccessGetService, + ) { } - @Get('profile') - findOneProfile( - @Req() req, - @Query('employee_email') employee_email?: string, - ): Promise> { - const email = req.user?.email; - return this.employeesService.findOneProfile(employee_email ?? email); - } + @Get('profile') + 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' }; + if (!granted_access.data.employee_management) { + return await this.employeesService.findOwnProfile(email); + } else if (granted_access.data.employee_management) { + return await this.employeesService.findOneDetailedProfile(employee_email ?? email); + } else { + return { success: false, error: 'INVALID_USER'} + } + } - @Get('employee-list') - @RolesAllowed(...MANAGER_ROLES) - findListEmployees(): Promise> { - return this.employeesService.findListEmployees(); - } + @Get('employee-list') + async findListEmployees(@Access('email') email: string + ): Promise> { + const granted_access = await this.accessGetService.findModuleAccess(email); + if (!granted_access.success) return { success: false, error: 'INVALID_USER' }; + if (!granted_access.data.employee_management) return { success: false, error: 'UNAUTHORIZED_ACCESS' }; + return this.employeesService.findListEmployees(); + } - @Patch() - @RolesAllowed(...MANAGER_ROLES) - async updateOrArchiveOrRestore(@Req() req, @Body() dto: UpdateEmployeeDto,) { - // if last_work_day is set => archive the employee - // else if employee is archived and first_work_day or last_work_day = null => restore - //otherwise => standard update - const email = req.user?.email; - const result = await this.archiveService.patchEmployee(email, dto); - if (!result) { - throw new NotFoundException(`Employee with email: ${email} is not found in active or archive.`) - } - return result; - } + @Post() + async createEmployee(@Access('email') email: string, @Body() dto: EmployeeDetailedDto + ): Promise> { + const granted_access = await this.accessGetService.findModuleAccess(email); + if (!granted_access.success) return { success: false, error: 'INVALID_USER' }; + if (!granted_access.data.employee_management) return { success: false, error: 'UNAUTHORIZED_ACCESS' }; + return await this.employeesService.createEmployee(dto); + } - @Post() - @RolesAllowed(...MANAGER_ROLES) - create(@Body() dto: CreateEmployeeDto): Promise { - return this.employeesService.create(dto); - } + // @Patch() + // async updateOrArchiveOrRestore(@Req() req, @Body() dto: UpdateEmployeeDto,) { + // // if last_work_day is set => archive the employee + // // else if employee is archived and first_work_day or last_work_day = null => restore + // //otherwise => standard update + // const email = req.user?.email; + // const result = await this.archiveService.patchEmployee(email, dto); + // if (!result) { + // throw new NotFoundException(`Employee with email: ${email} is not found in active or archive.`) + // } + // return result; + // } diff --git a/src/identity-and-account/employees/dtos/create-employee.dto.ts b/src/identity-and-account/employees/dtos/create-employee.dto.ts deleted file mode 100644 index 6b717ee..0000000 --- a/src/identity-and-account/employees/dtos/create-employee.dto.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { - Allow, - IsBoolean, - IsDateString, - IsEmail, - IsInt, - IsNotEmpty, - IsOptional, - IsPositive, - IsString, - IsUUID, -} from 'class-validator'; -import { Type } from 'class-transformer'; -import { ApiProperty } from '@nestjs/swagger'; -import { UserDto } from 'src/identity-and-account/users-management/dtos/user.dto'; - -export class CreateEmployeeDto { - @ApiProperty({ - example: 1, - description: 'Unique ID of an employee(primary-key, auto-incremented)', - }) - @Allow() - id: number; - - @ApiProperty({ - example: '0e6e2e1f-b157-4c7c-ae3f-999b3e4f914d', - description: 'UUID of the user linked to that employee', - }) - @IsUUID() - @IsOptional() - user_id?: string; - - @ApiProperty({ - example: 'Frodo', - description: 'Employee`s first name', - }) - @IsString() - @IsNotEmpty() - first_name: string; - - @ApiProperty({ - example: 'Baggins', - description: 'Employee`s last name', - }) - @IsString() - @IsNotEmpty() - last_name: string; - - @ApiProperty({ - example: 'i_cant_do_this_sam@targointernet.com', - description: 'Employee`s email', - }) - @IsEmail() - @IsOptional() - email: string; - - - @IsOptional() - @IsBoolean() - is_supervisor: boolean; - - @ApiProperty({ - example: '82538437464', - description: 'Employee`s phone number', - }) - @IsString() - phone_number: string; - - @ApiProperty({ - example: '1 Bagshot Row, Hobbiton, The Shire, Middle-earth', - description: 'Employee`s residence', - required: false, - }) - @IsString() - @IsOptional() - residence?: string; - - @ApiProperty({ - example: 7464, - description: 'external ID for the pay system', - }) - @IsInt() - @IsPositive() - @Type(() => Number) - external_payroll_id: number; - - @ApiProperty({ - example: 335567447, - description: 'Employee`s company code', - }) - @IsInt() - @IsPositive() - @Type(() => Number) - company_code: number; - - @ApiProperty({ - example:'technicient', - description: 'employee`s job title', - }) - @IsString() - @IsOptional() - job_title: string; - - @ApiProperty({ - example: '23/09/3018', - description: 'Employee`s first working day', - }) - @IsDateString() - first_work_day: string; - - @ApiProperty({ - example: '25/03/3019', - description: 'Employee`s last working day', - required: false, - }) - @IsDateString() - @IsOptional() - last_work_day?: string; - - user?: UserDto; -} diff --git a/src/identity-and-account/employees/dtos/employee-detailed.dto.ts b/src/identity-and-account/employees/dtos/employee-detailed.dto.ts new file mode 100644 index 0000000..8e87d09 --- /dev/null +++ b/src/identity-and-account/employees/dtos/employee-detailed.dto.ts @@ -0,0 +1,20 @@ +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 { Type } from 'class-transformer'; + +export class EmployeeDetailedDto { + @IsString() @IsNotEmpty() first_name: string; + @IsString() @IsNotEmpty() last_name: string; + @IsString() employee_full_name: string; + @IsString() @IsOptional() supervisor_full_name: string; + @IsOptional() @IsBoolean() is_supervisor: boolean; + @IsString() company_name: string; + @IsString() @IsOptional() job_title: string; + @IsEmail() @IsOptional() email: string; + @IsString() phone_number: string; + @IsDateString() first_work_day: string; + @IsDateString() @IsOptional() last_work_day?: string; + @IsString() @IsOptional() residence?: string; + @IsInt() @IsPositive() @Type(() => Number) external_payroll_id: number; + @Type(() => ModuleAccess) module_access: ModuleAccess; +} diff --git a/src/identity-and-account/employees/dtos/employee.dto.ts b/src/identity-and-account/employees/dtos/employee.dto.ts new file mode 100644 index 0000000..e4dc1a8 --- /dev/null +++ b/src/identity-and-account/employees/dtos/employee.dto.ts @@ -0,0 +1,12 @@ +import { Type } from "class-transformer"; +import { IsEmail, IsInt, IsOptional, IsString } from "class-validator"; + +export class EmployeeDto { + @IsString() first_name!: string; + @IsString() last_name!: string; + @IsString() @IsEmail() email!: string; + @IsString() @IsOptional() supervisor_full_name: string | ''; + @IsString() company_name: number; + @IsString() @IsOptional() job_title: string; + @IsInt() @Type(()=> Number) external_payroll_id: number; +} \ No newline at end of file diff --git a/src/identity-and-account/employees/dtos/list-employee.dto.ts b/src/identity-and-account/employees/dtos/list-employee.dto.ts deleted file mode 100644 index 39abf03..0000000 --- a/src/identity-and-account/employees/dtos/list-employee.dto.ts +++ /dev/null @@ -1,8 +0,0 @@ -export class EmployeeListItemDto { - first_name: string; - last_name: string; - email: string; - supervisor_full_name: string | null; - company_name: number | null; - job_title: string | null; -} \ No newline at end of file diff --git a/src/identity-and-account/employees/dtos/profil-employee.dto.ts b/src/identity-and-account/employees/dtos/profil-employee.dto.ts deleted file mode 100644 index c6836cf..0000000 --- a/src/identity-and-account/employees/dtos/profil-employee.dto.ts +++ /dev/null @@ -1,13 +0,0 @@ -export class EmployeeProfileItemDto { - first_name: string; - last_name: string; - employee_full_name: string; - supervisor_full_name: string | null; - company_name: number | null; - job_title: string | null; - email: string | null; - phone_number: string; - first_work_day: string; - last_work_day?: string | null; - residence: string | null; -} \ No newline at end of file diff --git a/src/identity-and-account/employees/dtos/update-employee.dto.ts b/src/identity-and-account/employees/dtos/update-employee.dto.ts deleted file mode 100644 index 334a01a..0000000 --- a/src/identity-and-account/employees/dtos/update-employee.dto.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { ApiProperty, PartialType } from '@nestjs/swagger'; -import { CreateEmployeeDto } from './create-employee.dto'; -import { IsDateString, IsOptional, Max } from 'class-validator'; - -export class UpdateEmployeeDto extends PartialType(CreateEmployeeDto) { - @ApiProperty({ required: false, type: Date, description: 'New hire date or undefined' }) - @IsDateString() - @IsOptional() - first_work_day?: string; - - @ApiProperty({ required: false, type: Date, description: 'Termination date (null to restore)' }) - @IsDateString() - @IsOptional() - last_work_day?: string; - - @ApiProperty({ required: false, type: Number, description: 'Supervisor ID' }) - @IsOptional() - supervisor_id?: number; - - @IsOptional() - phone_number: string; -} diff --git a/src/identity-and-account/employees/employees.module.ts b/src/identity-and-account/employees/employees.module.ts index ceebf40..9ad88eb 100644 --- a/src/identity-and-account/employees/employees.module.ts +++ b/src/identity-and-account/employees/employees.module.ts @@ -1,12 +1,13 @@ import { Module } from '@nestjs/common'; import { EmployeesController } from './controllers/employees.controller'; import { EmployeesService } from './services/employees.service'; -import { EmployeesArchivalService } from 'src/identity-and-account/employees/services/employees-archival.service'; +import { AccessGetService } from 'src/identity-and-account/user-module-access/services/module-access-get.service'; +import { EmailToIdResolver } from 'src/common/mappers/email-id.mapper'; @Module({ imports: [], controllers: [EmployeesController], - providers: [EmployeesService, EmployeesArchivalService], + providers: [EmployeesService, AccessGetService, EmailToIdResolver], exports: [EmployeesService ], }) export class EmployeesModule {} diff --git a/src/identity-and-account/employees/services/employees-archival.service.ts b/src/identity-and-account/employees/services/employees-archival.service.ts index 2aa184a..2d1235b 100644 --- a/src/identity-and-account/employees/services/employees-archival.service.ts +++ b/src/identity-and-account/employees/services/employees-archival.service.ts @@ -1,173 +1,173 @@ -import { Injectable } from "@nestjs/common"; -import { Employees, Users } from "@prisma/client"; -import { UpdateEmployeeDto } from "src/identity-and-account/employees/dtos/update-employee.dto"; -import { toDateOrUndefined, toDateOrNull } from "src/identity-and-account/employees/utils/employee.utils"; -import { PrismaService } from "src/prisma/prisma.service"; +// import { Injectable } from "@nestjs/common"; +// import { Employees, Users } from "@prisma/client"; +// // import { UpdateEmployeeDto } from "src/identity-and-account/employees/dtos/update-employee.dto"; +// import { toDateOrUndefined, toDateOrNull } from "src/identity-and-account/employees/utils/employee.utils"; +// import { PrismaService } from "src/prisma/prisma.service"; -@Injectable() -export class EmployeesArchivalService { - constructor(private readonly prisma: PrismaService) { } +// @Injectable() +// export class EmployeesArchivalService { +// constructor(private readonly prisma: PrismaService) { } - async patchEmployee(email: string, dto: UpdateEmployeeDto): Promise { - // 1) Tenter sur employés actifs - const active = await this.prisma.employees.findFirst({ - where: { user: { email } }, - include: { user: true }, - }); +// async patchEmployee(email: string, dto: UpdateEmployeeDto): Promise { +// // 1) Tenter sur employés actifs +// const active = await this.prisma.employees.findFirst({ +// where: { user: { email } }, +// include: { user: true }, +// }); - if (active) { - // Archivage : si on reçoit un last_work_day défini et que l'employé n’est pas déjà terminé - // if (dto.last_work_day !== undefined && active.last_work_day == null && dto.last_work_day !== null) { - // return this.archiveOnTermination(active, dto); - // } +// if (active) { +// // Archivage : si on reçoit un last_work_day défini et que l'employé n’est pas déjà terminé +// // if (dto.last_work_day !== undefined && active.last_work_day == null && dto.last_work_day !== null) { +// // return this.archiveOnTermination(active, dto); +// // } - // Sinon, update standard (split Users/Employees) - const { - first_name, - last_name, - email: new_email, - phone_number, - residence, - external_payroll_id, - company_code, - job_title, - first_work_day, - last_work_day, - supervisor_id, - is_supervisor, - } = dto as any; +// // Sinon, update standard (split Users/Employees) +// const { +// first_name, +// last_name, +// email: new_email, +// phone_number, +// residence, +// external_payroll_id, +// company_code, +// job_title, +// first_work_day, +// last_work_day, +// supervisor_id, +// is_supervisor, +// } = dto as any; - const first_work_d = toDateOrUndefined(first_work_day); - const last_work_d = Object.prototype.hasOwnProperty('last_work_day') - ? toDateOrNull(last_work_day ?? null) - : undefined; +// const first_work_d = toDateOrUndefined(first_work_day); +// const last_work_d = Object.prototype.hasOwnProperty('last_work_day') +// ? toDateOrNull(last_work_day ?? null) +// : undefined; - await this.prisma.$transaction(async (transaction) => { - if ( - first_name !== undefined || - last_name !== undefined || - new_email !== undefined || - phone_number !== undefined || - residence !== undefined - ) { - await transaction.users.update({ - where: { id: active.user_id }, - data: { - ...(first_name !== undefined ? { first_name } : {}), - ...(last_name !== undefined ? { last_name } : {}), - ...(email !== undefined ? { email: new_email } : {}), - ...(phone_number !== undefined ? { phone_number } : {}), - ...(residence !== undefined ? { residence } : {}), - }, - }); +// await this.prisma.$transaction(async (transaction) => { +// if ( +// first_name !== undefined || +// last_name !== undefined || +// new_email !== undefined || +// phone_number !== undefined || +// residence !== undefined +// ) { +// await transaction.users.update({ +// where: { id: active.user_id }, +// data: { +// ...(first_name !== undefined ? { first_name } : {}), +// ...(last_name !== undefined ? { last_name } : {}), +// ...(email !== undefined ? { email: new_email } : {}), +// ...(phone_number !== undefined ? { phone_number } : {}), +// ...(residence !== undefined ? { residence } : {}), +// }, +// }); - } +// } - const updated = await transaction.employees.update({ - where: { id: active.id }, - data: { - ...(external_payroll_id !== undefined ? { external_payroll_id } : {}), - ...(company_code !== undefined ? { company_code } : {}), - ...(job_title !== undefined ? { job_title } : {}), - ...(first_work_d !== undefined ? { first_work_day: first_work_d } : {}), - ...(last_work_d !== undefined ? { last_work_day: last_work_d } : {}), - ...(is_supervisor !== undefined ? { is_supervisor } : {}), - ...(supervisor_id !== undefined ? { supervisor_id } : {}), - }, - include: { user: true }, - }); +// const updated = await transaction.employees.update({ +// where: { id: active.id }, +// data: { +// ...(external_payroll_id !== undefined ? { external_payroll_id } : {}), +// ...(company_code !== undefined ? { company_code } : {}), +// ...(job_title !== undefined ? { job_title } : {}), +// ...(first_work_d !== undefined ? { first_work_day: first_work_d } : {}), +// ...(last_work_d !== undefined ? { last_work_day: last_work_d } : {}), +// ...(is_supervisor !== undefined ? { is_supervisor } : {}), +// ...(supervisor_id !== undefined ? { supervisor_id } : {}), +// }, +// include: { user: true }, +// }); - return updated; - }); +// return updated; +// }); - return this.prisma.employees.findFirst({ where: { user: { email } } }); - } +// return this.prisma.employees.findFirst({ where: { user: { email } } }); +// } - const user = await this.prisma.users.findUnique({ where: { email } }); - if (!user) return null; - // 2) Pas trouvé en actifs → regarder en archive (pour restauration) - // const archived = await this.prisma.employeesArchive.findFirst({ - // where: { user_id: user.id }, - // include: { user: true }, - // }); +// const user = await this.prisma.users.findUnique({ where: { email } }); +// if (!user) return null; +// // 2) Pas trouvé en actifs → regarder en archive (pour restauration) +// // const archived = await this.prisma.employeesArchive.findFirst({ +// // where: { user_id: user.id }, +// // include: { user: true }, +// // }); - // if (archived) { - // // Condition de restauration : last_work_day === null ou first_work_day fourni - // const restore = dto.last_work_day === null || dto.first_work_day != null; - // if (restore) { - // return this.restoreEmployee(archived, dto); - // } - // } - // 3) Ni actif, ni archivé → 404 dans le controller - return null; - } +// // if (archived) { +// // // Condition de restauration : last_work_day === null ou first_work_day fourni +// // const restore = dto.last_work_day === null || dto.first_work_day != null; +// // if (restore) { +// // return this.restoreEmployee(archived, dto); +// // } +// // } +// // 3) Ni actif, ni archivé → 404 dans le controller +// return null; +// } - //transfers the employee to archive and then delete from employees table - // private async archiveOnTermination(active: Employees & { user: Users }, dto: UpdateEmployeeDto): Promise { - // const last_work_d = toDateOrNull(dto.last_work_day!); - // if (!last_work_d) throw new Error('invalide last_work_day for archive'); - // return this.prisma.$transaction(async transaction => { - // //detach crew from supervisor if employee is a supervisor - // await transaction.employees.updateMany({ - // where: { supervisor_id: active.id }, - // data: { supervisor_id: null }, - // }) - // const archived = await transaction.employeesArchive.create({ - // data: { - // employee_id: active.id, - // user_id: active.user_id, - // first_name: active.user.first_name, - // last_name: active.user.last_name, - // company_code: active.company_code, - // job_title: active.job_title, - // first_work_day: active.first_work_day, - // last_work_day: last_work_d, - // supervisor_id: active.supervisor_id ?? null, - // is_supervisor: active.is_supervisor, - // external_payroll_id: active.external_payroll_id, - // }, - // include: { user: true } - // }); - // //delete from employees table - // await transaction.employees.delete({ where: { id: active.id } }); - // //return archived employee - // return archived - // }); - // } +// //transfers the employee to archive and then delete from employees table +// // private async archiveOnTermination(active: Employees & { user: Users }, dto: UpdateEmployeeDto): Promise { +// // const last_work_d = toDateOrNull(dto.last_work_day!); +// // if (!last_work_d) throw new Error('invalide last_work_day for archive'); +// // return this.prisma.$transaction(async transaction => { +// // //detach crew from supervisor if employee is a supervisor +// // await transaction.employees.updateMany({ +// // where: { supervisor_id: active.id }, +// // data: { supervisor_id: null }, +// // }) +// // const archived = await transaction.employeesArchive.create({ +// // data: { +// // employee_id: active.id, +// // user_id: active.user_id, +// // first_name: active.user.first_name, +// // last_name: active.user.last_name, +// // company_code: active.company_code, +// // job_title: active.job_title, +// // first_work_day: active.first_work_day, +// // last_work_day: last_work_d, +// // supervisor_id: active.supervisor_id ?? null, +// // is_supervisor: active.is_supervisor, +// // external_payroll_id: active.external_payroll_id, +// // }, +// // include: { user: true } +// // }); +// // //delete from employees table +// // await transaction.employees.delete({ where: { id: active.id } }); +// // //return archived employee +// // return archived +// // }); +// // } - // //transfers the employee from archive to the employees table - // private async restoreEmployee(archived: EmployeesArchive & { user: Users }, dto: UpdateEmployeeDto): Promise { - // // const first_work_d = toDateOrUndefined(dto.first_work_day); - // return this.prisma.$transaction(async transaction => { - // //restores the archived employee into the employees table - // const restored = await transaction.employees.create({ - // data: { - // user_id: archived.user_id, - // company_code: archived.company_code, - // job_title: archived.job_title, - // first_work_day: archived.first_work_day, - // last_work_day: null, - // is_supervisor: archived.is_supervisor ?? false, - // external_payroll_id: archived.external_payroll_id, - // }, - // }); - // //deleting archived entry by id - // await transaction.employeesArchive.delete({ where: { id: archived.id } }); +// // //transfers the employee from archive to the employees table +// // private async restoreEmployee(archived: EmployeesArchive & { user: Users }, dto: UpdateEmployeeDto): Promise { +// // // const first_work_d = toDateOrUndefined(dto.first_work_day); +// // return this.prisma.$transaction(async transaction => { +// // //restores the archived employee into the employees table +// // const restored = await transaction.employees.create({ +// // data: { +// // user_id: archived.user_id, +// // company_code: archived.company_code, +// // job_title: archived.job_title, +// // first_work_day: archived.first_work_day, +// // last_work_day: null, +// // is_supervisor: archived.is_supervisor ?? false, +// // external_payroll_id: archived.external_payroll_id, +// // }, +// // }); +// // //deleting archived entry by id +// // await transaction.employeesArchive.delete({ where: { id: archived.id } }); - // //return restored employee - // return restored; - // }); - // } +// // //return restored employee +// // return restored; +// // }); +// // } - // //fetches all archived employees - // async findAllArchived(): Promise { - // return this.prisma.employeesArchive.findMany(); - // } +// // //fetches all archived employees +// // async findAllArchived(): Promise { +// // return this.prisma.employeesArchive.findMany(); +// // } - // //fetches an archived employee - // async findOneArchived(id: number): Promise { - // return this.prisma.employeesArchive.findUniqueOrThrow({ where: { id } }); - // } +// // //fetches an archived employee +// // async findOneArchived(id: number): Promise { +// // return this.prisma.employeesArchive.findUniqueOrThrow({ where: { id } }); +// // } -} +// } diff --git a/src/identity-and-account/employees/services/employees.service.ts b/src/identity-and-account/employees/services/employees.service.ts index bd4d8ce..36de7a8 100644 --- a/src/identity-and-account/employees/services/employees.service.ts +++ b/src/identity-and-account/employees/services/employees.service.ts @@ -1,135 +1,225 @@ -import { Injectable, NotFoundException } from "@nestjs/common"; -import { Employees, Users } from "@prisma/client"; +import { Injectable } from "@nestjs/common"; +import { Users } from "@prisma/client"; import { Result } from "src/common/errors/result-error.factory"; -import { CreateEmployeeDto } from "src/identity-and-account/employees/dtos/create-employee.dto"; -import { EmployeeListItemDto } from "src/identity-and-account/employees/dtos/list-employee.dto"; -import { EmployeeProfileItemDto } from "src/identity-and-account/employees/dtos/profil-employee.dto"; +import { EmailToIdResolver } from "src/common/mappers/email-id.mapper"; +import { EmployeeDetailedDto } from "src/identity-and-account/employees/dtos/employee-detailed.dto"; +import { EmployeeDto } from "src/identity-and-account/employees/dtos/employee.dto"; import { PrismaService } from "src/prisma/prisma.service"; @Injectable() export class EmployeesService { - constructor(private readonly prisma: PrismaService) { } + constructor( + private readonly prisma: PrismaService, + private readonly emailResolver: EmailToIdResolver, + ) { } - async findListEmployees(): Promise> { - return {success: true, data:await this.prisma.employees.findMany({ - select: { - user: { - select: { - first_name: true, - last_name: true, - email: true, - }, - }, - supervisor: { - select: { - user: { - select: { - first_name: true, - last_name: true, - }, - }, - }, - }, - job_title: true, - company_code: true, - } - }).then(rows => rows.map(r => ({ - first_name: r.user.first_name, - last_name: r.user.last_name, - email: r.user.email, - company_name: r.company_code, - job_title: r.job_title, - employee_full_name: `${r.user.first_name} ${r.user.last_name}`, - supervisor_full_name: r.supervisor ? `${r.supervisor.user.first_name} ${r.supervisor.user.last_name}` : null, - })), - )} - } + async findListEmployees(): Promise> { + const employee_list = await this.prisma.employees.findMany({ + select: { + user: { + select: { + first_name: true, + last_name: true, + email: true, + }, + }, + supervisor: { + select: { + user: { + select: { + first_name: true, + last_name: true, + }, + }, + }, + }, + job_title: true, + company_code: true, + external_payroll_id: true, - async findOneProfile(email: string): Promise> { - const employee = await this.prisma.employees.findFirst({ - where: { user: { email } }, - select: { - user: { - select: { - first_name: true, - last_name: true, - email: true, - phone_number: true, - residence: true, - }, - }, - supervisor: { - select: { - user: { - select: { - first_name: true, - last_name: true, - }, - }, - }, - }, - job_title: true, - company_code: true, - first_work_day: true, - last_work_day: true, - } - }); - if (!employee)return {success: false, error: `Employee with email ${email} not found`}; + } + }).then(rows => rows.map(r => ({ + first_name: r.user.first_name, + last_name: r.user.last_name, + email: r.user.email, + company_name: r.company_code, + job_title: r.job_title ?? '', + external_payroll_id: r.external_payroll_id, + employee_full_name: `${r.user.first_name} ${r.user.last_name}`, + supervisor_full_name: `${r.supervisor?.user.first_name} ${r.supervisor?.user.last_name}`, + })), + ) + return { + success: true, + data: employee_list, + } + } - return { - success: true, - data: { - first_name: employee.user.first_name, - last_name: employee.user.last_name, - email: employee.user.email, - residence: employee.user.residence, - phone_number: employee.user.phone_number, - company_name: employee.company_code, - job_title: employee.job_title, - employee_full_name: `${employee.user.first_name} ${employee.user.last_name}`, - first_work_day: employee.first_work_day.toISOString().slice(0, 10), - last_work_day: employee.last_work_day ? employee.last_work_day.toISOString().slice(0, 10) : null, - supervisor_full_name: employee.supervisor ? `${employee.supervisor.user.first_name}, ${employee.supervisor.user.last_name}` : null, - } - }; - } + async findOwnProfile(email: string): Promise, string>> { + const user_id = await this.emailResolver.resolveUserIdWithEmail(email); + if (!user_id.success) return { success: false, error: 'INVALID_USER' }; - async create(dto: CreateEmployeeDto): Promise { - const { - first_name, - last_name, - email, - phone_number, - residence, - external_payroll_id, - company_code, - job_title, - first_work_day, - last_work_day, - is_supervisor, - } = dto; + const existing_profile = await this.prisma.employees.findUnique({ + where: { user_id: user_id.data }, + select: { + user: { + select: { + first_name: true, + last_name: true, + email: true, + }, + }, + company_code: true, + job_title: true, + external_payroll_id: true, + supervisor: { + select: { + id: true, user: { + select: { + first_name: true, last_name: true + }, + }, + }, + }, + }, + }); + if (!existing_profile) return { success: false, error: 'EMPLOYEE_NOT_FOUND' }; - return this.prisma.$transaction(async (transaction) => { - const user: Users = await transaction.users.create({ - data: { - first_name, - last_name, - email, - phone_number, - residence, - }, - }); - return transaction.employees.create({ - data: { - user_id: user.id, - external_payroll_id, - company_code, - job_title, - first_work_day, - last_work_day, - is_supervisor, - }, - }); - }); - } + let company_name = 'Solucom'; + if (existing_profile.company_code === 271583) { + company_name = 'Targo'; + } + + return { + success: true, data: { + first_name: existing_profile.user.first_name, + last_name: existing_profile.user.last_name, + email: existing_profile.user.email, + supervisor_full_name: `${existing_profile.supervisor?.user.first_name} ${existing_profile.supervisor?.user.last_name}`, + company_name: company_name, + job_title: existing_profile.job_title ?? '', + external_payroll_id: existing_profile.external_payroll_id, + } + } + } + + async findOneDetailedProfile(email: string): Promise> { + const user_id = await this.emailResolver.resolveUserIdWithEmail(email); + if (!user_id.success) return { success: false, error: 'INVALID_USER' }; + + const employee = await this.prisma.employees.findUnique({ + where: { user_id: user_id.data }, + select: { + user: { + select: { + first_name: true, + last_name: true, + email: true, + phone_number: true, + residence: true, + user_module_access: { + select: { + dashboard: true, + employee_list: true, + employee_management: true, + personal_profile: true, + timesheets: true, + timesheets_approval: true, + } + } + }, + }, + supervisor: { + select: { + user: { + select: { + first_name: true, + last_name: true, + }, + }, + }, + }, + job_title: true, + company_code: true, + first_work_day: true, + last_work_day: true, + external_payroll_id: true, + is_supervisor: true, + } + }); + if (!employee) return { success: false, error: `EMPLOYEE_NOT_FOUND` }; + if (!employee.user) return { success: false, error: 'USER_NOT_FOUND' }; + + let company_name = 'Solucom'; + if (employee.company_code === 271583) { + company_name = 'Targo'; + } + + return { + success: true, + data: { + first_name: employee.user.first_name, + last_name: employee.user.last_name, + email: employee.user.email, + residence: employee.user.residence ?? '', + phone_number: employee.user.phone_number, + company_name: company_name, + is_supervisor: employee.is_supervisor ?? false, + job_title: employee.job_title ?? '', + external_payroll_id: employee.external_payroll_id, + employee_full_name: `${employee.user.first_name} ${employee.user.last_name}`, + first_work_day: employee.first_work_day.toISOString().slice(0, 10), + last_work_day: employee.last_work_day ? employee.last_work_day.toISOString().slice(0, 10) : 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, + }, + }, + }; + } + + async createEmployee(dto: EmployeeDetailedDto): Promise> { + let company_code = 271585; + if (dto.company_name === 'Targo') { + company_code = 271583; + } + + await this.prisma.$transaction(async (tx) => { + const user: Users = await tx.users.create({ + data: { + first_name: dto.first_name, + last_name: dto.last_name, + email: dto.email, + phone_number: dto.phone_number, + 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, + }, + }, + }, + }); + return tx.employees.create({ + data: { + user_id: user.id, + company_code: company_code, + job_title: dto.job_title, + first_work_day: dto.first_work_day, + last_work_day: dto.last_work_day, + is_supervisor: dto.is_supervisor, + }, + }) + }); + return { success: true, data: true } + } } \ No newline at end of file diff --git a/src/identity-and-account/identity-and-account.module.ts b/src/identity-and-account/identity-and-account.module.ts index 3476d73..076c176 100644 --- a/src/identity-and-account/identity-and-account.module.ts +++ b/src/identity-and-account/identity-and-account.module.ts @@ -2,7 +2,6 @@ import { Module } from "@nestjs/common"; import { EmailToIdResolver } from "src/common/mappers/email-id.mapper"; import { EmployeesController } from "src/identity-and-account/employees/controllers/employees.controller"; import { EmployeesModule } from "src/identity-and-account/employees/employees.module"; -import { EmployeesArchivalService } from "src/identity-and-account/employees/services/employees-archival.service"; import { EmployeesService } from "src/identity-and-account/employees/services/employees.service"; import { PreferencesController } from "src/identity-and-account/preferences/controllers/preferences.controller"; import { PreferencesModule } from "src/identity-and-account/preferences/preferences.module"; @@ -27,7 +26,6 @@ import { UsersModule } from "src/identity-and-account/users-management/users.mod ModuleAccessController, ], providers: [ - EmployeesArchivalService, EmployeesService, PreferencesService, UsersService, diff --git a/src/identity-and-account/preferences/controllers/preferences.controller.ts b/src/identity-and-account/preferences/controllers/preferences.controller.ts index e215581..b1aa246 100644 --- a/src/identity-and-account/preferences/controllers/preferences.controller.ts +++ b/src/identity-and-account/preferences/controllers/preferences.controller.ts @@ -2,23 +2,20 @@ import { Body, Controller, Get, Patch, Query, Req } from "@nestjs/common"; import { PreferencesService } from "../services/preferences.service"; import { PreferencesDto } from "../dtos/preferences.dto"; import { Result } from "src/common/errors/result-error.factory"; +import { Access } from "src/common/decorators/module-access.decorators"; @Controller('preferences') export class PreferencesController { - constructor(private readonly service: PreferencesService){} + constructor(private readonly service: PreferencesService) { } @Patch('update') - async updatePreferences( - @Req()req, - @Body() payload: PreferencesDto + async updatePreferences(@Access('email') email: string, @Body() payload: PreferencesDto ): Promise> { - const email = req.user?.email; return this.service.updatePreferences(email, payload); } @Get() - async findPreferences(@Req() req, @Query() employee_email?:string) { - const email = req.user?.email; + async findPreferences(@Access('email') email: string, @Query() employee_email?: string) { return this.service.findPreferences(email, employee_email); } diff --git a/src/identity-and-account/preferences/services/preferences.service.ts b/src/identity-and-account/preferences/services/preferences.service.ts index 6af26de..eda3523 100644 --- a/src/identity-and-account/preferences/services/preferences.service.ts +++ b/src/identity-and-account/preferences/services/preferences.service.ts @@ -1,6 +1,5 @@ import { DisplayLanguage, PreferencesDto } from "../dtos/preferences.dto"; import { PrismaService } from "src/prisma/prisma.service"; -import { Preferences, Prisma } from "@prisma/client"; import { Injectable } from "@nestjs/common"; import { Result } from "src/common/errors/result-error.factory"; import { EmailToIdResolver } from "src/common/mappers/email-id.mapper"; diff --git a/src/identity-and-account/user-module-access/controllers/module-access.controller.ts b/src/identity-and-account/user-module-access/controllers/module-access.controller.ts index 2aeeb8a..4f792a3 100644 --- a/src/identity-and-account/user-module-access/controllers/module-access.controller.ts +++ b/src/identity-and-account/user-module-access/controllers/module-access.controller.ts @@ -1,4 +1,6 @@ import { Body, Controller, Get, Patch, Query, Req } from "@nestjs/common"; +import { Access } from "src/common/decorators/module-access.decorators"; +import { Result } from "src/common/errors/result-error.factory"; import { ModuleAccess } from "src/identity-and-account/user-module-access/dtos/module-acces.dto"; import { AccessGetService } from "src/identity-and-account/user-module-access/services/module-access-get.service"; import { AccessUpdateService } from "src/identity-and-account/user-module-access/services/module-access-update.service"; @@ -11,30 +13,25 @@ export class ModuleAccessController { ) { } @Get() - async findAccess( - @Req() req, - @Query('employee_email') employee_email?: string - ) { - const email = req.user?.email; + async findAccess(@Access('email') email: string, @Query('employee_email') employee_email?: string + ): Promise> { + const granted_access = await this.getService.findModuleAccess(email); + if (!granted_access.success) return { success: false, error: 'INVALID_USER' }; + if (!granted_access.data.employee_management) return { success: false, error: 'UNAUTHORIZED_ACCESS' }; + await this.getService.findModuleAccess(email, employee_email); + return { success: true, data: true }; }; @Patch('update') - async updateAccess( - @Req() req, - @Body() dto: ModuleAccess, - @Query('employee_email') employee_email?: string - ) { - const email = req.user?.email; - await this.updateService.updateModuleAccess(email, dto, employee_email); - }; + async updateAccess(@Access('email') email: string, @Body() dto: ModuleAccess, @Query('employee_email') employee_email?: string + ): Promise> { + const granted_access = await this.getService.findModuleAccess(email); + if (!granted_access.success) return { success: false, error: 'INVALID_USER' }; + //check if credentials are enough to use this resource + if (!granted_access.data.employee_management) return { success: false, error: 'UNAUTHORIZED_ACCESS' }; - @Patch('revoke') - async revokeModuleAccess( - @Req() req, - @Query('employee_email') employee_email?: string - ) { - const email = req.user?.email; - await this.updateService.revokeModuleAccess(email, employee_email); + await this.updateService.updateModuleAccess(email, dto, employee_email); + return { success: true, data: true }; }; } \ No newline at end of file diff --git a/src/identity-and-account/user-module-access/dtos/module-acces.dto.ts b/src/identity-and-account/user-module-access/dtos/module-acces.dto.ts index 5de04d9..5e1e6be 100644 --- a/src/identity-and-account/user-module-access/dtos/module-acces.dto.ts +++ b/src/identity-and-account/user-module-access/dtos/module-acces.dto.ts @@ -5,6 +5,6 @@ export class ModuleAccess { @IsBoolean() timesheets_approval!: boolean; @IsBoolean() employee_list!: boolean; @IsBoolean() employee_management!: boolean; - @IsBoolean() personnal_profile!: boolean; + @IsBoolean() personal_profile!: boolean; @IsBoolean() dashboard!: boolean; } \ No newline at end of file diff --git a/src/identity-and-account/user-module-access/module-access.module.ts b/src/identity-and-account/user-module-access/module-access.module.ts index 0a0cd99..15c96d8 100644 --- a/src/identity-and-account/user-module-access/module-access.module.ts +++ b/src/identity-and-account/user-module-access/module-access.module.ts @@ -7,6 +7,6 @@ import { EmailToIdResolver } from "src/common/mappers/email-id.mapper"; @Module({ controllers: [ModuleAccessController], providers: [AccessUpdateService, AccessGetService, EmailToIdResolver], - exports: [], + exports: [AccessGetService], }) export class ModuleAccessModule { } \ No newline at end of file diff --git a/src/identity-and-account/user-module-access/services/module-access-get.service.ts b/src/identity-and-account/user-module-access/services/module-access-get.service.ts index 5aaf07d..828fd45 100644 --- a/src/identity-and-account/user-module-access/services/module-access-get.service.ts +++ b/src/identity-and-account/user-module-access/services/module-access-get.service.ts @@ -4,6 +4,7 @@ import { EmailToIdResolver } from "src/common/mappers/email-id.mapper"; import { ModuleAccess } from "src/identity-and-account/user-module-access/dtos/module-acces.dto"; import { PrismaService } from "src/prisma/prisma.service"; + @Injectable() export class AccessGetService { constructor( @@ -34,9 +35,9 @@ export class AccessGetService { timesheets_approval: access.timesheets_approval, employee_list: access.employee_list, employee_management: access.employee_management, - personnal_profile: access.personal_profile, + personal_profile: access.personal_profile, dashboard: access.dashboard, }; return { success: true, data: granted_access } - } + }; } \ No newline at end of file diff --git a/src/identity-and-account/user-module-access/services/module-access-update.service.ts b/src/identity-and-account/user-module-access/services/module-access-update.service.ts index 051a1a5..8fbb1c8 100644 --- a/src/identity-and-account/user-module-access/services/module-access-update.service.ts +++ b/src/identity-and-account/user-module-access/services/module-access-update.service.ts @@ -11,7 +11,7 @@ export class AccessUpdateService { private readonly emailResolver: EmailToIdResolver, ) { } - async updateModuleAccess(email: string, dto: ModuleAccess, employee_email?: string): Promise> { + async updateModuleAccess(email: string, dto: ModuleAccess, employee_email?: string): Promise> { const account_email = employee_email ?? email; const user_id = await this.emailResolver.resolveUserIdWithEmail(account_email); if (!user_id.success) return { success: false, error: 'EMPLOYEE_NOT_FOUND' }; @@ -30,21 +30,29 @@ export class AccessUpdateService { }); if (!orignal_access) return { success: false, error: 'MODULE_ACCESS_NOT_FOUND' }; - await this.prisma.userModuleAccess.update({ + const updated_access:ModuleAccess = await this.prisma.userModuleAccess.update({ where: { id: orignal_access.id }, data: { timesheets: dto.timesheets, timesheets_approval: dto.timesheets_approval, employee_list: dto.employee_list, employee_management: dto.employee_management, - personal_profile: dto.personnal_profile, + personal_profile: dto.personal_profile, dashboard: dto.dashboard, + }, + select: { + timesheets: true, + timesheets_approval: true, + employee_list: true, + employee_management: true, + personal_profile: true, + dashboard: true, } }) - return { success: true, data: true }; + return { success: true, data: updated_access }; } - async revokeModuleAccess(email: string, employee_email?: string): Promise> { + async revokeModuleAccess(email: string, employee_email?: string): Promise> { const account_email = employee_email ?? email; const user_id = await this.emailResolver.resolveUserIdWithEmail(account_email); if (!user_id.success) return { success: false, error: 'EMPLOYEE_NOT_FOUND' }; @@ -55,7 +63,7 @@ export class AccessUpdateService { }); if (!access) return { success: false, error: 'MODULE_ACCESS_NOT_FOUND' }; - await this.prisma.userModuleAccess.update({ + const revoked_access: ModuleAccess = await this.prisma.userModuleAccess.update({ where: { id: access.id }, data: { timesheets: false, @@ -63,10 +71,18 @@ export class AccessUpdateService { employee_list: false, employee_management: false, personal_profile: false, - dashboard: true, + dashboard: false, }, + select: { + timesheets: true, + timesheets_approval: true, + employee_list: true, + employee_management: true, + personal_profile: true, + dashboard: true, + } }); - return { success: true, data: true }; + return { success: true, data: revoked_access }; } } \ No newline at end of file diff --git a/src/identity-and-account/users-management/dtos/user.dto.ts b/src/identity-and-account/users-management/dtos/user.dto.ts deleted file mode 100644 index 8598b1f..0000000 --- a/src/identity-and-account/users-management/dtos/user.dto.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { Roles } from '@prisma/client'; - -export class UserDto { - @ApiProperty({ - example: 'd67f05be-6dd1-464f-b5f7-31b325e21b4a', - description: 'User`s unique UUID (primary key)', - }) - id: string; - - @ApiProperty({ - example: 'Aragorn', - description: 'user`s first name', - }) - first_name: string; - - @ApiProperty({ - example: 'Elessar', - description: 'user`s last name', - }) - last_name: string; - - @ApiProperty({ - example: 'king@arnor-gondor.gov', - description: 'Unique email address', - }) - email: string; - - @ApiProperty({ - example: 5141234567, - description: 'Unique phone number', - }) - phone_number: string; - - @ApiProperty({ - example: 'Minas Tirith, Gondor', - description: 'residence address (optional)', - required: false, - }) - residence?: string; - - @ApiProperty({ - example: 'EMPLOYEE', - enum: Roles, - description: 'User`s given role', - }) - role: Roles; -} 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 9a2ffd1..4664dcf 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 @@ -6,36 +6,44 @@ import { PrismaService } from 'src/prisma/prisma.service'; export abstract class AbstractUserService { constructor(protected readonly prisma: PrismaService) { } - findAll(): Promise { - return this.prisma.users.findMany(); - } - - async findOne(id: string): Promise { - const user = await this.prisma.users.findUnique({ where: { id } }); - if (!user) { - throw new NotFoundException(`User #${id} not found`); - } - return user; - } - async findOneByEmail(email: string): Promise> { - const user = await this.prisma.users.findUnique({ where: { email } }); + const user = await this.prisma.users.findUnique({ + where: { email }, + include: { + user_module_access: { + select: { + dashboard: true, + employee_list: true, + employee_management: true, + personal_profile: true, + timesheets: true, + timesheets_approval: true, + }, + }, + }, + + }); 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, } return clean_user; } - - async remove(id: string): Promise { - await this.findOne(id); - return this.prisma.users.delete({ where: { id } }); - } } diff --git a/src/main.ts b/src/main.ts index 0be6b3c..6d4b5d3 100644 --- a/src/main.ts +++ b/src/main.ts @@ -13,7 +13,6 @@ import { ModuleRef, NestFactory, Reflector } from '@nestjs/core'; import { AppModule } from './app.module'; // import { JwtAuthGuard } from './modules/authentication/guards/jwt-auth.guard'; import { RolesGuard } from './common/guards/roles.guard'; -import { OwnershipGuard } from './common/guards/ownership.guard'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; import { writeFileSync } from 'fs'; import * as session from 'express-session'; @@ -32,7 +31,6 @@ async function bootstrap() { app.useGlobalGuards( // new JwtAuthGuard(reflector), //Authentification JWT new RolesGuard(reflector), //deny-by-default and Role-based Access Control - new OwnershipGuard(reflector, app.get(ModuleRef)), //Global use of OwnershipGuard, not implemented yet ); // Authentication and session