import { Injectable } from "@nestjs/common"; import { Cron } from "@nestjs/schedule"; import { startOfYear } from "src/time-and-attendance/attachments/cas.util"; import { PrismaService } from "src/prisma/prisma.service"; @Injectable() export class AttachmentArchivalService { private readonly batch_size = Number(process.env.ARCHIVE_BATCH_SIZE || 1000); private readonly cron_expression = process.env.ARCHIVE_CRON || '0 3 * * 1'; constructor(private readonly prisma: PrismaService) { } @Cron(function (this: AttachmentArchivalService) { return this.cron_expression; } as any) async runScheduled() { await this.archiveCutoffToStartOfYear(); } //archive everything before current year async archiveCutoffToStartOfYear() { const cutoff = startOfYear(); console.log(`Archival: cutoff=${cutoff.toISOString()} batch=${this.batch_size}`); let moved = 0, total = 0, i = 0; do { moved = await this.archiveBatch(cutoff, this.batch_size); total += moved; i++; if (moved > 0) console.log(`Batch #${i}: moved ${moved}`); } while (moved === this.batch_size); console.log(`Archival done: total moved : ${total}`); return { moved: total }; } //only moves table content to archive and not blobs. private async archiveBatch(cutoff: Date, batch_size: number): Promise { const moved = await this.prisma.$executeRaw ` WITH moved AS ( DELETE FROM "attachments" WHERE id IN ( SELECT id FROM "attachments" WHERE created_at < ${cutoff} ORDER BY id LIMIT ${batch_size} ) RETURNING id, sha256, owner_type, owner_id, original_name, status, retention_policy, created_by, created_at ) INSERT INTO archive.attachments_archive (id, sha256, owner_type, owner_id, original_name, status, retention_policy, created_by, created_at) SELECT * FROM moved;`; return Number(moved) || 0; } }