diff --git a/package-lock.json b/package-lock.json index 455fac11..e957faaa 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", @@ -2101,6 +2102,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", @@ -2544,6 +2558,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", @@ -4125,6 +4144,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", @@ -7026,6 +7054,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", @@ -9658,6 +9694,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 70a6153f..7a2ece26 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,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/prisma/migrations/20240521123041_create_supply_remove_log/migration.sql b/prisma/migrations/20240521123041_create_supply_remove_log/migration.sql new file mode 100644 index 00000000..c278aa68 --- /dev/null +++ b/prisma/migrations/20240521123041_create_supply_remove_log/migration.sql @@ -0,0 +1,15 @@ +-- CreateTable +CREATE TABLE "supply_auto_remove_logs" ( + "id" TEXT NOT NULL, + "supply_id" TEXT NOT NULL, + "shelter_id" TEXT NOT NULL, + "removed_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "supply_auto_remove_logs_pkey" PRIMARY KEY ("id") +); + +-- AddForeignKey +ALTER TABLE "supply_auto_remove_logs" ADD CONSTRAINT "supply_auto_remove_logs_shelter_id_fkey" FOREIGN KEY ("shelter_id") REFERENCES "shelters"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "supply_auto_remove_logs" ADD CONSTRAINT "supply_auto_remove_logs_supply_id_fkey" FOREIGN KEY ("supply_id") REFERENCES "supplies"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 04d0ee48..5f2dc82f 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -85,6 +85,7 @@ model Supply { supplyCategory SupplyCategory @relation(fields: [supplyCategoryId], references: [id]) shelterSupplies ShelterSupply[] + supplyAutoRemoveLogs SupplyAutoRemoveLog[] @@map("supplies") } @@ -114,6 +115,7 @@ model Shelter { shelterManagers ShelterManagers[] shelterSupplies ShelterSupply[] + supplyAutoRemoveLogs SupplyAutoRemoveLog[] @@map("shelters") } @@ -151,3 +153,18 @@ model Supporters { @@map("supporters") } + + +model SupplyAutoRemoveLog { + id String @id @default(uuid()) + supplyId String @map("supply_id") + shelterId String @map("shelter_id") + removedAt DateTime @default(value: now()) @map("removed_at") @db.Timestamp(6) + + + + shelter Shelter @relation(fields: [shelterId], references: [id]) + supply Supply @relation(fields: [supplyId], references: [id]) + + @@map("supply_auto_remove_logs") +} \ No newline at end of file diff --git a/src/app.module.ts b/src/app.module.ts index 82663d34..02350120 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -13,6 +13,7 @@ import { ShelterManagersModule } from './shelter-managers/shelter-managers.modul import { ShelterSupplyModule } from './shelter-supply/shelter-supply.module'; import { PartnersModule } from './partners/partners.module'; import { SupportersModule } from './supporters/supporters.module'; +import { ScheduleModule } from '@nestjs/schedule'; @Module({ imports: [ @@ -26,6 +27,7 @@ import { SupportersModule } from './supporters/supporters.module'; ShelterSupplyModule, PartnersModule, SupportersModule, + ScheduleModule.forRoot(), ], controllers: [], providers: [ diff --git a/src/shelter-supply/shelter-supply-cleanup.service.ts b/src/shelter-supply/shelter-supply-cleanup.service.ts new file mode 100644 index 00000000..419beacc --- /dev/null +++ b/src/shelter-supply/shelter-supply-cleanup.service.ts @@ -0,0 +1,88 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { Cron } from '@nestjs/schedule'; +import { ShelterSupply } from '@prisma/client'; +import { subDays } from 'date-fns'; +import { PrismaService } from 'src/prisma/prisma.service'; +import { SupplyPriority } from 'src/supply/types'; + +@Injectable() +export class ShelterSupplyCleanupService { + private logger = new Logger('ShelterSupplyCleanupService'); + constructor(private readonly prismaService: PrismaService) {} + + @Cron('0 0 */2 * *') + async handleCron() { + const shelterSuppliesToDelete = await this.getShelterSuppliesToDelete(); + + for (const shelterSupply of shelterSuppliesToDelete) { + this.logger.log( + `Verificando necessidade de exclusão de itens no abrigo ${shelterSupply.shelterId}`, + ); + + await this.removeShelterSupply(shelterSupply); + } + } + + private async getShelterSuppliesToDelete(): Promise { + const thresholdDate = subDays(new Date(), 2).toISOString(); + + return await this.prismaService.shelterSupply.findMany({ + where: { + OR: [ + { + createdAt: { + lte: thresholdDate, + }, + updatedAt: null, + }, + { + updatedAt: { + lte: thresholdDate, + }, + }, + ], + priority: { + not: SupplyPriority.Urgent, + }, + }, + }); + } + + private async removeShelterSupply( + shelterSupply: ShelterSupply, + ): Promise { + this.logger.log( + `Suprimento ${shelterSupply.supplyId} já está há 48 horas com baixa movimentação e não é urgente. Removendo relação com o abrigo ${shelterSupply.shelterId}`, + ); + + try { + await this.prismaService.$transaction([ + this.prismaService.shelterSupply.deleteMany({ + where: { + supplyId: shelterSupply.supplyId, + shelterId: shelterSupply.shelterId, + }, + }), + this.prismaService.supplyAutoRemoveLog.create({ + data: { + supply: { + connect: { + id: shelterSupply.supplyId, + }, + }, + shelter: { + connect: { + id: shelterSupply.shelterId, + }, + }, + }, + }), + ]); + } catch (error) { + this.logger.error( + `Erro ao tentar remover o suprimento ${shelterSupply.supplyId} do abrigo ${shelterSupply.shelterId}`, + (error as Error).stack, + ); + } + } +} diff --git a/src/shelter-supply/shelter-supply.module.ts b/src/shelter-supply/shelter-supply.module.ts index 0609a149..4775a120 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 { ShelterSupplyCleanupService } from './shelter-supply-cleanup.service'; @Module({ imports: [PrismaModule], - providers: [ShelterSupplyService], + providers: [ShelterSupplyService, ShelterSupplyCleanupService], controllers: [ShelterSupplyController], }) export class ShelterSupplyModule {}