feat(notify): setup notifications packages
This commit is contained in:
parent
13a3ccb292
commit
1a0f8f8b0a
1898
package-lock.json
generated
1898
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
|
@ -20,15 +20,18 @@
|
||||||
"test:e2e": "jest --config ./test/jest-e2e.json"
|
"test:e2e": "jest --config ./test/jest-e2e.json"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@nestjs/bullmq": "^11.0.3",
|
||||||
"@nestjs/common": "^11.0.1",
|
"@nestjs/common": "^11.0.1",
|
||||||
"@nestjs/config": "^4.0.2",
|
"@nestjs/config": "^4.0.2",
|
||||||
"@nestjs/core": "^11.0.1",
|
"@nestjs/core": "^11.0.1",
|
||||||
|
"@nestjs/event-emitter": "^3.0.1",
|
||||||
"@nestjs/jwt": "^11.0.0",
|
"@nestjs/jwt": "^11.0.0",
|
||||||
"@nestjs/passport": "^11.0.5",
|
"@nestjs/passport": "^11.0.5",
|
||||||
"@nestjs/platform-express": "^11.0.1",
|
"@nestjs/platform-express": "^11.0.1",
|
||||||
"@nestjs/schedule": "^6.0.0",
|
"@nestjs/schedule": "^6.0.0",
|
||||||
"@nestjs/swagger": "^11.2.0",
|
"@nestjs/swagger": "^11.2.0",
|
||||||
"@prisma/client": "^6.11.1",
|
"@prisma/client": "^6.11.1",
|
||||||
|
"bullmq": "^5.56.9",
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"class-validator": "^0.14.2",
|
"class-validator": "^0.14.2",
|
||||||
"express-session": "^1.18.2",
|
"express-session": "^1.18.2",
|
||||||
|
|
@ -41,7 +44,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3.2.0",
|
"@eslint/eslintrc": "^3.2.0",
|
||||||
"@eslint/js": "^9.18.0",
|
"@eslint/js": "^9.18.0",
|
||||||
"@nestjs/cli": "^11.0.0",
|
"@nestjs/cli": "^10.4.9",
|
||||||
"@nestjs/schematics": "^11.0.0",
|
"@nestjs/schematics": "^11.0.0",
|
||||||
"@nestjs/testing": "^11.0.1",
|
"@nestjs/testing": "^11.0.1",
|
||||||
"@swc/cli": "^0.6.0",
|
"@swc/cli": "^0.6.0",
|
||||||
|
|
@ -50,6 +53,7 @@
|
||||||
"@types/express-session": "^1.18.2",
|
"@types/express-session": "^1.18.2",
|
||||||
"@types/jest": "^29.5.14",
|
"@types/jest": "^29.5.14",
|
||||||
"@types/node": "^22.10.7",
|
"@types/node": "^22.10.7",
|
||||||
|
"@types/nodemailer": "^6.4.17",
|
||||||
"@types/passport-jwt": "^4.0.1",
|
"@types/passport-jwt": "^4.0.1",
|
||||||
"@types/passport-openidconnect": "^0.1.3",
|
"@types/passport-openidconnect": "^0.1.3",
|
||||||
"@types/supertest": "^6.0.2",
|
"@types/supertest": "^6.0.2",
|
||||||
|
|
|
||||||
|
|
@ -1,45 +1,47 @@
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { AppController } from './app.controller';
|
import { AppController } from './app.controller';
|
||||||
import { AppService } from './app.service';
|
import { AppService } from './app.service';
|
||||||
import { PrismaModule } from './prisma/prisma.module';
|
import { ArchivalModule } from './modules/archival/archival.module';
|
||||||
import { HealthModule } from './health/health.module';
|
import { AuthenticationModule } from './modules/authentication/auth.module';
|
||||||
import { HealthController } from './health/health.controller';
|
import { BankCodesModule } from './modules/bank-codes/bank-codes.module';
|
||||||
import { UsersModule } from './modules/users-management/users.module';
|
import { BusinessLogicsModule } from './modules/business-logics/business-logics.module';
|
||||||
|
import { CsvExportModule } from './modules/exports/csv-exports.module';
|
||||||
import { CustomersModule } from './modules/customers/customers.module';
|
import { CustomersModule } from './modules/customers/customers.module';
|
||||||
import { EmployeesModule } from './modules/employees/employees.module';
|
import { EmployeesModule } from './modules/employees/employees.module';
|
||||||
|
import { EventEmitterModule } from '@nestjs/event-emitter';
|
||||||
|
import { ExpensesModule } from './modules/expenses/expenses.module';
|
||||||
|
import { HealthModule } from './health/health.module';
|
||||||
|
import { HealthController } from './health/health.controller';
|
||||||
import { LeaveRequestsModule } from './modules/leave-requests/leave-requests.module';
|
import { LeaveRequestsModule } from './modules/leave-requests/leave-requests.module';
|
||||||
|
import { OauthSessionsModule } from './modules/oauth-sessions/oauth-sessions.module';
|
||||||
|
import { OvertimeService } from './modules/business-logics/services/overtime.service';
|
||||||
|
import { PayperiodsModule } from './modules/pay-periods/pay-periods.module';
|
||||||
|
import { PrismaModule } from './prisma/prisma.module';
|
||||||
|
import { ScheduleModule } from '@nestjs/schedule';
|
||||||
import { ShiftsModule } from './modules/shifts/shifts.module';
|
import { ShiftsModule } from './modules/shifts/shifts.module';
|
||||||
import { TimesheetsModule } from './modules/timesheets/timesheets.module';
|
import { TimesheetsModule } from './modules/timesheets/timesheets.module';
|
||||||
import { AuthenticationModule } from './modules/authentication/auth.module';
|
import { UsersModule } from './modules/users-management/users.module';
|
||||||
import { ExpensesModule } from './modules/expenses/expenses.module';
|
|
||||||
import { PayperiodsModule } from './modules/pay-periods/pay-periods.module';
|
|
||||||
import { ScheduleModule } from '@nestjs/schedule';
|
|
||||||
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 { OauthSessionsModule } from './modules/oauth-sessions/oauth-sessions.module';
|
|
||||||
import { CsvExportModule } from './modules/exports/csv-exports.module';
|
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
ScheduleModule.forRoot(),
|
ArchivalModule,
|
||||||
ArchivalModule,
|
AuthenticationModule,
|
||||||
AuthenticationModule,
|
BankCodesModule,
|
||||||
BankCodesModule,
|
BusinessLogicsModule,
|
||||||
BusinessLogicsModule,
|
CsvExportModule,
|
||||||
CsvExportModule,
|
CustomersModule,
|
||||||
CustomersModule,
|
EmployeesModule,
|
||||||
EmployeesModule,
|
EventEmitterModule.forRoot(),
|
||||||
ExpensesModule,
|
ExpensesModule,
|
||||||
HealthModule,
|
HealthModule,
|
||||||
LeaveRequestsModule,
|
LeaveRequestsModule,
|
||||||
OauthSessionsModule,
|
OauthSessionsModule,
|
||||||
PayperiodsModule,
|
PayperiodsModule,
|
||||||
PrismaModule,
|
PrismaModule,
|
||||||
ShiftsModule,
|
ScheduleModule.forRoot(),
|
||||||
TimesheetsModule,
|
ShiftsModule,
|
||||||
UsersModule,
|
TimesheetsModule,
|
||||||
|
UsersModule,
|
||||||
],
|
],
|
||||||
controllers: [AppController, HealthController],
|
controllers: [AppController, HealthController],
|
||||||
providers: [AppService, OvertimeService],
|
providers: [AppService, OvertimeService],
|
||||||
|
|
|
||||||
0
src/modules/notifications/notifications.module.ts
Normal file
0
src/modules/notifications/notifications.module.ts
Normal file
73
src/modules/notifications/notifications.service.ts
Normal file
73
src/modules/notifications/notifications.service.ts
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
import { InjectQueue } from "@nestjs/bullmq";
|
||||||
|
import { Injectable, Logger } from "@nestjs/common";
|
||||||
|
import { Queue } from "bullmq";
|
||||||
|
import { TimesheetsService } from "../timesheets/services/timesheets.service";
|
||||||
|
import { ShiftsService } from "../shifts/services/shifts.service";
|
||||||
|
import { ExpensesService } from "../expenses/services/expenses.service";
|
||||||
|
import { EmployeesService } from "../employees/services/employees.service";
|
||||||
|
import { LeaveRequestsService } from "../leave-requests/services/leave-requests.service";
|
||||||
|
|
||||||
|
export interface DigestItem {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
link?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class NotificationsService {
|
||||||
|
private readonly logger = new Logger(NotificationsService.name);
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@InjectQueue('notifications') private readonly queue: Queue,
|
||||||
|
private readonly timesheetsService : TimesheetsService,
|
||||||
|
private readonly shiftsService : ShiftsService,
|
||||||
|
private readonly expensesService : ExpensesService,
|
||||||
|
private readonly employeesService : EmployeesService,
|
||||||
|
private readonly leaveRequestsService: LeaveRequestsService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async queueNotification(channel: string, payload: Record<string,any>): Promise<void> {
|
||||||
|
await this.queue.add(channel, payload);
|
||||||
|
this.logger.debug(`Enqueued notification on channel= "${channel}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async buildWeeklyDigest(): Promise<{recipients: string[], items: DigestItem[]}> {
|
||||||
|
//TO DO add logic of missing shifts, overtime alert, vacation alerts, leave-requests, etc...
|
||||||
|
//fetching all business datas
|
||||||
|
//const missingShifts = await this.timesheetsService.findMissingShftsLastWeek();
|
||||||
|
|
||||||
|
const items: DigestItem[] = [
|
||||||
|
//example:
|
||||||
|
{title: 'Carte de temps incomplete', description: 'Des employes n`ont pas saisi leurs quarts de travail'},
|
||||||
|
{title: 'Overtime détecté', description: '....'},
|
||||||
|
];
|
||||||
|
|
||||||
|
const recipients = [
|
||||||
|
//exemple : await this.userService.findSupervisorsEmails();
|
||||||
|
'supervisor@targointernet.com',
|
||||||
|
'accounting@targointernet.com',
|
||||||
|
];
|
||||||
|
|
||||||
|
return {recipients, items}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async buildMonthlyDigest(): Promise<{ recipients: string[], items: DigestItem[]}> {
|
||||||
|
//const anniversaries = await this.employeesService.findAnniversariesthisMonth();
|
||||||
|
//const totalOvertime = await this.timesheetsService.calculateTotalOvertimeThisMonth();
|
||||||
|
|
||||||
|
const items: DigestItem[] = [
|
||||||
|
{title: '5 ans d`ancienneté', description:'Marc-André, Jessy'},
|
||||||
|
{title: '10 ans d`ancienneté', description:'Kadi, Maxime'},
|
||||||
|
{title: 'Calendrier Annuel', description: 'Nouveau calendrier de l`an prochain maintenant disponible!'},
|
||||||
|
// ...
|
||||||
|
];
|
||||||
|
|
||||||
|
const recipients = [
|
||||||
|
'allemployees@targointernent.com',
|
||||||
|
];
|
||||||
|
|
||||||
|
return { recipients, items };
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
94
src/modules/notifications/notifications.tasks.service.ts
Normal file
94
src/modules/notifications/notifications.tasks.service.ts
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
import { Injectable, Logger } from "@nestjs/common";
|
||||||
|
import { Cron, Interval, SchedulerRegistry } from "@nestjs/schedule";
|
||||||
|
import { NotificationsService as Orchestrator } from './notifications.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class NotificationsService {
|
||||||
|
private readonly logger = new Logger(NotificationsService.name);
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly schedulerRegistry: SchedulerRegistry,
|
||||||
|
private readonly orchestrator: Orchestrator,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
//cache purging
|
||||||
|
//@TimeOut(15_000)
|
||||||
|
async onStartup() {
|
||||||
|
this.logger.debug('Startup cleanup: initial verifications');
|
||||||
|
//clean up of useless cache on start up
|
||||||
|
}
|
||||||
|
|
||||||
|
//Q monitoring
|
||||||
|
@Interval(300_000)
|
||||||
|
async monitorQueueHealth() {
|
||||||
|
this.logger.debug('monitoring notification queue')
|
||||||
|
//this.orchestrator.checkQueueLength();
|
||||||
|
//monitor backlog for overload and such
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//weekly cron jobs
|
||||||
|
@Cron('0 0 8 * * 1', {
|
||||||
|
name: 'weeklyDigest',
|
||||||
|
timeZone: 'America/Toronto',
|
||||||
|
})
|
||||||
|
async sendWeeklyDigest() {
|
||||||
|
this.logger.debug('Building Weekly digest');
|
||||||
|
const { recipients, items } = await this.orchestrator.buildWeeklyDigest();
|
||||||
|
await this.orchestrator.queueNotification('email', {
|
||||||
|
to: recipients,
|
||||||
|
subject: '[Journal Hebdo] Sommaire de la semaine dernière',
|
||||||
|
template: 'weekly-digest',
|
||||||
|
context: { items },
|
||||||
|
});
|
||||||
|
this.logger.debug('Weekly digest Queued');
|
||||||
|
}
|
||||||
|
|
||||||
|
async disableWeeklyDigest() {
|
||||||
|
const job = this.schedulerRegistry.getCronJob('weeklyDigest');
|
||||||
|
job.stop();
|
||||||
|
this.logger.debug(`Weekly digest stopped`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async enableWeeklyDigest() {
|
||||||
|
const job = this.schedulerRegistry.getCronJob('weeklyDigest');
|
||||||
|
job.start();
|
||||||
|
this.logger.debug(`Weekly digest started`);
|
||||||
|
}
|
||||||
|
|
||||||
|
//monthly cron jobs
|
||||||
|
@Cron('0 0 9 1 * *', {
|
||||||
|
name: 'monthlyDigest',
|
||||||
|
timeZone: 'America/Toronto',
|
||||||
|
})
|
||||||
|
async sendMonthlyDigest() {
|
||||||
|
this.logger.debug('Building Monthly digest');
|
||||||
|
const {recipients, items} = await this.orchestrator.buildMonthlyDigest();
|
||||||
|
await this.orchestrator.queueNotification('email', {
|
||||||
|
to: recipients,
|
||||||
|
subject: '[Journal Mensuel] Sommaire du mois',
|
||||||
|
template: 'monthly-digest',
|
||||||
|
context: { items },
|
||||||
|
|
||||||
|
})
|
||||||
|
this.logger.debug('Monthly digest queued');
|
||||||
|
}
|
||||||
|
|
||||||
|
async disableMonthlyDigest() {
|
||||||
|
const job = this.schedulerRegistry.getCronJob('monthlyDigest');
|
||||||
|
job.stop();
|
||||||
|
this.logger.debug(`Monthly digest stopped`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async enableMonthlyDigest() {
|
||||||
|
const job = this.schedulerRegistry.getCronJob('monthlyDigest');
|
||||||
|
job.start();
|
||||||
|
this.logger.debug(`Monthly digest stopped`);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function TimeOut(arg0: number): (target: Orchestrator, propertyKey: "onStartup", descriptor: TypedPropertyDescriptor<() => Promise<void>>) => void | TypedPropertyDescriptor<() => Promise<void>> {
|
||||||
|
throw new Error("Function not implemented.");
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user