diff --git a/.env.local b/.env.local index 9cd74618..288ad4f9 100644 --- a/.env.local +++ b/.env.local @@ -10,4 +10,10 @@ DATABASE_URL="postgresql://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_ SECRET_KEY=batata HOST=::0.0.0.0 -PORT=4000 \ No newline at end of file +PORT=4000 + +# In hours +SHELTER_SUPPLY_PRIORITY_EXPIRY_INTERVAL=4 + +# Every 30 minutes +SHELTER_SUPPLY_PRIORITY_JOB_INTERVAL="0 */30 * * * *" \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 6c3ae692..b72af896 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@nestjs/passport": "^10.0.3", "@nestjs/platform-express": "^10.0.0", "@nestjs/platform-fastify": "^10.3.8", + "@nestjs/schedule": "^4.0.2", "@nestjs/swagger": "^7.3.1", "@prisma/client": "^5.13.0", "bcrypt": "^5.1.1", @@ -2100,6 +2101,19 @@ } } }, + "node_modules/@nestjs/schedule": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@nestjs/schedule/-/schedule-4.0.2.tgz", + "integrity": "sha512-po9oauE7fO0CjhDKvVC2tzEgjOUwhxYoIsXIVkgfu+xaDMmzzpmXY2s1LT4oP90Z+PaTtPoAHmhslnYmo4mSZg==", + "dependencies": { + "cron": "3.1.7", + "uuid": "9.0.1" + }, + "peerDependencies": { + "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", + "@nestjs/core": "^8.0.0 || ^9.0.0 || ^10.0.0" + } + }, "node_modules/@nestjs/schematics": { "version": "10.1.1", "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-10.1.1.tgz", @@ -2543,6 +2557,11 @@ "@types/node": "*" } }, + "node_modules/@types/luxon": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.4.2.tgz", + "integrity": "sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==" + }, "node_modules/@types/methods": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", @@ -4124,6 +4143,15 @@ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true }, + "node_modules/cron": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/cron/-/cron-3.1.7.tgz", + "integrity": "sha512-tlBg7ARsAMQLzgwqVxy8AZl/qlTc5nibqYwtNGoCrd+cV+ugI+tvZC1oT/8dFH8W455YrywGykx/KMmAqOr7Jw==", + "dependencies": { + "@types/luxon": "~3.4.0", + "luxon": "~3.4.0" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -7000,6 +7028,14 @@ "yallist": "^3.0.2" } }, + "node_modules/luxon": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz", + "integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==", + "engines": { + "node": ">=12" + } + }, "node_modules/magic-string": { "version": "0.30.5", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", @@ -9632,6 +9668,18 @@ "node": ">= 0.4.0" } }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", diff --git a/package.json b/package.json index 5e154b46..70abf4c3 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "@nestjs/passport": "^10.0.3", "@nestjs/platform-express": "^10.0.0", "@nestjs/platform-fastify": "^10.3.8", + "@nestjs/schedule": "^4.0.2", "@nestjs/swagger": "^7.3.1", "@prisma/client": "^5.13.0", "bcrypt": "^5.1.1", diff --git a/src/app.module.ts b/src/app.module.ts index 8b528767..a5f60a78 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,5 +1,6 @@ import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; import { APP_INTERCEPTOR } from '@nestjs/core'; +import { ScheduleModule } from '@nestjs/schedule'; import { PrismaModule } from './prisma/prisma.module'; import { ShelterModule } from './shelter/shelter.module'; @@ -15,6 +16,7 @@ import { ShelterSupplyModule } from './shelter-supply/shelter-supply.module'; @Module({ imports: [ PrismaModule, + ScheduleModule.forRoot(), UsersModule, SessionsModule, ShelterModule, diff --git a/src/shelter-supply/constants.ts b/src/shelter-supply/constants.ts new file mode 100644 index 00000000..32bab413 --- /dev/null +++ b/src/shelter-supply/constants.ts @@ -0,0 +1,8 @@ +const jobInterval: string = + process.env.SHELTER_SUPPLY_PRIORITY_JOB_INTERVAL ?? '0 */30 * * * *'; + +const priorityExpiryInterval: number = Number.parseInt( + process.env.SHELTER_SUPPLY_PRIORITY_EXPIRY_INTERVAL ?? '4', +); + +export { jobInterval, priorityExpiryInterval }; diff --git a/src/shelter-supply/shelter-supply-expiration.job.ts b/src/shelter-supply/shelter-supply-expiration.job.ts new file mode 100644 index 00000000..5d9cab1d --- /dev/null +++ b/src/shelter-supply/shelter-supply-expiration.job.ts @@ -0,0 +1,35 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { Cron } from '@nestjs/schedule'; +import { PrismaService } from '../prisma/prisma.service'; +import { addHours } from '@/utils'; +import { priorityExpiryInterval, jobInterval } from './constants'; +import { SupplyPriority } from '../supply/types'; +import { ShelterSupplyService } from './shelter-supply.service'; + +@Injectable() +export class ShelterSupplyExpirationJob { + constructor( + private readonly prismaService: PrismaService, + private readonly shelterSupplyService: ShelterSupplyService, + ) {} + + private readonly logger = new Logger(ShelterSupplyExpirationJob.name); + + @Cron(jobInterval) + async handleCron() { + this.logger.log(`${ShelterSupplyExpirationJob.name} running`); + + const expiryDate = addHours(new Date(Date.now()), -priorityExpiryInterval); + await this.prismaService.shelterSupply.updateMany({ + data: { + priority: SupplyPriority.Needing, + }, + where: { + priority: SupplyPriority.Urgent, + createdAt: { + lte: expiryDate, + }, + }, + }); + } +} diff --git a/src/shelter-supply/shelter-supply.module.ts b/src/shelter-supply/shelter-supply.module.ts index 0609a149..7601d5da 100644 --- a/src/shelter-supply/shelter-supply.module.ts +++ b/src/shelter-supply/shelter-supply.module.ts @@ -3,10 +3,11 @@ import { Module } from '@nestjs/common'; import { ShelterSupplyService } from './shelter-supply.service'; import { ShelterSupplyController } from './shelter-supply.controller'; import { PrismaModule } from '../prisma/prisma.module'; +import { ShelterSupplyExpirationJob } from './shelter-supply-expiration.job'; @Module({ imports: [PrismaModule], - providers: [ShelterSupplyService], + providers: [ShelterSupplyService, ShelterSupplyExpirationJob], controllers: [ShelterSupplyController], }) export class ShelterSupplyModule {} diff --git a/src/utils/index.ts b/src/utils/index.ts index 09934e74..d755ebeb 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -4,6 +4,7 @@ import { getSessionData, deepMerge, capitalize, + addHours, } from './utils'; export { @@ -12,4 +13,5 @@ export { removeNotNumbers, getSessionData, deepMerge, + addHours, }; diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 772b3e12..9fe32f0e 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -75,10 +75,17 @@ function deepMerge(target: Record, source: Record) { } } +function addHours(date: Date, hours: number) { + const result = new Date(); + result.setTime(date.getTime() + hours * 60 * 60 * 1000); + return result; +} + export { ServerResponse, removeNotNumbers, getSessionData, deepMerge, capitalize, + addHours, };