Merge branch 'main' of git.targo.ca:Targo/targo_backend

This commit is contained in:
Nicolas Drolet 2025-12-01 11:31:36 -05:00
commit c7f92ed46c
29 changed files with 585 additions and 860 deletions

View File

@ -465,7 +465,7 @@
}, },
"/employees/profile": { "/employees/profile": {
"get": { "get": {
"operationId": "EmployeesController_findOneProfile", "operationId": "EmployeesController_findProfile",
"parameters": [ "parameters": [
{ {
"name": "employee_email", "name": "employee_email",
@ -501,37 +501,15 @@
} }
}, },
"/employees": { "/employees": {
"patch": {
"operationId": "EmployeesController_updateOrArchiveOrRestore",
"parameters": [],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateEmployeeDto"
}
}
}
},
"responses": {
"200": {
"description": ""
}
},
"tags": [
"Employees"
]
},
"post": { "post": {
"operationId": "EmployeesController_create", "operationId": "EmployeesController_createEmployee",
"parameters": [], "parameters": [],
"requestBody": { "requestBody": {
"required": true, "required": true,
"content": { "content": {
"application/json": { "application/json": {
"schema": { "schema": {
"$ref": "#/components/schemas/CreateEmployeeDto" "$ref": "#/components/schemas/EmployeeDetailedDto"
} }
} }
} }
@ -639,29 +617,6 @@
"ModuleAccess" "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": { "info": {
@ -729,153 +684,9 @@
"type": "object", "type": "object",
"properties": {} "properties": {}
}, },
"UpdateEmployeeDto": { "EmployeeDetailedDto": {
"type": "object", "type": "object",
"properties": { "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"
]
}, },
"PreferencesDto": { "PreferencesDto": {
"type": "object", "type": "object",

View File

@ -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";

View File

@ -53,7 +53,7 @@ model Employees {
supervisor Employees? @relation("EmployeeSupervisor", fields: [supervisor_id], references: [id]) supervisor Employees? @relation("EmployeeSupervisor", fields: [supervisor_id], references: [id])
supervisor_id Int? supervisor_id Int?
external_payroll_id Int external_payroll_id Int @default(autoincrement())
company_code Int company_code Int
first_work_day DateTime @db.Date first_work_day DateTime @db.Date
last_work_day DateTime? @db.Date last_work_day DateTime? @db.Date

View File

@ -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;
},
);

View File

@ -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);

View File

@ -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<boolean> {
const meta = this.reflector.get<OwnershipMeta>(
OWNER_KEY, context.getHandler(),
);
if (!meta)
return true;
const request = context.switchToHttp().getRequest<RequestWithUser>();
const user = request.user;
const resourceId = request.params[meta.idParam || 'id'];
const service = this.moduleRef.get<any>(
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;
}
}

View File

@ -13,19 +13,19 @@ export class EmployeeTimesheetResolver {
constructor( constructor(
private readonly prisma: PrismaService, private readonly prisma: PrismaService,
private readonly emailResolver: EmailToIdResolver, private readonly emailResolver: EmailToIdResolver,
) {} ) { }
readonly findTimesheetIdByEmail = async (email: string, date: Date, client?: Tx): Promise<Result<{id: number}, string>> => { readonly findTimesheetIdByEmail = async (email: string, date: Date, client?: Tx): Promise<Result<{ id: number }, string>> => {
const db = client ?? this.prisma; const db = client ?? this.prisma;
const employee_id = await this.emailResolver.findIdByEmail(email); 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); const start_date = weekStartSunday(date);
console.log('start date: ', start_date); console.log('start date: ', start_date);
const timesheet = await db.timesheets.findFirst({ 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 }, select: { id: true },
}); });
if(!timesheet) throw new NotFoundException(`TIMESHEET_NOT_FOUND`); if (!timesheet) return { success: false, error: 'TIMESHEET_NOT_FOUND' };
return { success: true, data: {id: timesheet.id} }; return { success: true, data: { id: timesheet.id } };
} }
} }

View File

@ -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)) {
}
}
}

View File

