Merge branch 'main' of git.targo.ca:Targo/targo_backend into dev/matthieu/refactor

This commit is contained in:
Matthieu Haineault 2025-10-21 16:29:20 -04:00
commit 9270033f24
5 changed files with 605 additions and 63 deletions

View File

@ -153,6 +153,423 @@
] ]
} }
}, },
<<<<<<< HEAD
=======
"/employees/profile/{email}": {
"get": {
"operationId": "EmployeesController_findOneProfile",
"parameters": [
{
"name": "email",
"required": true,
"in": "path",
"description": "Identifier of the employee",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Employee profile found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EmployeeProfileItemDto"
}
}
}
},
"400": {
"description": "Employee profile not found"
}
},
"security": [
{
"access-token": []
}
],
"summary": "Find employee profile",
"tags": [
"Employees"
]
}
},
"/timesheets": {
"get": {
"operationId": "TimesheetsController_getPeriodByQuery",
"parameters": [
{
"name": "year",
"required": true,
"in": "query",
"schema": {
"type": "number"
}
},
{
"name": "period_no",
"required": true,
"in": "query",
"schema": {
"type": "number"
}
},
{
"name": "email",
"required": true,
"in": "query",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"security": [
{
"access-token": []
}
],
"tags": [
"Timesheets"
]
}
},
"/timesheets/{email}": {
"get": {
"operationId": "TimesheetsController_getByEmail",
"parameters": [
{
"name": "email",
"required": true,
"in": "path",
"schema": {
"type": "string"
}
},
{
"name": "offset",
"required": true,
"in": "query",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"security": [
{
"access-token": []
}
],
"tags": [
"Timesheets"
]
}
},
"/timesheets/shifts/{email}": {
"post": {
"operationId": "TimesheetsController_createTimesheetShifts",
"parameters": [
{
"name": "email",
"required": true,
"in": "path",
"schema": {
"type": "string"
}
},
{
"name": "offset",
"required": true,
"in": "query",
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateWeekShiftsDto"
}
}
}
},
"responses": {
"201": {
"description": ""
}
},
"security": [
{
"access-token": []
}
],
"tags": [
"Timesheets"
]
}
},
"/Expenses/upsert/{email}/{date}": {
"put": {
"operationId": "ExpensesController_upsert_by_date",
"parameters": [
{
"name": "email",
"required": true,
"in": "path",
"schema": {
"type": "string"
}
},
{
"name": "date",
"required": true,
"in": "path",
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpsertExpenseDto"
}
}
}
},
"responses": {
"200": {
"description": ""
}
},
"security": [
{
"access-token": []
}
],
"tags": [
"Expenses"
]
}
},
"/Expenses/list/{email}/{year}/{period_no}": {
"get": {
"operationId": "ExpensesController_findExpenseListByPayPeriodAndEmail",
"parameters": [
{
"name": "email",
"required": true,
"in": "path",
"schema": {
"type": "string"
}
},
{
"name": "year",
"required": true,
"in": "path",
"schema": {
"type": "number"
}
},
{
"name": "period_no",
"required": true,
"in": "path",
"schema": {
"type": "number"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"security": [
{
"access-token": []
}
],
"tags": [
"Expenses"
]
}
},
"/shifts/upsert/{email}": {
"put": {
"operationId": "ShiftsController_upsert_by_date",
"parameters": [
{
"name": "email",
"required": true,
"in": "path",
"schema": {
"type": "string"
}
},
{
"name": "action",
"required": true,
"in": "query",
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpsertShiftDto"
}
}
}
},
"responses": {
"200": {
"description": ""
}
},
"security": [
{
"access-token": []
}
],
"tags": [
"Shifts"
]
}
},
"/shifts/delete/{email}/{date}": {
"delete": {
"operationId": "ShiftsController_remove",
"parameters": [
{
"name": "email",
"required": true,
"in": "path",
"schema": {
"type": "string"
}
},
{
"name": "date",
"required": true,
"in": "path",
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpsertShiftDto"
}
}
}
},
"responses": {
"200": {
"description": ""
}
},
"security": [
{
"access-token": []
}
],
"tags": [
"Shifts"
]
}
},
"/shifts/approval/{id}": {
"patch": {
"operationId": "ShiftsController_approve",
"parameters": [
{
"name": "id",
"required": true,
"in": "path",
"schema": {
"type": "number"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"security": [
{
"access-token": []
}
],
"tags": [
"Shifts"
]
}
},
"/shifts/summary": {
"get": {
"operationId": "ShiftsController_getSummary",
"parameters": [],
"responses": {
"200": {
"description": ""
}
},
"security": [
{
"access-token": []
}
],
"tags": [
"Shifts"
]
}
},
"/shifts/export.csv": {
"get": {
"operationId": "ShiftsController_exportCsv",
"parameters": [],
"responses": {
"200": {
"description": ""
}
},
"security": [
{
"access-token": []
}
],
"tags": [
"Shifts"
]
}
},
>>>>>>> 88f7c0cb0e3824f6faed91058a7d55a9cca048a7
"/notifications/summary": { "/notifications/summary": {
"get": { "get": {
"operationId": "NotificationsController_summary", "operationId": "NotificationsController_summary",
@ -181,6 +598,80 @@
] ]
} }
}, },
<<<<<<< HEAD
=======
"/leave-requests/upsert": {
"post": {
"operationId": "LeaveRequestController_upsertLeaveRequest",
"parameters": [],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpsertLeaveRequestDto"
}
}
}
},
"responses": {
"201": {
"description": ""
}
},
"security": [
{
"access-token": []
}
],
"tags": [
"Leave Requests"
]
}
},
"/auth/v1/login": {
"get": {
"operationId": "AuthController_login",
"parameters": [],
"responses": {
"200": {
"description": ""
}
},
"tags": [
"Auth"
]
}
},
"/auth/callback": {
"get": {
"operationId": "AuthController_loginCallback",
"parameters": [],
"responses": {
"200": {
"description": ""
}
},
"tags": [
"Auth"
]
}
},
"/auth/me": {
"get": {
"operationId": "AuthController_getProfile",
"parameters": [],
"responses": {
"200": {
"description": ""
}
},
"tags": [
"Auth"
]
}
},
>>>>>>> 88f7c0cb0e3824f6faed91058a7d55a9cca048a7
"/oauth-sessions": { "/oauth-sessions": {
"post": { "post": {
"operationId": "OauthSessionsController_create", "operationId": "OauthSessionsController_create",
@ -836,6 +1327,29 @@
"first_work_day" "first_work_day"
] ]
}, },
<<<<<<< HEAD
=======
"EmployeeProfileItemDto": {
"type": "object",
"properties": {}
},
"CreateWeekShiftsDto": {
"type": "object",
"properties": {}
},
"UpsertExpenseDto": {
"type": "object",
"properties": {}
},
"UpsertShiftDto": {
"type": "object",
"properties": {}
},
"UpsertLeaveRequestDto": {
"type": "object",
"properties": {}
},
>>>>>>> 88f7c0cb0e3824f6faed91058a7d55a9cca048a7
"CreateOauthSessionDto": { "CreateOauthSessionDto": {
"type": "object", "type": "object",
"properties": { "properties": {

View File

@ -1,4 +1,4 @@
import { Controller, Get, Req, Res, UseGuards } from '@nestjs/common'; import { Controller, Get, Req, Res, UnauthorizedException, UseGuards } from '@nestjs/common';
import { OIDCLoginGuard } from '../guards/authentik-auth.guard'; import { OIDCLoginGuard } from '../guards/authentik-auth.guard';
import { Request, Response } from 'express'; import { Request, Response } from 'express';
@ -7,11 +7,20 @@ export class AuthController {
@UseGuards(OIDCLoginGuard) @UseGuards(OIDCLoginGuard)
@Get('/v1/login') @Get('/v1/login')
login() {} login() { }
@Get('/callback') @Get('/callback')
@UseGuards(OIDCLoginGuard) @UseGuards(OIDCLoginGuard)
loginCallback(@Req() req: Request, @Res() res: Response) { loginCallback(@Req() req: Request, @Res() res: Response) {
res.redirect('http://localhost:9000/#/login-success'); res.redirect('http://localhost:9000/#/login-success');
} }
@Get('/me')
getProfile(@Req() req: Request) {
if (!req.user) {
throw new UnauthorizedException('Not logged in');
}
return req.user;
}
} }

View File

@ -3,6 +3,7 @@ import { Strategy as OIDCStrategy, Profile, VerifyCallback } from 'passport-open
import { PassportStrategy } from '@nestjs/passport'; import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { AuthentikAuthService } from '../services/authentik-auth.service'; import { AuthentikAuthService } from '../services/authentik-auth.service';
import { ValidationError } from 'class-validator';
export interface AuthentikPayload { export interface AuthentikPayload {
iss: string; // Issuer iss: string; // Issuer
@ -38,18 +39,27 @@ export class AuthentikStrategy extends PassportStrategy(OIDCStrategy, 'openidcon
} }
async validate( async validate(
issuer: string, _issuer: string,
profile: Profile, profile: Profile,
_context: any, _context: any,
idToken: string, _idToken: string,
accessToken: string, _accessToken: string,
refreshToken: string, _refreshToken: string,
params: any, _params: any,
cb: VerifyCallback, cb: VerifyCallback,
): Promise<any> { ): Promise<any> {
try {
const email = profile.emails?.[0]?.value;
console.log('email: ', email);
if (!email) return cb(new Error('Missing email in OIDC profile'), false);
// saving all info from validate() into NestJS session under 'user' const user = await this.authentikAuthService.validateUser(email);
/* TODO: Will need to add authorization logic with Prisma here later */ console.log('user: ', user);
return cb(null, { issuer, ...profile, idToken, accessToken, refreshToken, ...params }); if (!user) return cb(new Error('User not found'), false);
return cb(null, user);
} catch (err) {
return cb(err, false);
}
} }
} }

View File

@ -6,6 +6,7 @@ import { RolesAllowed } from '../../../common/decorators/roles.decorators';
import { ApiBearerAuth, ApiOperation, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger'; import { ApiBearerAuth, ApiOperation, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger';
import { EmployeeListItemDto } from '../dtos/list-employee.dto'; import { EmployeeListItemDto } from '../dtos/list-employee.dto';
import { EmployeesArchivalService } from '../services/employees-archival.service'; import { EmployeesArchivalService } from '../services/employees-archival.service';
import { EmployeeProfileItemDto } from 'src/modules/employees/dtos/profil-employee.dto';
@ApiTags('Employees') @ApiTags('Employees')
@ApiBearerAuth('access-token') @ApiBearerAuth('access-token')
@ -76,14 +77,14 @@ export class EmployeesController {
// return this.employeesService.findOne(email); // return this.employeesService.findOne(email);
// } // }
// @Get('profile/:email') @Get('profile/:email')
// @ApiOperation({summary: 'Find employee profile' }) @ApiOperation({summary: 'Find employee profile' })
// @ApiParam({ name: 'email', type: String, description: 'Identifier of the employee' }) @ApiParam({ name: 'email', type: String, description: 'Identifier of the employee' })
// @ApiResponse({ status: 200, description: 'Employee profile found', type: EmployeeProfileItemDto }) @ApiResponse({ status: 200, description: 'Employee profile found', type: EmployeeProfileItemDto })
// @ApiResponse({ status: 400, description: 'Employee profile not found' }) @ApiResponse({ status: 400, description: 'Employee profile not found' })
// findOneProfile(@Param('email') email: string): Promise<EmployeeProfileItemDto> { findOneProfile(@Param('email') email: string): Promise<EmployeeProfileItemDto> {
// return this.employeesService.findOneProfile(email); return this.employeesService.findOneProfile(email);
// } }
// @Delete(':email') // @Delete(':email')
// //@RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR ) // //@RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR )

View File

@ -18,12 +18,20 @@ export abstract class AbstractUserService {
return user; return user;
} }
async findOneByEmail( email: string ): Promise<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 } });
if (!user) { if (!user) {
throw new NotFoundException(`No user with email #${email} exists`); throw new NotFoundException(`No user with email #${email} exists`);
} }
return user;
const clean_user = {
first_name: user.first_name,
last_name: user.last_name,
email: user.email,
role: user.role,
}
return clean_user;
} }
async remove(id: string): Promise<Users> { async remove(id: string): Promise<Users> {