From 5df657d7738c7277b86e3215316d3104e71f6cb1 Mon Sep 17 00:00:00 2001 From: Matthieu Haineault Date: Mon, 4 Aug 2025 11:54:44 -0400 Subject: [PATCH 1/3] fix(module): fix typos and added import to shifts.module.ts --- src/app.module.ts | 2 ++ src/modules/shifts/shifts.module.ts | 6 +++++- .../validation/controllers/shifts-validation.controller.ts | 2 +- ...ts-validation.service.ts => shifts-validation.module.ts} | 0 4 files changed, 8 insertions(+), 2 deletions(-) rename src/modules/shifts/validation/{shifts-validation.service.ts => shifts-validation.module.ts} (100%) diff --git a/src/app.module.ts b/src/app.module.ts index 5f49b87..f160032 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -19,6 +19,7 @@ import { ArchivalModule } from './modules/archival/archival.module'; import { BankCodesModule } from './modules/bank-codes/bank-codes.module'; import { OvertimeService } from './modules/business-logics/services/overtime.service'; import { BusinessLogicsModule } from './modules/business-logics/business-logics.module'; +import { ShiftsValidationModule } from './modules/shifts/validation/shifts-validation.module'; @Module({ imports: [ @@ -36,6 +37,7 @@ import { BusinessLogicsModule } from './modules/business-logics/business-logics. PayperiodsModule, PrismaModule, ShiftsModule, + ShiftsValidationModule, TimesheetsModule, UsersModule, ], diff --git a/src/modules/shifts/shifts.module.ts b/src/modules/shifts/shifts.module.ts index 09d63a1..57cd331 100644 --- a/src/modules/shifts/shifts.module.ts +++ b/src/modules/shifts/shifts.module.ts @@ -2,9 +2,13 @@ import { Module } from '@nestjs/common'; import { ShiftsController } from './controllers/shifts.controller'; import { ShiftsService } from './services/shifts.service'; import { BusinessLogicsModule } from 'src/modules/business-logics/business-logics.module'; +import { ShiftsValidationModule } from './validation/shifts-validation.module'; @Module({ - imports: [BusinessLogicsModule], + imports: [ + BusinessLogicsModule, + ShiftsValidationModule, + ], controllers: [ShiftsController], providers: [ShiftsService], exports: [ShiftsService], diff --git a/src/modules/shifts/validation/controllers/shifts-validation.controller.ts b/src/modules/shifts/validation/controllers/shifts-validation.controller.ts index f5c7426..298808f 100644 --- a/src/modules/shifts/validation/controllers/shifts-validation.controller.ts +++ b/src/modules/shifts/validation/controllers/shifts-validation.controller.ts @@ -45,7 +45,7 @@ export class ShiftsValidationController { ].join(','); }).join('\n'); - return Buffer.from(header + body, 'utf8'); + return Buffer.from('\uFEFF' + header + body, 'utf8'); } } \ No newline at end of file diff --git a/src/modules/shifts/validation/shifts-validation.service.ts b/src/modules/shifts/validation/shifts-validation.module.ts similarity index 100% rename from src/modules/shifts/validation/shifts-validation.service.ts rename to src/modules/shifts/validation/shifts-validation.module.ts From 5aac0463565848e8d6db6d898275a6824e4c37e9 Mon Sep 17 00:00:00 2001 From: Matthieu Haineault Date: Tue, 5 Aug 2025 09:17:39 -0400 Subject: [PATCH 2/3] feat(CSV): export to CSV modul fit filters --- .../migration.sql | 37 ++++ .../migration.sql | 12 ++ prisma/schema.prisma | 9 +- .../controllers/csv-exports.controller.ts | 45 +++++ src/modules/exports/csv-exports.module.ts | 11 + .../exports/dtos/export-csv-options.dto.ts | 26 +++ .../exports/services/csv-exports.service.ts | 188 ++++++++++++++++++ 7 files changed, 324 insertions(+), 4 deletions(-) create mode 100644 prisma/migrations/20250804192610_rename_o_auth_table/migration.sql create mode 100644 prisma/migrations/20250804192837_added_sid_column_to_o_auth_sessions/migration.sql create mode 100644 src/modules/exports/controllers/csv-exports.controller.ts create mode 100644 src/modules/exports/csv-exports.module.ts create mode 100644 src/modules/exports/dtos/export-csv-options.dto.ts create mode 100644 src/modules/exports/services/csv-exports.service.ts diff --git a/prisma/migrations/20250804192610_rename_o_auth_table/migration.sql b/prisma/migrations/20250804192610_rename_o_auth_table/migration.sql new file mode 100644 index 0000000..82c4572 --- /dev/null +++ b/prisma/migrations/20250804192610_rename_o_auth_table/migration.sql @@ -0,0 +1,37 @@ +/* + Warnings: + + - You are about to drop the `refresh_tokens` table. If the table is not empty, all the data it contains will be lost. + +*/ +-- DropForeignKey +ALTER TABLE "refresh_tokens" DROP CONSTRAINT "refresh_tokens_user_id_fkey"; + +-- DropTable +DROP TABLE "refresh_tokens"; + +-- CreateTable +CREATE TABLE "oauth_sessions" ( + "id" TEXT NOT NULL, + "user_id" UUID NOT NULL, + "application" TEXT NOT NULL, + "access_token" TEXT NOT NULL, + "refresh_token" TEXT NOT NULL, + "access_token_expiry" TIMESTAMP(3) NOT NULL, + "refresh_token_expiry" TIMESTAMP(3), + "is_revoked" BOOLEAN NOT NULL DEFAULT false, + "scopes" JSONB NOT NULL DEFAULT '[]', + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3), + + CONSTRAINT "oauth_sessions_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "oauth_sessions_access_token_key" ON "oauth_sessions"("access_token"); + +-- CreateIndex +CREATE UNIQUE INDEX "oauth_sessions_refresh_token_key" ON "oauth_sessions"("refresh_token"); + +-- AddForeignKey +ALTER TABLE "oauth_sessions" ADD CONSTRAINT "oauth_sessions_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/migrations/20250804192837_added_sid_column_to_o_auth_sessions/migration.sql b/prisma/migrations/20250804192837_added_sid_column_to_o_auth_sessions/migration.sql new file mode 100644 index 0000000..abce895 --- /dev/null +++ b/prisma/migrations/20250804192837_added_sid_column_to_o_auth_sessions/migration.sql @@ -0,0 +1,12 @@ +/* + Warnings: + + - A unique constraint covering the columns `[sid]` on the table `oauth_sessions` will be added. If there are existing duplicate values, this will fail. + - Added the required column `sid` to the `oauth_sessions` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "oauth_sessions" ADD COLUMN "sid" TEXT NOT NULL; + +-- CreateIndex +CREATE UNIQUE INDEX "oauth_sessions_sid_key" ON "oauth_sessions"("sid"); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index edb3ca1..3d317ee 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -25,7 +25,7 @@ model Users { employee Employees? @relation("UserEmployee") customer Customers? @relation("UserCustomer") - oauth_access_tokens OAuthAccessTokens[] @relation("UserOAuthAccessToken") + oauth_sessions OAuthSessions[] @relation("UserOAuthSessions") employees_archive EmployeesArchive[] @relation("UsersToEmployeesToArchive") customer_archive CustomersArchive[] @relation("UserToCustomersToArchive") @@ -245,13 +245,14 @@ model ExpensesArchive { @@map("expenses_archive") } -model OAuthAccessTokens { +model OAuthSessions { id String @id @default(cuid()) - user Users @relation("UserOAuthAccessToken", fields: [user_id], references: [id]) + user Users @relation("UserOAuthSessions", fields: [user_id], references: [id]) user_id String @db.Uuid application String access_token String @unique refresh_token String @unique + sid String @unique access_token_expiry DateTime refresh_token_expiry DateTime? is_revoked Boolean @default(false) @@ -259,7 +260,7 @@ model OAuthAccessTokens { created_at DateTime @default(now()) updated_at DateTime? - @@map("refresh_tokens") + @@map("oauth_sessions") } enum Roles { diff --git a/src/modules/exports/controllers/csv-exports.controller.ts b/src/modules/exports/controllers/csv-exports.controller.ts new file mode 100644 index 0000000..60de449 --- /dev/null +++ b/src/modules/exports/controllers/csv-exports.controller.ts @@ -0,0 +1,45 @@ +import { Controller, Get, Header, Query, UseGuards } from "@nestjs/common"; +import { RolesGuard } from "src/common/guards/roles.guard"; +import { Roles as RoleEnum } from '.prisma/client'; +import { CsvExportService } from "../services/csv-exports.service"; +import { ExportCompany, ExportCsvOptionsDto, ExportType } from "../dtos/export-csv-options.dto"; +import { RolesAllowed } from "src/common/decorators/roles.decorators"; + + +@Controller('exports') +@UseGuards(RolesGuard) +export class CsvExportController { + constructor(private readonly csvService: CsvExportService) {} + + @Get('csv') + @Header('Content-Type', 'text/csv; charset=utf-8') + @Header('Content-Dispoition', 'attachment; filename="export.csv"') + @RolesAllowed(RoleEnum.ADMIN, RoleEnum.ACCOUNTING, RoleEnum.HR) + async exportCsv(@Query() options: ExportCsvOptionsDto, + @Query('period') periodId: string ): Promise { + + //sets default values + const companies = options.companies && options.companies.length ? options.companies : + [ ExportCompany.TARGO, ExportCompany.SOLUCOM]; + const types = options.type && options.type.length ? options.type : + Object.values(ExportType); + + //collects all + const all = await this.csvService.collectTransaction(Number(periodId), companies); + + //filters by type + const filtered = all.filter(r => { + switch (r.bankCode.toLocaleLowerCase()) { + case 'holiday' : return types.includes(ExportType.HOLIDAY); + case 'vacation' : return types.includes(ExportType.VACATION); + case 'sick-leave': return types.includes(ExportType.SICK_LEAVE); + case 'expenses' : return types.includes(ExportType.EXPENSES); + default : return types.includes(ExportType.SHIFTS); + } + }); + + //generating the csv file + return this.csvService.generateCsv(filtered); + } + +} \ No newline at end of file diff --git a/src/modules/exports/csv-exports.module.ts b/src/modules/exports/csv-exports.module.ts new file mode 100644 index 0000000..92a5a96 --- /dev/null +++ b/src/modules/exports/csv-exports.module.ts @@ -0,0 +1,11 @@ +import { Module } from "@nestjs/common"; +import { CsvExportController } from "./controllers/csv-exports.controller"; +import { CsvExportService } from "./services/csv-exports.service"; + +@Module({ + providers:[CsvExportService], + controllers: [CsvExportController], +}) +export class CsvExportModule {} + + diff --git a/src/modules/exports/dtos/export-csv-options.dto.ts b/src/modules/exports/dtos/export-csv-options.dto.ts new file mode 100644 index 0000000..dc969ad --- /dev/null +++ b/src/modules/exports/dtos/export-csv-options.dto.ts @@ -0,0 +1,26 @@ +import { IsArray, IsEnum, IsOptional } from "class-validator"; + +export enum ExportType { + SHIFTS = 'Quart de travail', + EXPENSES = 'Depenses', + HOLIDAY = 'Ferie', + VACATION = 'Vacance', + SICK_LEAVE = 'Absence' +} + +export enum ExportCompany { + TARGO = 'Targo', + SOLUCOM = 'Solucom', +} + +export class ExportCsvOptionsDto { + @IsOptional() + @IsArray() + @IsEnum(ExportCompany, { each: true }) + companies?: ExportCompany[]; + + @IsOptional() + @IsArray() + @IsEnum(ExportType, { each: true }) + type?: ExportType[]; +} \ No newline at end of file diff --git a/src/modules/exports/services/csv-exports.service.ts b/src/modules/exports/services/csv-exports.service.ts new file mode 100644 index 0000000..30c15c1 --- /dev/null +++ b/src/modules/exports/services/csv-exports.service.ts @@ -0,0 +1,188 @@ +import { PrismaService } from "src/prisma/prisma.service"; +import { ExportCompany } from "../dtos/export-csv-options.dto"; +import { Injectable, NotFoundException } from "@nestjs/common"; + +export interface CsvRow { + companyCode: number; + externalPayrollId: number; + fullName: string; + bankCode: string; + quantityHours?: number; + amount?: number; + weekNumber: number; + payDate: string; + holidayDate?: string; +} + +@Injectable() +export class CsvExportService { + constructor(private readonly prisma: PrismaService) {} + + async collectTransaction(periodId: number, companies: ExportCompany[]): Promise { + const companyCodes = companies.map(c => c === ExportCompany.TARGO ? 1 : 2); + + const period = await this.prisma.payPeriods.findUnique({ + where: { period_number: periodId }, + }); + if(!period) { + throw new NotFoundException(`Pay period ${periodId} not found`); + } + + const startDate = period.start_date; + const endDate = period.end_date; + + //fetching shifts + const shifts = await this.prisma.shifts.findMany({ + where: { date: { gte: startDate, lte: endDate }, + timesheet: { employee: { company_code: { in: companyCodes} } }, + }, + include: { bank_code: true, + timesheet: { include: {employee: { include: { user:true, + supervisor: { include: { user:true } }, + }}, + }}, + }, + }); + + //fetching expenses + const expenses = await this.prisma.expenses.findMany({ + where: { date: { gte: startDate, lte: endDate }, + timesheet: { employee: { company_code: { in: companyCodes} } }, + }, + include: { bank_code: true, + timesheet: { include: { employee: { include: { user: true, + supervisor: { include: { user:true } }, + } }, + } }, + }, + }); + + //fetching leave requests + const leaves = await this.prisma.leaveRequests.findMany({ + where : { start_date_time: { gte: startDate, lte: endDate }, + employee: { company_code: { in: companyCodes } }, + }, + include: { bank_code: true, + employee: { include: { user: true, + supervisor: { include: { user: true } }, + }}, + }, + }); + + const rows: CsvRow[] = []; + + //Shifts Mapping + for (const s of shifts) { + const emp = s.timesheet.employee; + const weekNumber = this.computeWeekNumber(startDate, s.date); + const hours = this.computeHours(s.start_time, s.end_time); + + rows.push({ + companyCode: emp.company_code, + externalPayrollId: emp.external_payroll_id, + fullName: `${emp.user.first_name} ${emp.user.last_name}`, + bankCode: s.bank_code.bank_code, + quantityHours: hours, + amount: undefined, + weekNumber, + payDate: this.formatDate(endDate), + holidayDate: undefined, + }); + } + + //Expenses Mapping + for (const e of expenses) { + const emp = e.timesheet.employee; + const weekNumber = this.computeWeekNumber(startDate, e.date); + + rows.push({ + companyCode: emp.company_code, + externalPayrollId: emp.external_payroll_id, + fullName: `${emp.user.first_name} ${emp.user.last_name}`, + bankCode: e.bank_code.bank_code, + quantityHours: undefined, + amount: Number(e.amount), + weekNumber, + payDate: this.formatDate(endDate), + holidayDate: undefined, + }); + } + + //Leaves Mapping + for(const l of leaves) { + if(!l.bank_code) continue; + const emp = l.employee; + const start = l.start_date_time; + const end = l.end_date_time ?? start; + + const weekNumber = this.computeWeekNumber(startDate, start); + const hours = this.computeHours(start, end); + + rows.push({ + companyCode: emp.company_code, + externalPayrollId: emp.external_payroll_id, + fullName: `${emp.user.first_name} ${emp.user.last_name}`, + bankCode: l.bank_code.bank_code, + quantityHours: hours, + amount: undefined, + weekNumber, + payDate: this.formatDate(endDate), + holidayDate: undefined, + }); + } + + //Final Mapping and sorts + return rows.sort((a,b) => { + if(a.externalPayrollId !== b.externalPayrollId) { + return a.externalPayrollId - b.externalPayrollId; + } + if(a.bankCode !== b.bankCode) { + return a.bankCode.localeCompare(b.bankCode); + } + return a.weekNumber - b.weekNumber; + }); + } + + generateCsv(rows: CsvRow[]): Buffer { + const header = [ + 'companyCode', + 'externalPayrolId', + 'fullName', + 'bankCode', + 'quantityHours', + 'amount', + 'weekNumber', + 'payDate', + 'holidayDate', + ].join(',') + '\n'; + + const body = rows.map(r => [ + r.companyCode, + r.externalPayrollId, + `${r.fullName.replace(/"/g, '""')}"`, + r.bankCode, + r.quantityHours?.toFixed(2) ?? '', + r.weekNumber, + r.payDate, + r.holidayDate ?? '', + ].join(',')).join('\n'); + + return Buffer.from('\uFEFF' + header + body, 'utf8'); + } + + + private computeHours(start: Date, end: Date): number { + const diffMs = end.getTime() - start.getTime(); + return +(diffMs / 1000 / 3600).toFixed(2); + } + + private computeWeekNumber(start: Date, date: Date): number { + const days = Math.floor((date.getTime() - start.getTime()) / (1000*60*60*24)); + return Math.floor(days / 7 ) + 1; + } + + private formatDate(d:Date): string { + return d.toISOString().split('T')[0]; + } + +} From b0406b3a4c00223b9430ef29b60a4775beca4328 Mon Sep 17 00:00:00 2001 From: Matthieu Haineault Date: Tue, 5 Aug 2025 09:44:43 -0400 Subject: [PATCH 3/3] refactor(Oauth-sessions): refactor OAuth-Access-Token to OAuth-sessions --- docs/swagger/swagger-spec.json | 100 ++++++++++-------- src/app.module.ts | 4 +- .../oauth-access-tokens.controller.ts | 63 ----------- .../dtos/update-oauth-access-token.dto.ts | 4 - .../oauth-access-tokens.module.ts | 10 -- .../controllers/oauth-sessions.controller.ts | 63 +++++++++++ .../dtos/create-oauth-sessions.dto.ts} | 5 +- .../oauth-sessions.entity.ts} | 2 +- .../dtos/update-oauth-sessions.dto.ts | 4 + .../oauth-sessions/oauth-sessions.module.ts | 10 ++ .../services/oauth-sessions.service.ts} | 30 +++--- 11 files changed, 157 insertions(+), 138 deletions(-) delete mode 100644 src/modules/oauth-access-tokens/controllers/oauth-access-tokens.controller.ts delete mode 100644 src/modules/oauth-access-tokens/dtos/update-oauth-access-token.dto.ts delete mode 100644 src/modules/oauth-access-tokens/oauth-access-tokens.module.ts create mode 100644 src/modules/oauth-sessions/controllers/oauth-sessions.controller.ts rename src/modules/{oauth-access-tokens/dtos/create-oauth-access-token.dto.ts => oauth-sessions/dtos/create-oauth-sessions.dto.ts} (95%) rename src/modules/{oauth-access-tokens/dtos/swagger-entities/oauth-access-token.entity.ts => oauth-sessions/dtos/swagger-entities/oauth-sessions.entity.ts} (97%) create mode 100644 src/modules/oauth-sessions/dtos/update-oauth-sessions.dto.ts create mode 100644 src/modules/oauth-sessions/oauth-sessions.module.ts rename src/modules/{oauth-access-tokens/services/oauth-access-tokens.service.ts => oauth-sessions/services/oauth-sessions.service.ts} (67%) diff --git a/docs/swagger/swagger-spec.json b/docs/swagger/swagger-spec.json index 59d8045..9f084d0 100644 --- a/docs/swagger/swagger-spec.json +++ b/docs/swagger/swagger-spec.json @@ -3,7 +3,7 @@ "paths": { "/": { "get": { - "operationId": "AppController_getHello", + "operationId": "ShiftsValidationController_getSummary", "parameters": [], "responses": { "200": { @@ -11,7 +11,7 @@ } }, "tags": [ - "App" + "ShiftsValidation" ] } }, @@ -933,6 +933,20 @@ ] } }, + "/export.csv": { + "get": { + "operationId": "ShiftsValidationController_exportCsv", + "parameters": [], + "responses": { + "200": { + "description": "" + } + }, + "tags": [ + "ShiftsValidation" + ] + } + }, "/leave-requests": { "post": { "operationId": "LeaveRequestController_create", @@ -1468,27 +1482,27 @@ ] } }, - "/oauth-access-tokens": { + "/oauth-sessions": { "post": { - "operationId": "OauthAccessTokensController_create", + "operationId": "OauthSessionsController_create", "parameters": [], "requestBody": { "required": true, "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/CreateOauthAccessTokenDto" + "$ref": "#/components/schemas/CreateOauthSessionDto" } } } }, "responses": { "201": { - "description": "OAuth access token created", + "description": "OAuth session created", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/OAuthAccessTokenEntity" + "$ref": "#/components/schemas/OAuthSessionEntity" } } } @@ -1499,49 +1513,49 @@ }, "security": [ { - "access-token": [] + "sessions": [] } ], - "summary": "Create OAuth access token", + "summary": "Create OAuth session", "tags": [ - "OAuth Access Tokens" + "OAuth Sessions" ] }, "get": { - "operationId": "OauthAccessTokensController_findAll", + "operationId": "OauthSessionsController_findAll", "parameters": [], "responses": { "201": { - "description": "List of OAuth access token found", + "description": "List of OAuth session found", "content": { "application/json": { "schema": { "type": "array", "items": { - "$ref": "#/components/schemas/OAuthAccessTokenEntity" + "$ref": "#/components/schemas/OAuthSessionEntity" } } } } }, "400": { - "description": "List of OAuth access token not found" + "description": "List of OAuth session not found" } }, "security": [ { - "access-token": [] + "sessions": [] } ], - "summary": "Find all OAuth access token", + "summary": "Find all OAuth session", "tags": [ - "OAuth Access Tokens" + "OAuth Sessions" ] } }, - "/oauth-access-tokens/{id}": { + "/oauth-sessions/{id}": { "get": { - "operationId": "OauthAccessTokensController_findOne", + "operationId": "OauthSessionsController_findOne", "parameters": [ { "name": "id", @@ -1554,31 +1568,31 @@ ], "responses": { "201": { - "description": "OAuth access token found", + "description": "OAuth session found", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/OAuthAccessTokenEntity" + "$ref": "#/components/schemas/OAuthSessionEntity" } } } }, "400": { - "description": "OAuth access token not found" + "description": "OAuth session not found" } }, "security": [ { - "access-token": [] + "sessions": [] } ], - "summary": "Find OAuth access token", + "summary": "Find OAuth session", "tags": [ - "OAuth Access Tokens" + "OAuth Sessions" ] }, "patch": { - "operationId": "OauthAccessTokensController_update", + "operationId": "OauthSessionsController_update", "parameters": [ { "name": "id", @@ -1594,38 +1608,38 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UpdateOauthAccessTokenDto" + "$ref": "#/components/schemas/UpdateOauthSessionDto" } } } }, "responses": { "201": { - "description": "OAuth access token updated", + "description": "OAuth session updated", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/OAuthAccessTokenEntity" + "$ref": "#/components/schemas/OAuthSessionEntity" } } } }, "400": { - "description": "OAuth access token not found" + "description": "OAuth session not found" } }, "security": [ { - "access-token": [] + "sessions": [] } ], - "summary": "Update OAuth access token", + "summary": "Update OAuth session", "tags": [ - "OAuth Access Tokens" + "OAuth Sessions" ] }, "delete": { - "operationId": "OauthAccessTokensController_remove", + "operationId": "OauthSessionsController_remove", "parameters": [ { "name": "id", @@ -1638,27 +1652,27 @@ ], "responses": { "201": { - "description": "OAuth access token deleted", + "description": "OAuth session deleted", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/OAuthAccessTokenEntity" + "$ref": "#/components/schemas/OAuthSessionEntity" } } } }, "400": { - "description": "OAuth access token not found" + "description": "OAuth session not found" } }, "security": [ { - "access-token": [] + "sessions": [] } ], - "summary": "Delete OAuth access token", + "summary": "Delete OAuth session", "tags": [ - "OAuth Access Tokens" + "OAuth Sessions" ] } }, @@ -2453,7 +2467,7 @@ } } }, - "CreateOauthAccessTokenDto": { + "CreateOauthSessionDto": { "type": "object", "properties": { "user_id": { @@ -2505,7 +2519,7 @@ "access_token_expiry" ] }, - "OAuthAccessTokenEntity": { + "OAuthSessionEntity": { "type": "object", "properties": { "id": { @@ -2586,7 +2600,7 @@ "created_at" ] }, - "UpdateOauthAccessTokenDto": { + "UpdateOauthSessionDto": { "type": "object", "properties": { "user_id": { diff --git a/src/app.module.ts b/src/app.module.ts index f160032..c22aaab 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -5,7 +5,6 @@ import { PrismaModule } from './prisma/prisma.module'; import { HealthModule } from './health/health.module'; import { HealthController } from './health/health.controller'; import { UsersModule } from './modules/users-management/users.module'; -import { OauthAccessTokensModule } from './modules/oauth-access-tokens/oauth-access-tokens.module'; import { CustomersModule } from './modules/customers/customers.module'; import { EmployeesModule } from './modules/employees/employees.module'; import { LeaveRequestsModule } from './modules/leave-requests/leave-requests.module'; @@ -20,6 +19,7 @@ import { BankCodesModule } from './modules/bank-codes/bank-codes.module'; import { OvertimeService } from './modules/business-logics/services/overtime.service'; import { BusinessLogicsModule } from './modules/business-logics/business-logics.module'; import { ShiftsValidationModule } from './modules/shifts/validation/shifts-validation.module'; +import { OauthSessionsModule } from './modules/oauth-sessions/oauth-sessions.module'; @Module({ imports: [ @@ -33,7 +33,7 @@ import { ShiftsValidationModule } from './modules/shifts/validation/shifts-valid ExpensesModule, HealthModule, LeaveRequestsModule, - OauthAccessTokensModule, + OauthSessionsModule, PayperiodsModule, PrismaModule, ShiftsModule, diff --git a/src/modules/oauth-access-tokens/controllers/oauth-access-tokens.controller.ts b/src/modules/oauth-access-tokens/controllers/oauth-access-tokens.controller.ts deleted file mode 100644 index 156f463..0000000 --- a/src/modules/oauth-access-tokens/controllers/oauth-access-tokens.controller.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { Body, Controller, Delete, Get, Param, Patch, Post, UseGuards } from '@nestjs/common'; -import { OauthAccessTokensService } from '../services/oauth-access-tokens.service'; -import { CreateOauthAccessTokenDto } from '../dtos/create-oauth-access-token.dto'; -import { OAuthAccessTokens } from '@prisma/client'; -import { UpdateOauthAccessTokenDto } from '../dtos/update-oauth-access-token.dto'; -import { RolesAllowed } from "src/common/decorators/roles.decorators"; -import { Roles as RoleEnum } from '.prisma/client'; -import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; -import { JwtAuthGuard } from 'src/modules/authentication/guards/jwt-auth.guard'; -import { OAuthAccessTokenEntity } from '../dtos/swagger-entities/oauth-access-token.entity'; - -@ApiTags('OAuth Access Tokens') -@ApiBearerAuth('access-token') -@UseGuards(JwtAuthGuard) -@Controller('oauth-access-tokens') -export class OauthAccessTokensController { - constructor(private readonly oauthAccessTokensService: OauthAccessTokensService){} - - @Post() - @RolesAllowed(RoleEnum.ADMIN) - @ApiOperation({summary: 'Create OAuth access token' }) - @ApiResponse({ status: 201, description: 'OAuth access token created', type: OAuthAccessTokenEntity }) - @ApiResponse({ status: 400, description: 'Incomplete task or invalid data' }) - create(@Body()dto: CreateOauthAccessTokenDto): Promise { - return this.oauthAccessTokensService.create(dto); - } - - @Get() - @RolesAllowed(RoleEnum.ADMIN) - @ApiOperation({summary: 'Find all OAuth access token' }) - @ApiResponse({ status: 201, description: 'List of OAuth access token found', type: OAuthAccessTokenEntity, isArray: true }) - @ApiResponse({ status: 400, description: 'List of OAuth access token not found' }) - findAll(): Promise { - return this.oauthAccessTokensService.findAll(); - } - - @Get(':id') - @RolesAllowed(RoleEnum.ADMIN) - @ApiOperation({summary: 'Find OAuth access token' }) - @ApiResponse({ status: 201, description: 'OAuth access token found', type: OAuthAccessTokenEntity }) - @ApiResponse({ status: 400, description: 'OAuth access token not found' }) - findOne(@Param('id') id: string): Promise { - return this.oauthAccessTokensService.findOne(id); - } - - @Patch(':id') - @RolesAllowed(RoleEnum.ADMIN) - @ApiOperation({summary: 'Update OAuth access token' }) - @ApiResponse({ status: 201, description: 'OAuth access token updated', type: OAuthAccessTokenEntity }) - @ApiResponse({ status: 400, description: 'OAuth access token not found' }) - update(@Param('id') id: string, @Body() dto: UpdateOauthAccessTokenDto): Promise { - return this.oauthAccessTokensService.update(id,dto); - } - - @Delete(':id') - @RolesAllowed(RoleEnum.ADMIN) - @ApiOperation({summary: 'Delete OAuth access token' }) - @ApiResponse({ status: 201, description: 'OAuth access token deleted', type: OAuthAccessTokenEntity }) - @ApiResponse({ status: 400, description: 'OAuth access token not found' }) - remove(@Param('id') id: string): Promise { - return this.oauthAccessTokensService.remove(id); - } -} diff --git a/src/modules/oauth-access-tokens/dtos/update-oauth-access-token.dto.ts b/src/modules/oauth-access-tokens/dtos/update-oauth-access-token.dto.ts deleted file mode 100644 index 055c338..0000000 --- a/src/modules/oauth-access-tokens/dtos/update-oauth-access-token.dto.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { PartialType } from "@nestjs/swagger"; -import { CreateOauthAccessTokenDto } from "./create-oauth-access-token.dto"; - -export class UpdateOauthAccessTokenDto extends PartialType(CreateOauthAccessTokenDto) {} diff --git a/src/modules/oauth-access-tokens/oauth-access-tokens.module.ts b/src/modules/oauth-access-tokens/oauth-access-tokens.module.ts deleted file mode 100644 index ca33da5..0000000 --- a/src/modules/oauth-access-tokens/oauth-access-tokens.module.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Module } from '@nestjs/common'; -import { OauthAccessTokensController } from './controllers/oauth-access-tokens.controller'; -import { OauthAccessTokensService } from './services/oauth-access-tokens.service'; -import { PrismaService } from 'src/prisma/prisma.service'; - -@Module({ - controllers: [OauthAccessTokensController], - providers: [OauthAccessTokensService, PrismaService] -}) -export class OauthAccessTokensModule {} diff --git a/src/modules/oauth-sessions/controllers/oauth-sessions.controller.ts b/src/modules/oauth-sessions/controllers/oauth-sessions.controller.ts new file mode 100644 index 0000000..08d30d5 --- /dev/null +++ b/src/modules/oauth-sessions/controllers/oauth-sessions.controller.ts @@ -0,0 +1,63 @@ +import { Body, Controller, Delete, Get, Param, Patch, Post, UseGuards } from '@nestjs/common'; +import { OAuthSessions } from '@prisma/client'; +import { RolesAllowed } from "src/common/decorators/roles.decorators"; +import { Roles as RoleEnum } from '.prisma/client'; +import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; +import { JwtAuthGuard } from 'src/modules/authentication/guards/jwt-auth.guard'; +import { CreateOauthSessionDto } from '../dtos/create-oauth-sessions.dto'; +import { OauthSessionsService } from '../services/oauth-sessions.service'; +import { OAuthSessionEntity } from '../dtos/swagger-entities/oauth-sessions.entity'; +import { UpdateOauthSessionDto } from '../dtos/update-oauth-sessions.dto'; + +@ApiTags('OAuth Sessions') +@ApiBearerAuth('sessions') +@UseGuards(JwtAuthGuard) +@Controller('oauth-sessions') +export class OauthSessionsController { + constructor(private readonly oauthSessionsService: OauthSessionsService){} + + @Post() + @RolesAllowed(RoleEnum.ADMIN) + @ApiOperation({summary: 'Create OAuth session' }) + @ApiResponse({ status: 201, description: 'OAuth session created', type: OAuthSessionEntity }) + @ApiResponse({ status: 400, description: 'Incomplete task or invalid data' }) + create(@Body()dto: CreateOauthSessionDto): Promise { + return this.oauthSessionsService.create(dto); + } + + @Get() + @RolesAllowed(RoleEnum.ADMIN) + @ApiOperation({summary: 'Find all OAuth session' }) + @ApiResponse({ status: 201, description: 'List of OAuth session found', type: OAuthSessionEntity, isArray: true }) + @ApiResponse({ status: 400, description: 'List of OAuth session not found' }) + findAll(): Promise { + return this.oauthSessionsService.findAll(); + } + + @Get(':id') + @RolesAllowed(RoleEnum.ADMIN) + @ApiOperation({summary: 'Find OAuth session' }) + @ApiResponse({ status: 201, description: 'OAuth session found', type: OAuthSessionEntity }) + @ApiResponse({ status: 400, description: 'OAuth session not found' }) + findOne(@Param('id') id: string): Promise { + return this.oauthSessionsService.findOne(id); + } + + @Patch(':id') + @RolesAllowed(RoleEnum.ADMIN) + @ApiOperation({summary: 'Update OAuth session' }) + @ApiResponse({ status: 201, description: 'OAuth session updated', type: OAuthSessionEntity }) + @ApiResponse({ status: 400, description: 'OAuth session not found' }) + update(@Param('id') id: string, @Body() dto: UpdateOauthSessionDto): Promise { + return this.oauthSessionsService.update(id,dto); + } + + @Delete(':id') + @RolesAllowed(RoleEnum.ADMIN) + @ApiOperation({summary: 'Delete OAuth session' }) + @ApiResponse({ status: 201, description: 'OAuth session deleted', type: OAuthSessionEntity }) + @ApiResponse({ status: 400, description: 'OAuth session not found' }) + remove(@Param('id') id: string): Promise { + return this.oauthSessionsService.remove(id); + } +} diff --git a/src/modules/oauth-access-tokens/dtos/create-oauth-access-token.dto.ts b/src/modules/oauth-sessions/dtos/create-oauth-sessions.dto.ts similarity index 95% rename from src/modules/oauth-access-tokens/dtos/create-oauth-access-token.dto.ts rename to src/modules/oauth-sessions/dtos/create-oauth-sessions.dto.ts index 8e6d1ef..46e098a 100644 --- a/src/modules/oauth-access-tokens/dtos/create-oauth-access-token.dto.ts +++ b/src/modules/oauth-sessions/dtos/create-oauth-sessions.dto.ts @@ -2,7 +2,7 @@ import { ApiProperty } from "@nestjs/swagger"; import { Type } from "class-transformer"; import { IsArray, IsDate, IsOptional, IsString, IsUUID } from "class-validator"; -export class CreateOauthAccessTokenDto { +export class CreateOauthSessionDto { @ApiProperty({ example: 'S7A2U8R7O6N6', @@ -18,6 +18,9 @@ export class CreateOauthAccessTokenDto { @IsString() application: string; + @IsString() + sid: string; + @ApiProperty({ example: 'L5O6R4D3/O6F3#T8H4E3&R6I4N6G4S7 ...', description: 'Access token', diff --git a/src/modules/oauth-access-tokens/dtos/swagger-entities/oauth-access-token.entity.ts b/src/modules/oauth-sessions/dtos/swagger-entities/oauth-sessions.entity.ts similarity index 97% rename from src/modules/oauth-access-tokens/dtos/swagger-entities/oauth-access-token.entity.ts rename to src/modules/oauth-sessions/dtos/swagger-entities/oauth-sessions.entity.ts index e80e6d7..c5c4c28 100644 --- a/src/modules/oauth-access-tokens/dtos/swagger-entities/oauth-access-token.entity.ts +++ b/src/modules/oauth-sessions/dtos/swagger-entities/oauth-sessions.entity.ts @@ -1,6 +1,6 @@ import { ApiProperty } from '@nestjs/swagger'; -export class OAuthAccessTokenEntity { +export class OAuthSessionEntity { @ApiProperty({ example: 'cklwi0vb70000z2z20q6f19qk', description: 'Unique ID of an OAuth token (auto-generated)', diff --git a/src/modules/oauth-sessions/dtos/update-oauth-sessions.dto.ts b/src/modules/oauth-sessions/dtos/update-oauth-sessions.dto.ts new file mode 100644 index 0000000..5a2abef --- /dev/null +++ b/src/modules/oauth-sessions/dtos/update-oauth-sessions.dto.ts @@ -0,0 +1,4 @@ +import { PartialType } from "@nestjs/swagger"; +import { CreateOauthSessionDto } from "./create-oauth-sessions.dto"; + +export class UpdateOauthSessionDto extends PartialType(CreateOauthSessionDto) {} diff --git a/src/modules/oauth-sessions/oauth-sessions.module.ts b/src/modules/oauth-sessions/oauth-sessions.module.ts new file mode 100644 index 0000000..e68a481 --- /dev/null +++ b/src/modules/oauth-sessions/oauth-sessions.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; +import { PrismaService } from 'src/prisma/prisma.service'; +import { OauthSessionsController } from './controllers/oauth-sessions.controller'; +import { OauthSessionsService } from './services/oauth-sessions.service'; + +@Module({ + controllers: [OauthSessionsController], + providers: [OauthSessionsService, PrismaService] +}) +export class OauthSessionsModule {} diff --git a/src/modules/oauth-access-tokens/services/oauth-access-tokens.service.ts b/src/modules/oauth-sessions/services/oauth-sessions.service.ts similarity index 67% rename from src/modules/oauth-access-tokens/services/oauth-access-tokens.service.ts rename to src/modules/oauth-sessions/services/oauth-sessions.service.ts index 7a7a6a5..353f128 100644 --- a/src/modules/oauth-access-tokens/services/oauth-access-tokens.service.ts +++ b/src/modules/oauth-sessions/services/oauth-sessions.service.ts @@ -1,30 +1,32 @@ import { Injectable, NotFoundException } from '@nestjs/common'; import { PrismaService } from 'src/prisma/prisma.service'; -import { CreateOauthAccessTokenDto } from '../dtos/create-oauth-access-token.dto'; -import { OAuthAccessTokens } from '@prisma/client'; -import { UpdateOauthAccessTokenDto } from '../dtos/update-oauth-access-token.dto'; +import { CreateOauthSessionDto } from '../dtos/create-oauth-sessions.dto'; +import { OAuthSessions } from '@prisma/client'; +import { UpdateOauthSessionDto } from '../dtos/update-oauth-sessions.dto'; @Injectable() -export class OauthAccessTokensService { +export class OauthSessionsService { constructor(private readonly prisma: PrismaService) {} - async create(dto: CreateOauthAccessTokenDto): Promise { + async create(dto: CreateOauthSessionDto): Promise { const { user_id, application, access_token, refresh_token, + sid, access_token_expiry, refresh_token_expiry, scopes, } = dto; - return this.prisma.oAuthAccessTokens.create({ + return this.prisma.oAuthSessions.create({ data: { user_id, application, access_token, refresh_token, + sid, access_token_expiry, refresh_token_expiry, scopes, @@ -33,14 +35,14 @@ export class OauthAccessTokensService { }); } - findAll(): Promise { - return this.prisma.oAuthAccessTokens.findMany({ + findAll(): Promise { + return this.prisma.oAuthSessions.findMany({ include: { user: true }, }); } - async findOne(id: string): Promise { - const token = await this.prisma.oAuthAccessTokens.findUnique({ + async findOne(id: string): Promise { + const token = await this.prisma.oAuthSessions.findUnique({ where: { id }, include: { user: true }, }); @@ -50,7 +52,7 @@ export class OauthAccessTokensService { return token; } - async update(id: string, dto: UpdateOauthAccessTokenDto): Promise { + async update(id: string, dto: UpdateOauthSessionDto): Promise { await this.findOne(id); const { user_id, @@ -62,7 +64,7 @@ export class OauthAccessTokensService { scopes, } = dto; - return this.prisma.oAuthAccessTokens.update({ + return this.prisma.oAuthSessions.update({ where: { id }, data: { ...(user_id !== undefined && { user_id }), @@ -77,8 +79,8 @@ export class OauthAccessTokensService { }); } - async remove(id: string): Promise { + async remove(id: string): Promise { await this.findOne(id); - return this.prisma.oAuthAccessTokens.delete({ where: { id }}); + return this.prisma.oAuthSessions.delete({ where: { id }}); } }