chore(repo) init AppSchedule module

This commit is contained in:
Francesco Spilla 2025-02-10 17:02:59 +01:00
parent ef50d7075f
commit 1b7c586b69
9 changed files with 169 additions and 8 deletions

View File

@ -8,6 +8,7 @@ import { EnumifyModule } from './modules/enumify/enumify.module';
import { AppFileTransactionsModule } from './modules/file-transactions/file-transactions.module';
import { AppHealthModule } from './modules/health/health.module';
import { AppLoggerModule } from './modules/logger/logger.module';
import { AppScheduleModule } from './modules/schedule/schedule.module';
@Module({
imports: [
@ -16,6 +17,7 @@ import { AppLoggerModule } from './modules/logger/logger.module';
AppDatabaseModule,
AppHealthModule,
AppFileTransactionsModule,
AppScheduleModule,
EnumifyModule,
],
controllers: [AppController],

View File

@ -1,12 +1,11 @@
import { Global, Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { DataSource } from 'typeorm';
import { DatabaseConfig, databaseConfig } from './database.config';
import { APP_DATASOURCES } from './database.constants';
import { typeormTransactionalDataSourceFactory } from './utils/typeorm-data-source-factory';
import { typeormEntitiesFromImport } from './utils/typeorm-import-entities';
import { typeormModuleOptionsFactory } from './utils/typeorm-module-options-factory';
import { ConfigModule } from '@nestjs/config';
const dataSources: DataSource[] = [];
@ -15,10 +14,7 @@ const typeormModules = [
imports: databaseConfig.asProvider().imports,
dataSourceFactory: typeormTransactionalDataSourceFactory(),
useFactory: async (dbConfig: DatabaseConfig) => {
// const entities = await import('./path/to/entities');
const entities = {};
const config = await typeormModuleOptionsFactory(dbConfig, [typeormEntitiesFromImport(entities)]);
const config = await typeormModuleOptionsFactory(dbConfig);
return config;
},
inject: [databaseConfig.KEY],

View File

@ -0,0 +1 @@
export { TipiJobsEntity } from './tipi_jobs.entity';

View File

@ -0,0 +1,50 @@
import { Column, Entity, Index } from 'typeorm';
import { Enumify } from '../../enumify/enumify';
export class InviaMailSeErrori {
@Column('bit', { name: 'flag', default: () => '(0)' })
flagAttivo: boolean;
@Column('varchar', { name: 'oggetto', nullable: true, length: 255 })
oggetto: string | null;
@Column('varchar', { name: 'destinatari', nullable: true, length: 255 })
destinatari: string | null;
@Column('varchar', { name: 'cc', nullable: true, length: 255 })
cc: string | null;
}
@Entity('tipi_jobs')
export class TipiJobsEntity extends Enumify {
static _ = TipiJobsEntity.closeEnum();
// Columns
@Column('int', { primary: true, name: 'id' })
id: number;
@Column('varchar', { name: 'nome', length: 50 })
nome: string;
@Column('varchar', { name: 'descrizione', nullable: true, length: 255 })
descrizione: string | null;
@Column(() => InviaMailSeErrori, { prefix: 'invia_mail_se_errori'})
inviaMailSeErrori: InviaMailSeErrori;
@Column('bit', { name: 'flag_attivo', default: () => '(0)' })
flagAttivo: boolean;
@Column('varchar', { name: 'pattern_cron', length: 20 })
patternCron: string;
public constructor(
id: number,
nome: string,
patternCron: string,
) {
super(id, nome);
this.patternCron = patternCron;
}
}

View File

@ -1,9 +1,9 @@
import { TypeOrmModuleOptions } from '@nestjs/typeorm';
import { DatabaseConfig } from '../database.config';
import { SnakeNamingStrategy } from 'typeorm-naming-strategies';
export const typeormModuleOptionsFactory = async (
databaseConfig: DatabaseConfig,
entities: any[],
name?: string
): Promise<TypeOrmModuleOptions> => {
return {
@ -14,9 +14,10 @@ export const typeormModuleOptionsFactory = async (
username: databaseConfig.username,
password: databaseConfig.password,
database: databaseConfig.database,
entities: [...entities],
autoLoadEntities: true,
synchronize: false,
logging: process.env['NODE_ENV'] !== 'production',
namingStrategy: new SnakeNamingStrategy(),
options: {
...(!databaseConfig.secure ? { trustServerCertificate: true } : {}),
},

View File

@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { ScheduleModule } from '@nestjs/schedule';
import { TypeOrmModule } from '@nestjs/typeorm';
import { TipiJobsEntity } from '../database/entities';
import { SchedulerService } from './schedule.service';
@Module({
imports: [TypeOrmModule.forFeature([TipiJobsEntity]), ScheduleModule.forRoot()],
providers: [SchedulerService],
exports: [],
})
export class AppScheduleModule {}

View File

@ -0,0 +1,52 @@
import { Injectable, Logger } from '@nestjs/common';
import { SchedulerRegistry } from '@nestjs/schedule';
import { InjectRepository } from '@nestjs/typeorm';
import { CronJob, CronTime } from 'cron';
import { Repository } from 'typeorm';
import { TipiJobsEntity } from '../database/entities/tipi_jobs.entity';
@Injectable()
export class SchedulerService {
private readonly logger = new Logger(SchedulerService.name);
private static readonly TIME_ZONE = 'Europe/Rome';
constructor(
private readonly schedulerRegistry: SchedulerRegistry,
@InjectRepository(TipiJobsEntity) private readonly tipiJobsRepository: Repository<TipiJobsEntity>
) {}
async onApplicationBootstrap() {
try {
const jobs = this.schedulerRegistry.getCronJobs();
const jobTypes = await this.tipiJobsRepository.find();
for (const [jobName, job] of jobs) {
this.rescheduleJobAccordingToJobType(jobTypes, jobName, job);
}
} catch (err) {
this.logger.error(err.message);
this.logger.warn(`Error while retrieving scheduled jobs on database. Skipping jobs.`);
}
}
private rescheduleJobAccordingToJobType(jobTypes: TipiJobsEntity[], jobName: string, job: CronJob) {
const jobType = jobTypes.find((jobType) => jobType.nome == jobName);
if (!jobType) {
this.logger.warn(`Job type for job '${jobName}' not found on database. Skipping job.`);
this.schedulerRegistry.deleteCronJob(jobName);
return;
}
const jobTime = new CronTime(jobType.patternCron, SchedulerService.TIME_ZONE);
job.setTime(jobTime);
job.start();
this.logger.log(
`Job type id '${jobType.id}' found for job '${jobName}' [isActive: ${jobType.flagAttivo}, cronPattern: ${
jobType.patternCron
}]. Upcoming date => ${job.nextDate()}`
);
}
}

View File

@ -18,12 +18,14 @@
"@nestjs/core": "^11.0.8",
"@nestjs/platform-express": "^11.0.8",
"@nestjs/platform-fastify": "^11.0.8",
"@nestjs/schedule": "^5.0.1",
"@nestjs/terminus": "^11.0.0",
"@nestjs/typeorm": "^11.0.0",
"@nx/devkit": "20.4.2",
"axios": "^1.7.9",
"cacheable": "^1.8.8",
"connection-string": "^4.4.0",
"cron": "^3.5.0",
"dayjs": "^1.11.13",
"flatted": "^3.3.2",
"lodash": "^4.17.21",
@ -34,6 +36,7 @@
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1",
"typeorm": "^0.3.20",
"typeorm-naming-strategies": "^4.1.0",
"typeorm-scoped": "^1.2.0",
"typeorm-transactional": "^0.5.0",
"zod": "^3.24.1"

44
pnpm-lock.yaml generated
View File

@ -29,6 +29,9 @@ dependencies:
'@nestjs/platform-fastify':
specifier: ^11.0.8
version: 11.0.8(@nestjs/common@11.0.8)(@nestjs/core@11.0.8)
'@nestjs/schedule':
specifier: ^5.0.1
version: 5.0.1(@nestjs/common@11.0.8)(@nestjs/core@11.0.8)
'@nestjs/terminus':
specifier: ^11.0.0
version: 11.0.0(@nestjs/common@11.0.8)(@nestjs/core@11.0.8)(@nestjs/typeorm@11.0.0)(reflect-metadata@0.2.2)(rxjs@7.8.1)(typeorm@0.3.20)
@ -47,6 +50,9 @@ dependencies:
connection-string:
specifier: ^4.4.0
version: 4.4.0
cron:
specifier: ^3.5.0
version: 3.5.0
dayjs:
specifier: ^1.11.13
version: 1.11.13
@ -77,6 +83,9 @@ dependencies:
typeorm:
specifier: ^0.3.20
version: 0.3.20(mssql@11.0.1)(ts-node@10.9.2)
typeorm-naming-strategies:
specifier: ^4.1.0
version: 4.1.0(typeorm@0.3.20)
typeorm-scoped:
specifier: ^1.2.0
version: 1.2.0(typeorm@0.3.20)
@ -2524,6 +2533,17 @@ packages:
tslib: 2.8.1
dev: false
/@nestjs/schedule@5.0.1(@nestjs/common@11.0.8)(@nestjs/core@11.0.8):
resolution: {integrity: sha512-kFoel84I4RyS2LNPH6yHYTKxB16tb3auAEciFuc788C3ph6nABkUfzX5IE+unjVaRX+3GuruJwurNepMlHXpQg==}
peerDependencies:
'@nestjs/common': ^10.0.0 || ^11.0.0
'@nestjs/core': ^10.0.0 || ^11.0.0
dependencies:
'@nestjs/common': 11.0.8(reflect-metadata@0.2.2)(rxjs@7.8.1)
'@nestjs/core': 11.0.8(@nestjs/common@11.0.8)(@nestjs/platform-express@11.0.8)(reflect-metadata@0.2.2)(rxjs@7.8.1)
cron: 3.5.0
dev: false
/@nestjs/schematics@11.0.0(typescript@5.7.3):
resolution: {integrity: sha512-wts8lG0GfNWw3Wk9aaG5I/wcMIAdm7HjjeThQfUZhJxeIFT82Z3F5+0cYdHH4ii2pYQGiCSrR1VcuMwPiHoecg==}
peerDependencies:
@ -3794,6 +3814,10 @@ packages:
resolution: {integrity: sha512-w/P33JFeySuhN6JLkysYUK2gEmy9kHHFN7E8ro0tkfmlDOgxBDzWEZ/J8cWA+fHqFevpswDTFZnDx+R9lbL6xw==}
dev: true
/@types/luxon@3.4.2:
resolution: {integrity: sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==}
dev: false
/@types/mime@1.3.5:
resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
dev: true
@ -5308,6 +5332,13 @@ packages:
/create-require@1.1.1:
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
/cron@3.5.0:
resolution: {integrity: sha512-0eYZqCnapmxYcV06uktql93wNWdlTmmBFP2iYz+JPVcQqlyFYcn1lFuIk4R54pkOmE7mcldTAPZv6X5XA4Q46A==}
dependencies:
'@types/luxon': 3.4.2
luxon: 3.5.0
dev: false
/cross-spawn@5.1.0:
resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==}
dependencies:
@ -8082,6 +8113,11 @@ packages:
yallist: 3.1.1
dev: true
/luxon@3.5.0:
resolution: {integrity: sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==}
engines: {node: '>=12'}
dev: false
/magic-string@0.30.0:
resolution: {integrity: sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==}
engines: {node: '>=12'}
@ -10875,6 +10911,14 @@ packages:
- typeorm-aurora-data-api-driver
dev: true
/typeorm-naming-strategies@4.1.0(typeorm@0.3.20):
resolution: {integrity: sha512-vPekJXzZOTZrdDvTl1YoM+w+sUIfQHG4kZTpbFYoTsufyv9NIBRe4Q+PdzhEAFA2std3D9LZHEb1EjE9zhRpiQ==}
peerDependencies:
typeorm: ^0.2.0 || ^0.3.0
dependencies:
typeorm: 0.3.20(mssql@11.0.1)(ts-node@10.9.2)
dev: false
/typeorm-scoped@1.2.0(typeorm@0.3.20):
resolution: {integrity: sha512-fVZUFIAHCib6Sq/k1wGzwtnFb9uP+g/hLDaoosczJpSNAG2YA2CyPR5J3+UuTz11kvdAfQO0Udk+6rL8xX2/Wg==}
peerDependencies: