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"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/bullmq": "^11.0.3",
|
||||
"@nestjs/common": "^11.0.1",
|
||||
"@nestjs/config": "^4.0.2",
|
||||
"@nestjs/core": "^11.0.1",
|
||||
"@nestjs/event-emitter": "^3.0.1",
|
||||
"@nestjs/jwt": "^11.0.0",
|
||||
"@nestjs/passport": "^11.0.5",
|
||||
"@nestjs/platform-express": "^11.0.1",
|
||||
"@nestjs/schedule": "^6.0.0",
|
||||
"@nestjs/swagger": "^11.2.0",
|
||||
"@prisma/client": "^6.11.1",
|
||||
"bullmq": "^5.56.9",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.2",
|
||||
"express-session": "^1.18.2",
|
||||
|
|
@ -41,7 +44,7 @@
|
|||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3.2.0",
|
||||
"@eslint/js": "^9.18.0",
|
||||
"@nestjs/cli": "^11.0.0",
|
||||
"@nestjs/cli": "^10.4.9",
|
||||
"@nestjs/schematics": "^11.0.0",
|
||||
"@nestjs/testing": "^11.0.1",
|
||||
"@swc/cli": "^0.6.0",
|
||||
|
|
@ -50,6 +53,7 @@
|
|||
"@types/express-session": "^1.18.2",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/node": "^22.10.7",
|
||||
"@types/nodemailer": "^6.4.17",
|
||||
"@types/passport-jwt": "^4.0.1",
|
||||
"@types/passport-openidconnect": "^0.1.3",
|
||||
"@types/supertest": "^6.0.2",
|
||||
|
|
|
|||
|
|
@ -1,45 +1,47 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
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 { ArchivalModule } from './modules/archival/archival.module';
|
||||
import { AuthenticationModule } from './modules/authentication/auth.module';
|
||||
import { BankCodesModule } from './modules/bank-codes/bank-codes.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 { 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 { 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 { TimesheetsModule } from './modules/timesheets/timesheets.module';
|
||||
import { AuthenticationModule } from './modules/authentication/auth.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';
|
||||
import { UsersModule } from './modules/users-management/users.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ScheduleModule.forRoot(),
|
||||
ArchivalModule,
|
||||
AuthenticationModule,
|
||||
BankCodesModule,
|
||||
BusinessLogicsModule,
|
||||
CsvExportModule,
|
||||
CustomersModule,
|
||||
EmployeesModule,
|
||||
ExpensesModule,
|
||||
HealthModule,
|
||||
LeaveRequestsModule,
|
||||
OauthSessionsModule,
|
||||
PayperiodsModule,
|
||||
PrismaModule,
|
||||
ShiftsModule,
|
||||
TimesheetsModule,
|
||||
UsersModule,
|
||||
ArchivalModule,
|
||||
AuthenticationModule,
|
||||
BankCodesModule,
|
||||
BusinessLogicsModule,
|
||||
CsvExportModule,
|
||||
CustomersModule,
|
||||
EmployeesModule,
|
||||
EventEmitterModule.forRoot(),
|
||||
ExpensesModule,
|
||||
HealthModule,
|
||||
LeaveRequestsModule,
|
||||
OauthSessionsModule,
|
||||
PayperiodsModule,
|
||||
PrismaModule,
|
||||
ScheduleModule.forRoot(),
|
||||
ShiftsModule,
|
||||
TimesheetsModule,
|
||||
UsersModule,
|
||||
],
|
||||
controllers: [AppController, HealthController],
|
||||
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