@ -1,56 +1,65 @@
import { Controller, Get, Patch, Param, Body, NotFoundException, Req, Post, Query } from "@nestjs/common"; import { Controller, Get, Query, Body, Post } from "@nestjs/common";
import { Employees } from "@prisma/client"; import { Access } from "src/common/decorators/module-access.decorators";
import { RolesAllowed } from "src/common/decorators/roles.decorators"; import { RolesAllowed } from "src/common/decorators/roles.decorators";
import { Result } from "src/common/errors/result-error.factory"; import { Result } from "src/common/errors/result-error.factory";
import { GLOBAL_CONTROLLER_ROLES, MANAGER_ROLES } from "src/common/shared/role-groupes"; import { GLOBAL_CONTROLLER_ROLES } from "src/common/shared/role-groupes";
import { CreateEmployeeDto } from "src/identity-and-account/employees/dtos/create-employee.dto"; import { EmployeeDetailedDto } from "src/identity-and-account/employees/dtos/employee-detailed.dto";
import { EmployeeListItemDto } from "src/identity-and-account/employees/dtos/list-employee.dto"; import { EmployeeDto } from "src/identity-and-account/employees/dtos/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 { EmployeesService } from "src/identity-and-account/employees/services/employees.service"; 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) @RolesAllowed(...GLOBAL_CONTROLLER_ROLES)
@Controller('employees') @Controller('employees')
export class EmployeesController { export class EmployeesController {
constructor( constructor(
private readonly employeesService: EmployeesService, private readonly employeesService: EmployeesService,
private readonly archiveService: EmployeesArchivalService, private readonly accessGetService: AccessGetService,
) { } ) { }
@Get('profile') @Get('profile')
findOneProfile( async findProfile(@Access('email') email: string, @Query('employee_email') employee_email?: string,
@Req() req, ): Promise<Result<Partial<EmployeeDetailedDto>, string>> {
@Query('employee_email') employee_email?: string, const granted_access = await this.accessGetService.findModuleAccess(email);
): Promise<Result<EmployeeProfileItemDto,string>> { if (!granted_access.success) return { success: false, error: 'INVALID_USER' };
const email = req.user?.email; if (!granted_access.data.employee_management) {
return this.employeesService.findOneProfile(employee_email ?? email); 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') @Get('employee-list')
@RolesAllowed(...MANAGER_ROLES) async findListEmployees(@Access('email') email: string
findListEmployees(): Promise<Result<EmployeeListItemDto[], string>> { ): Promise<Result<EmployeeDto[], string>> {
return this.employeesService.findListEmployees(); 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() @Post()
@RolesAllowed(...MANAGER_ROLES) async createEmployee(@Access('email') email: string, @Body() dto: EmployeeDetailedDto
async updateOrArchiveOrRestore(@Req() req, @Body() dto: UpdateEmployeeDto,) { ): Promise<Result<boolean, string>> {
// if last_work_day is set => archive the employee const granted_access = await this.accessGetService.findModuleAccess(email);
// else if employee is archived and first_work_day or last_work_day = null => restore if (!granted_access.success) return { success: false, error: 'INVALID_USER' };
//otherwise => standard update if (!granted_access.data.employee_management) return { success: false, error: 'UNAUTHORIZED_ACCESS' };
const email = req.user?.email; return await this.employeesService.createEmployee(dto);
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() // @Patch()
@RolesAllowed(...MANAGER_ROLES) // async updateOrArchiveOrRestore(@Req() req, @Body() dto: UpdateEmployeeDto,) {
create(@Body() dto: CreateEmployeeDto): Promise<Employees> { // // if last_work_day is set => archive the employee
return this.employeesService.create(dto); // // 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;
//
} }

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -1,12 +1,13 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { EmployeesController } from './controllers/employees.controller'; import { EmployeesController } from './controllers/employees.controller';
import { EmployeesService } from './services/employees.service'; 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({ @Module({
imports: [], imports: [],
controllers: [EmployeesController], controllers: [EmployeesController],
providers: [EmployeesService, EmployeesArchivalService], providers: [EmployeesService, AccessGetService, EmailToIdResolver],
exports: [EmployeesService ], exports: [EmployeesService ],
}) })
export class EmployeesModule {} export class EmployeesModule {}

View File

@ -1,173 +1,173 @@
import { Injectable } from "@nestjs/common"; // import { Injectable } from "@nestjs/common";
import { Employees, Users } from "@prisma/client"; // import { Employees, Users } from "@prisma/client";
import { UpdateEmployeeDto } from "src/identity-and-account/employees/dtos/update-employee.dto"; // // 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 { toDateOrUndefined, toDateOrNull } from "src/identity-and-account/employees/utils/employee.utils";
import { PrismaService } from "src/prisma/prisma.service"; // import { PrismaService } from "src/prisma/prisma.service";
@Injectable() // @Injectable()
export class EmployeesArchivalService { // export class EmployeesArchivalService {
constructor(private readonly prisma: PrismaService) { } // constructor(private readonly prisma: PrismaService) { }
async patchEmployee(email: string, dto: UpdateEmployeeDto): Promise<Employees | null> { // async patchEmployee(email: string, dto: UpdateEmployeeDto): Promise<Employees | null> {
// 1) Tenter sur employés actifs // // 1) Tenter sur employés actifs
const active = await this.prisma.employees.findFirst({ // const active = await this.prisma.employees.findFirst({
where: { user: { email } }, // where: { user: { email } },
include: { user: true }, // include: { user: true },
}); // });
if (active) { // if (active) {
// Archivage : si on reçoit un last_work_day défini et que l'employé nest pas déjà terminé // // Archivage : si on reçoit un last_work_day défini et que l'employé nest pas déjà terminé
// if (dto.last_work_day !== undefined && active.last_work_day == null && dto.last_work_day !== null) { // // if (dto.last_work_day !== undefined && active.last_work_day == null && dto.last_work_day !== null) {
// return this.archiveOnTermination(active, dto); // // return this.archiveOnTermination(active, dto);
// } // // }
// Sinon, update standard (split Users/Employees) // // Sinon, update standard (split Users/Employees)
const { // const {
first_name, // first_name,
last_name, // last_name,
email: new_email, // email: new_email,
phone_number, // phone_number,
residence, // residence,
external_payroll_id, // external_payroll_id,
company_code, // company_code,
job_title, // job_title,
first_work_day, // first_work_day,
last_work_day, // last_work_day,
supervisor_id, // supervisor_id,
is_supervisor, // is_supervisor,
} = dto as any; // } = dto as any;
const first_work_d = toDateOrUndefined(first_work_day); // const first_work_d = toDateOrUndefined(first_work_day);
const last_work_d = Object.prototype.hasOwnProperty('last_work_day') // const last_work_d = Object.prototype.hasOwnProperty('last_work_day')
? toDateOrNull(last_work_day ?? null) // ? toDateOrNull(last_work_day ?? null)
: undefined; // : undefined;
await this.prisma.$transaction(async (transaction) => { // await this.prisma.$transaction(async (transaction) => {
if ( // if (
first_name !== undefined || // first_name !== undefined ||
last_name !== undefined || // last_name !== undefined ||
new_email !== undefined || // new_email !== undefined ||
phone_number !== undefined || // phone_number !== undefined ||
residence !== undefined // residence !== undefined
) { // ) {
await transaction.users.update({ // await transaction.users.update({
where: { id: active.user_id }, // where: { id: active.user_id },
data: { // data: {
...(first_name !== undefined ? { first_name } : {}), // ...(first_name !== undefined ? { first_name } : {}),
...(last_name !== undefined ? { last_name } : {}), // ...(last_name !== undefined ? { last_name } : {}),
...(email !== undefined ? { email: new_email } : {}), // ...(email !== undefined ? { email: new_email } : {}),
...(phone_number !== undefined ? { phone_number } : {}), // ...(phone_number !== undefined ? { phone_number } : {}),
...(residence !== undefined ? { residence } : {}), // ...(residence !== undefined ? { residence } : {}),
}, // },
}); // });
} // }
const updated = await transaction.employees.update({ // const updated = await transaction.employees.update({
where: { id: active.id }, // where: { id: active.id },
data: { // data: {
...(external_payroll_id !== undefined ? { external_payroll_id } : {}), // ...(external_payroll_id !== undefined ? { external_payroll_id } : {}),
...(company_code !== undefined ? { company_code } : {}), // ...(company_code !== undefined ? { company_code } : {}),
...(job_title !== undefined ? { job_title } : {}), // ...(job_title !== undefined ? { job_title } : {}),
...(first_work_d !== undefined ? { first_work_day: first_work_d } : {}), // ...(first_work_d !== undefined ? { first_work_day: first_work_d } : {}),
...(last_work_d !== undefined ? { last_work_day: last_work_d } : {}), // ...(last_work_d !== undefined ? { last_work_day: last_work_d } : {}),
...(is_supervisor !== undefined ? { is_supervisor } : {}), // ...(is_supervisor !== undefined ? { is_supervisor } : {}),
...(supervisor_id !== undefined ? { supervisor_id } : {}), // ...(supervisor_id !== undefined ? { supervisor_id } : {}),
}, // },
include: { user: true }, // 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 } }); // const user = await this.prisma.users.findUnique({ where: { email } });
if (!user) return null; // if (!user) return null;
// 2) Pas trouvé en actifs → regarder en archive (pour restauration) // // 2) Pas trouvé en actifs → regarder en archive (pour restauration)
// const archived = await this.prisma.employeesArchive.findFirst({ // // const archived = await this.prisma.employeesArchive.findFirst({
// where: { user_id: user.id }, // // where: { user_id: user.id },
// include: { user: true }, // // include: { user: true },
// }); // // });
// if (archived) { // // if (archived) {
// // Condition de restauration : last_work_day === null ou first_work_day fourni // // // Condition de restauration : last_work_day === null ou first_work_day fourni
// const restore = dto.last_work_day === null || dto.first_work_day != null; // // const restore = dto.last_work_day === null || dto.first_work_day != null;
// if (restore) { // // if (restore) {
// return this.restoreEmployee(archived, dto); // // return this.restoreEmployee(archived, dto);
// } // // }
// } // // }
// 3) Ni actif, ni archivé → 404 dans le controller // // 3) Ni actif, ni archivé → 404 dans le controller
return null; // return null;
} // }
//transfers the employee to archive and then delete from employees table // //transfers the employee to archive and then delete from employees table
// private async archiveOnTermination(active: Employees & { user: Users }, dto: UpdateEmployeeDto): Promise<EmployeesArchive> { // // private async archiveOnTermination(active: Employees & { user: Users }, dto: UpdateEmployeeDto): Promise<EmployeesArchive> {
// const last_work_d = toDateOrNull(dto.last_work_day!); // // const last_work_d = toDateOrNull(dto.last_work_day!);
// if (!last_work_d) throw new Error('invalide last_work_day for archive'); // // if (!last_work_d) throw new Error('invalide last_work_day for archive');
// return this.prisma.$transaction(async transaction => { // // return this.prisma.$transaction(async transaction => {
// //detach crew from supervisor if employee is a supervisor // // //detach crew from supervisor if employee is a supervisor
// await transaction.employees.updateMany({ // // await transaction.employees.updateMany({
// where: { supervisor_id: active.id }, // // where: { supervisor_id: active.id },
// data: { supervisor_id: null }, // // data: { supervisor_id: null },
// }) // // })
// const archived = await transaction.employeesArchive.create({ // // const archived = await transaction.employeesArchive.create({
// data: { // // data: {
// employee_id: active.id, // // employee_id: active.id,
// user_id: active.user_id, // // user_id: active.user_id,
// first_name: active.user.first_name, // // first_name: active.user.first_name,
// last_name: active.user.last_name, // // last_name: active.user.last_name,
// company_code: active.company_code, // // company_code: active.company_code,
// job_title: active.job_title, // // job_title: active.job_title,
// first_work_day: active.first_work_day, // // first_work_day: active.first_work_day,
// last_work_day: last_work_d, // // last_work_day: last_work_d,
// supervisor_id: active.supervisor_id ?? null, // // supervisor_id: active.supervisor_id ?? null,
// is_supervisor: active.is_supervisor, // // is_supervisor: active.is_supervisor,
// external_payroll_id: active.external_payroll_id, // // external_payroll_id: active.external_payroll_id,
// }, // // },
// include: { user: true } // // include: { user: true }
// }); // // });
// //delete from employees table // // //delete from employees table
// await transaction.employees.delete({ where: { id: active.id } }); // // await transaction.employees.delete({ where: { id: active.id } });
// //return archived employee // // //return archived employee
// return archived // // return archived
// }); // // });
// } // // }
// //transfers the employee from archive to the employees table // // //transfers the employee from archive to the employees table
// private async restoreEmployee(archived: EmployeesArchive & { user: Users }, dto: UpdateEmployeeDto): Promise<Employees> { // // private async restoreEmployee(archived: EmployeesArchive & { user: Users }, dto: UpdateEmployeeDto): Promise<Employees> {
// // const first_work_d = toDateOrUndefined(dto.first_work_day); // // // const first_work_d = toDateOrUndefined(dto.first_work_day);
// return this.prisma.$transaction(async transaction => { // // return this.prisma.$transaction(async transaction => {
// //restores the archived employee into the employees table // // //restores the archived employee into the employees table
// const restored = await transaction.employees.create({ // // const restored = await transaction.employees.create({
// data: { // // data: {
// user_id: archived.user_id, // // user_id: archived.user_id,
// company_code: archived.company_code, // // company_code: archived.company_code,
// job_title: archived.job_title, // // job_title: archived.job_title,
// first_work_day: archived.first_work_day, // // first_work_day: archived.first_work_day,
// last_work_day: null, // // last_work_day: null,
// is_supervisor: archived.is_supervisor ?? false, // // is_supervisor: archived.is_supervisor ?? false,
// external_payroll_id: archived.external_payroll_id, // // external_payroll_id: archived.external_payroll_id,
// }, // // },
// }); // // });
// //deleting archived entry by id // // //deleting archived entry by id
// await transaction.employeesArchive.delete({ where: { id: archived.id } }); // // await transaction.employeesArchive.delete({ where: { id: archived.id } });
// //return restored employee // // //return restored employee
// return restored; // // return restored;
// }); // // });
// } // // }
// //fetches all archived employees // // //fetches all archived employees
// async findAllArchived(): Promise<EmployeesArchive[]> { // // async findAllArchived(): Promise<EmployeesArchive[]> {
// return this.prisma.employeesArchive.findMany(); // // return this.prisma.employeesArchive.findMany();
// } // // }
// //fetches an archived employee // // //fetches an archived employee
// async findOneArchived(id: number): Promise<EmployeesArchive> { // // async findOneArchived(id: number): Promise<EmployeesArchive> {
// return this.prisma.employeesArchive.findUniqueOrThrow({ where: { id } }); // // return this.prisma.employeesArchive.findUniqueOrThrow({ where: { id } });
// } // // }
} // }

View File

@ -1,135 +1,225 @@
import { Injectable, NotFoundException } from "@nestjs/common"; import { Injectable } from "@nestjs/common";
import { Employees, Users } from "@prisma/client"; import { Users } from "@prisma/client";
import { Result } from "src/common/errors/result-error.factory"; import { Result } from "src/common/errors/result-error.factory";
import { CreateEmployeeDto } from "src/identity-and-account/employees/dtos/create-employee.dto"; import { EmailToIdResolver } from "src/common/mappers/email-id.mapper";
import { EmployeeListItemDto } from "src/identity-and-account/employees/dtos/list-employee.dto"; import { EmployeeDetailedDto } from "src/identity-and-account/employees/dtos/employee-detailed.dto";
import { EmployeeProfileItemDto } from "src/identity-and-account/employees/dtos/profil-employee.dto"; import { EmployeeDto } from "src/identity-and-account/employees/dtos/employee.dto";
import { PrismaService } from "src/prisma/prisma.service"; import { PrismaService } from "src/prisma/prisma.service";
@Injectable() @Injectable()
export class EmployeesService { export class EmployeesService {
constructor(private readonly prisma: PrismaService) { } constructor(
private readonly prisma: PrismaService,
private readonly emailResolver: EmailToIdResolver,
) { }
async findListEmployees(): Promise<Result<EmployeeListItemDto[], string>> { async findListEmployees(): Promise<Result<EmployeeDto[], string>> {
return {success: true, data:await this.prisma.employees.findMany({ const employee_list = await this.prisma.employees.findMany({
select: { select: {
user: { user: {
select: { select: {
first_name: true, first_name: true,
last_name: true, last_name: true,
email: true, email: true,
}, },
}, },
supervisor: { supervisor: {
select: { select: {
user: { user: {
select: { select: {
first_name: true, first_name: true,
last_name: true, last_name: true,
}, },
}, },
}, },
}, },
job_title: true, job_title: true,
company_code: true, company_code: true,
} external_payroll_id: 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 findOneProfile(email: string): Promise<Result<EmployeeProfileItemDto, string>> { }
const employee = await this.prisma.employees.findFirst({ }).then(rows => rows.map(r => ({
where: { user: { email } }, first_name: r.user.first_name,
select: { last_name: r.user.last_name,
user: { email: r.user.email,
select: { company_name: r.company_code,
first_name: true, job_title: r.job_title ?? '',
last_name: true, external_payroll_id: r.external_payroll_id,
email: true, employee_full_name: `${r.user.first_name} ${r.user.last_name}`,
phone_number: true, supervisor_full_name: `${r.supervisor?.user.first_name} ${r.supervisor?.user.last_name}`,
residence: true, })),
}, )
}, return {
supervisor: { success: true,
select: { data: employee_list,
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`};
return { async findOwnProfile(email: string): Promise<Result<Partial<EmployeeDetailedDto>, string>> {
success: true, const user_id = await this.emailResolver.resolveUserIdWithEmail(email);
data: { if (!user_id.success) return { success: false, error: 'INVALID_USER' };
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 create(dto: CreateEmployeeDto): Promise<Employees> { const existing_profile = await this.prisma.employees.findUnique({
const { where: { user_id: user_id.data },
first_name, select: {
last_name, user: {
email, select: {
phone_number, first_name: true,
residence, last_name: true,
external_payroll_id, email: true,
company_code, },
job_title, },
first_work_day, company_code: true,
last_work_day, job_title: true,
is_supervisor, external_payroll_id: true,
} = dto; 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) => { let company_name = 'Solucom';
const user: Users = await transaction.users.create({ if (existing_profile.company_code === 271583) {
data: { company_name = 'Targo';
first_name, }
last_name,
email, return {
phone_number, success: true, data: {
residence, first_name: existing_profile.user.first_name,
}, last_name: existing_profile.user.last_name,
}); email: existing_profile.user.email,
return transaction.employees.create({ supervisor_full_name: `${existing_profile.supervisor?.user.first_name} ${existing_profile.supervisor?.user.last_name}`,
data: { company_name: company_name,
user_id: user.id, job_title: existing_profile.job_title ?? '',
external_payroll_id, external_payroll_id: existing_profile.external_payroll_id,
company_code, }
job_title, }
first_work_day, }
last_work_day,
is_supervisor, async findOneDetailedProfile(email: string): Promise<Result<EmployeeDetailedDto, string>> {
}, 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<Result<boolean, string>> {
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 }
}
} }

View File

@ -2,7 +2,6 @@ import { Module } from "@nestjs/common";
import { EmailToIdResolver } from "src/common/mappers/email-id.mapper"; import { EmailToIdResolver } from "src/common/mappers/email-id.mapper";
import { EmployeesController } from "src/identity-and-account/employees/controllers/employees.controller"; import { EmployeesController } from "src/identity-and-account/employees/controllers/employees.controller";
import { EmployeesModule } from "src/identity-and-account/employees/employees.module"; 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 { EmployeesService } from "src/identity-and-account/employees/services/employees.service";
import { PreferencesController } from "src/identity-and-account/preferences/controllers/preferences.controller"; import { PreferencesController } from "src/identity-and-account/preferences/controllers/preferences.controller";
import { PreferencesModule } from "src/identity-and-account/preferences/preferences.module"; 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, ModuleAccessController,
], ],
providers: [ providers: [
EmployeesArchivalService,
EmployeesService, EmployeesService,
PreferencesService, PreferencesService,
UsersService, UsersService,

View File

@ -2,23 +2,20 @@ import { Body, Controller, Get, Patch, Query, Req } from "@nestjs/common";
import { PreferencesService } from "../services/preferences.service"; import { PreferencesService } from "../services/preferences.service";
import { PreferencesDto } from "../dtos/preferences.dto"; import { PreferencesDto } from "../dtos/preferences.dto";
import { Result } from "src/common/errors/result-error.factory"; import { Result } from "src/common/errors/result-error.factory";
import { Access } from "src/common/decorators/module-access.decorators";
@Controller('preferences') @Controller('preferences')
export class PreferencesController { export class PreferencesController {
constructor(private readonly service: PreferencesService){} constructor(private readonly service: PreferencesService) { }
@Patch('update') @Patch('update')
async updatePreferences( async updatePreferences(@Access('email') email: string, @Body() payload: PreferencesDto
@Req()req,
@Body() payload: PreferencesDto
): Promise<Result<PreferencesDto, string>> { ): Promise<Result<PreferencesDto, string>> {
const email = req.user?.email;
return this.service.updatePreferences(email, payload); return this.service.updatePreferences(email, payload);
} }
@Get() @Get()
async findPreferences(@Req() req, @Query() employee_email?:string) { async findPreferences(@Access('email') email: string, @Query() employee_email?: string) {
const email = req.user?.email;
return this.service.findPreferences(email, employee_email); return this.service.findPreferences(email, employee_email);
} }

View File

@ -1,6 +1,5 @@
import { DisplayLanguage, PreferencesDto } from "../dtos/preferences.dto"; import { DisplayLanguage, PreferencesDto } from "../dtos/preferences.dto";
import { PrismaService } from "src/prisma/prisma.service"; import { PrismaService } from "src/prisma/prisma.service";
import { Preferences, Prisma } from "@prisma/client";
import { Injectable } from "@nestjs/common"; import { Injectable } from "@nestjs/common";
import { Result } from "src/common/errors/result-error.factory"; import { Result } from "src/common/errors/result-error.factory";
import { EmailToIdResolver } from "src/common/mappers/email-id.mapper"; import { EmailToIdResolver } from "src/common/mappers/email-id.mapper";

View File

@ -1,4 +1,6 @@
import { Body, Controller, Get, Patch, Query, Req } from "@nestjs/common"; 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 { 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 { 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"; import { AccessUpdateService } from "src/identity-and-account/user-module-access/services/module-access-update.service";
@ -11,30 +13,25 @@ export class ModuleAccessController {
) { } ) { }
@Get() @Get()
async findAccess( async findAccess(@Access('email') email: string, @Query('employee_email') employee_email?: string
@Req() req, ): Promise<Result<boolean, string>> {
@Query('employee_email') employee_email?: string const granted_access = await this.getService.findModuleAccess(email);
) { if (!granted_access.success) return { success: false, error: 'INVALID_USER' };
const email = req.user?.email; if (!granted_access.data.employee_management) return { success: false, error: 'UNAUTHORIZED_ACCESS' };
await this.getService.findModuleAccess(email, employee_email); await this.getService.findModuleAccess(email, employee_email);
return { success: true, data: true };
}; };
@Patch('update') @Patch('update')
async updateAccess( async updateAccess(@Access('email') email: string, @Body() dto: ModuleAccess, @Query('employee_email') employee_email?: string
@Req() req, ): Promise<Result<boolean, string>> {
@Body() dto: ModuleAccess, const granted_access = await this.getService.findModuleAccess(email);
@Query('employee_email') employee_email?: string if (!granted_access.success) return { success: false, error: 'INVALID_USER' };
) { //check if credentials are enough to use this resource
const email = req.user?.email; if (!granted_access.data.employee_management) return { success: false, error: 'UNAUTHORIZED_ACCESS' };
await this.updateService.updateModuleAccess(email, dto, employee_email);
};
@Patch('revoke') await this.updateService.updateModuleAccess(email, dto, employee_email);
async revokeModuleAccess( return { success: true, data: true };
@Req() req,
@Query('employee_email') employee_email?: string
) {
const email = req.user?.email;
await this.updateService.revokeModuleAccess(email, employee_email);
}; };
} }

View File

@ -5,6 +5,6 @@ export class ModuleAccess {
@IsBoolean() timesheets_approval!: boolean; @IsBoolean() timesheets_approval!: boolean;
@IsBoolean() employee_list!: boolean; @IsBoolean() employee_list!: boolean;
@IsBoolean() employee_management!: boolean; @IsBoolean() employee_management!: boolean;
@IsBoolean() personnal_profile!: boolean; @IsBoolean() personal_profile!: boolean;
@IsBoolean() dashboard!: boolean; @IsBoolean() dashboard!: boolean;
} }

View File

@ -7,6 +7,6 @@ import { EmailToIdResolver } from "src/common/mappers/email-id.mapper";
@Module({ @Module({
controllers: [ModuleAccessController], controllers: [ModuleAccessController],
providers: [AccessUpdateService, AccessGetService, EmailToIdResolver], providers: [AccessUpdateService, AccessGetService, EmailToIdResolver],
exports: [], exports: [AccessGetService],
}) })
export class ModuleAccessModule { } export class ModuleAccessModule { }

View File

@ -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 { ModuleAccess } from "src/identity-and-account/user-module-access/dtos/module-acces.dto";
import { PrismaService } from "src/prisma/prisma.service"; import { PrismaService } from "src/prisma/prisma.service";
@Injectable() @Injectable()
export class AccessGetService { export class AccessGetService {
constructor( constructor(
@ -34,9 +35,9 @@ export class AccessGetService {
timesheets_approval: access.timesheets_approval, timesheets_approval: access.timesheets_approval,
employee_list: access.employee_list, employee_list: access.employee_list,
employee_management: access.employee_management, employee_management: access.employee_management,
personnal_profile: access.personal_profile, personal_profile: access.personal_profile,
dashboard: access.dashboard, dashboard: access.dashboard,
}; };
return { success: true, data: granted_access } return { success: true, data: granted_access }
} };
} }

View File

@ -11,7 +11,7 @@ export class AccessUpdateService {
private readonly emailResolver: EmailToIdResolver, private readonly emailResolver: EmailToIdResolver,
) { } ) { }
async updateModuleAccess(email: string, dto: ModuleAccess, employee_email?: string): Promise<Result<boolean, string>> { async updateModuleAccess(email: string, dto: ModuleAccess, employee_email?: string): Promise<Result<ModuleAccess, string>> {
const account_email = employee_email ?? email; const account_email = employee_email ?? email;
const user_id = await this.emailResolver.resolveUserIdWithEmail(account_email); const user_id = await this.emailResolver.resolveUserIdWithEmail(account_email);
if (!user_id.success) return { success: false, error: 'EMPLOYEE_NOT_FOUND' }; 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' }; 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 }, where: { id: orignal_access.id },
data: { data: {
timesheets: dto.timesheets, timesheets: dto.timesheets,
timesheets_approval: dto.timesheets_approval, timesheets_approval: dto.timesheets_approval,
employee_list: dto.employee_list, employee_list: dto.employee_list,
employee_management: dto.employee_management, employee_management: dto.employee_management,
personal_profile: dto.personnal_profile, personal_profile: dto.personal_profile,
dashboard: dto.dashboard, 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<Result<boolean, string>> { async revokeModuleAccess(email: string, employee_email?: string): Promise<Result<ModuleAccess, string>> {
const account_email = employee_email ?? email; const account_email = employee_email ?? email;
const user_id = await this.emailResolver.resolveUserIdWithEmail(account_email); const user_id = await this.emailResolver.resolveUserIdWithEmail(account_email);
if (!user_id.success) return { success: false, error: 'EMPLOYEE_NOT_FOUND' }; 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' }; 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 }, where: { id: access.id },
data: { data: {
timesheets: false, timesheets: false,
@ -63,10 +71,18 @@ export class AccessUpdateService {
employee_list: false, employee_list: false,
employee_management: false, employee_management: false,
personal_profile: 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 };
} }
} }

View File

@ -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;
}

View File

@ -6,36 +6,44 @@ import { PrismaService } from 'src/prisma/prisma.service';
export abstract class AbstractUserService { export abstract class AbstractUserService {
constructor(protected readonly prisma: PrismaService) { } constructor(protected readonly prisma: PrismaService) { }
findAll(): Promise<Users[]> {
return this.prisma.users.findMany();
}
async findOne(id: string): Promise<Users> {
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<Partial<Users>> { async findOneByEmail(email: string): Promise<Partial<Users>> {
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) { if (!user) {
throw new NotFoundException(`No user with email #${email} exists`); 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 = { const clean_user = {
first_name: user.first_name, first_name: user.first_name,
last_name: user.last_name, last_name: user.last_name,
email: user.email, email: user.email,
role: user.role, role: user.role,
user_module_access,
} }
return clean_user; return clean_user;
} }
async remove(id: string): Promise<Users> {
await this.findOne(id);
return this.prisma.users.delete({ where: { id } });
}
} }

View File

@ -13,7 +13,6 @@ import { ModuleRef, NestFactory, Reflector } from '@nestjs/core';
import { AppModule } from './app.module'; import { AppModule } from './app.module';
// import { JwtAuthGuard } from './modules/authentication/guards/jwt-auth.guard'; // import { JwtAuthGuard } from './modules/authentication/guards/jwt-auth.guard';
import { RolesGuard } from './common/guards/roles.guard'; import { RolesGuard } from './common/guards/roles.guard';
import { OwnershipGuard } from './common/guards/ownership.guard';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { writeFileSync } from 'fs'; import { writeFileSync } from 'fs';
import * as session from 'express-session'; import * as session from 'express-session';
@ -32,7 +31,6 @@ async function bootstrap() {
app.useGlobalGuards( app.useGlobalGuards(
// new JwtAuthGuard(reflector), //Authentification JWT // new JwtAuthGuard(reflector), //Authentification JWT
new RolesGuard(reflector), //deny-by-default and Role-based Access Control 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 // Authentication and session