From 8909629d8ff8926676a8ef8b483d0284cbe97e8e Mon Sep 17 00:00:00 2001 From: admin Date: Tue, 2 Jun 2026 12:45:57 +0700 Subject: [PATCH] 690602:1245 ADR-033-233 #02 --- .../workers/cleanup-temp-files.worker.spec.ts | 57 +++++++++++++++++++ .../ai/workers/cleanup-temp-files.worker.ts | 47 ++++++++++++--- ...-id-to-migration-review-queue.rollback.sql | 11 ++++ ...ttachment-id-to-migration-review-queue.sql | 16 ++++++ .../lcbp3-v1.9.0-schema-02-tables.sql | 1 + 5 files changed, 125 insertions(+), 7 deletions(-) create mode 100644 backend/src/modules/ai/workers/cleanup-temp-files.worker.spec.ts create mode 100644 specs/03-Data-and-Storage/deltas/2026-06-02-add-temp-attachment-id-to-migration-review-queue.rollback.sql create mode 100644 specs/03-Data-and-Storage/deltas/2026-06-02-add-temp-attachment-id-to-migration-review-queue.sql diff --git a/backend/src/modules/ai/workers/cleanup-temp-files.worker.spec.ts b/backend/src/modules/ai/workers/cleanup-temp-files.worker.spec.ts new file mode 100644 index 00000000..b9a0da9b --- /dev/null +++ b/backend/src/modules/ai/workers/cleanup-temp-files.worker.spec.ts @@ -0,0 +1,57 @@ +// File: src/modules/ai/workers/cleanup-temp-files.worker.spec.ts +// Change Log: +// - 2026-06-02: เพิ่ม regression test สำหรับ schema drift ของ temp_attachment_id + +import { CleanupTempFilesWorker } from './cleanup-temp-files.worker'; +import { MigrationReviewStatus } from '../../migration/entities/migration-review-queue.entity'; + +describe('CleanupTempFilesWorker', () => { + const reviewQueueRepository = { + find: jest.fn(), + }; + const attachmentQueryBuilder = { + where: jest.fn().mockReturnThis(), + andWhere: jest.fn().mockReturnThis(), + getMany: jest.fn(), + }; + const attachmentRepository = { + createQueryBuilder: jest.fn().mockReturnValue(attachmentQueryBuilder), + remove: jest.fn(), + }; + + let worker: CleanupTempFilesWorker; + + beforeEach(() => { + jest.clearAllMocks(); + attachmentQueryBuilder.where.mockReturnThis(); + attachmentQueryBuilder.andWhere.mockReturnThis(); + worker = new CleanupTempFilesWorker( + attachmentRepository as never, + reviewQueueRepository as never + ); + }); + + it('should skip cleanup safely when temp_attachment_id column is missing', async () => { + reviewQueueRepository.find.mockRejectedValue( + new Error( + "Unknown column 'MigrationReviewQueue.temp_attachment_id' in 'SELECT'" + ) + ); + const warnSpy = jest.spyOn(worker['logger'], 'warn'); + const errorSpy = jest.spyOn(worker['logger'], 'error'); + + await worker.handleCleanup(); + + expect(reviewQueueRepository.find).toHaveBeenCalledWith({ + select: ['tempAttachmentId'], + where: { status: MigrationReviewStatus.PENDING }, + }); + expect(attachmentRepository.createQueryBuilder).not.toHaveBeenCalled(); + expect(warnSpy).toHaveBeenCalledWith( + expect.stringContaining( + 'migration_review_queue.temp_attachment_id is missing' + ) + ); + expect(errorSpy).not.toHaveBeenCalled(); + }); +}); diff --git a/backend/src/modules/ai/workers/cleanup-temp-files.worker.ts b/backend/src/modules/ai/workers/cleanup-temp-files.worker.ts index 65d6e4fc..a462812f 100644 --- a/backend/src/modules/ai/workers/cleanup-temp-files.worker.ts +++ b/backend/src/modules/ai/workers/cleanup-temp-files.worker.ts @@ -1,6 +1,7 @@ // File: src/modules/ai/workers/cleanup-temp-files.worker.ts // Change Log: // - 2026-05-22: อัปเดตและสร้างตัวล้างไฟล์ชั่วคราว (T016) เพื่อลบไฟล์ที่หมดอายุ 24 ชม. +// - 2026-06-02: ข้าม cleanup อย่างปลอดภัยเมื่อ schema migration_review_queue ยังไม่มี temp_attachment_id import { Injectable, Logger } from '@nestjs/common'; import { Cron, CronExpression } from '@nestjs/schedule'; @@ -16,6 +17,8 @@ import { @Injectable() export class CleanupTempFilesWorker { private readonly logger = new Logger(CleanupTempFilesWorker.name); + private static readonly MISSING_TEMP_ATTACHMENT_COLUMN_MESSAGE = + 'temp_attachment_id'; constructor( @InjectRepository(Attachment) @@ -34,13 +37,10 @@ export class CleanupTempFilesWorker { try { const oneDayAgo = new Date(); oneDayAgo.setHours(oneDayAgo.getHours() - 24); - const pendingRecords = await this.reviewQueueRepository.find({ - select: ['tempAttachmentId'], - where: { status: MigrationReviewStatus.PENDING }, - }); - const pendingAttachmentIds = pendingRecords - .map((r) => r.tempAttachmentId) - .filter((id): id is number => id !== undefined && id !== null); + const pendingAttachmentIds = await this.getPendingAttachmentIds(); + if (pendingAttachmentIds === null) { + return; + } const query = this.attachmentRepository .createQueryBuilder('attachment') .where('attachment.isTemporary = :isTemporary', { isTemporary: true }) @@ -85,4 +85,37 @@ export class CleanupTempFilesWorker { ); } } + + /** + * อ่านรายการ temp attachment ที่ยังถูกใช้งานโดย migration review status=PENDING + * ถ้า schema ยังไม่พร้อม ให้ข้าม cleanup รอบนี้เพื่อป้องกันการลบไฟล์ที่ยังถูกอ้างอิง + */ + private async getPendingAttachmentIds(): Promise { + try { + const pendingRecords = await this.reviewQueueRepository.find({ + select: ['tempAttachmentId'], + where: { status: MigrationReviewStatus.PENDING }, + }); + return pendingRecords + .map((record) => record.tempAttachmentId) + .filter((id): id is number => id !== undefined && id !== null); + } catch (error) { + if (this.isMissingTempAttachmentIdColumnError(error)) { + this.logger.warn( + 'Skipping temporary files cleanup because migration_review_queue.temp_attachment_id is missing. Apply delta specs/03-Data-and-Storage/deltas/2026-06-02-add-temp-attachment-id-to-migration-review-queue.sql first.' + ); + return null; + } + throw error; + } + } + + private isMissingTempAttachmentIdColumnError(error: unknown): boolean { + if (!(error instanceof Error)) { + return false; + } + return error.message.includes( + CleanupTempFilesWorker.MISSING_TEMP_ATTACHMENT_COLUMN_MESSAGE + ); + } } diff --git a/specs/03-Data-and-Storage/deltas/2026-06-02-add-temp-attachment-id-to-migration-review-queue.rollback.sql b/specs/03-Data-and-Storage/deltas/2026-06-02-add-temp-attachment-id-to-migration-review-queue.rollback.sql new file mode 100644 index 00000000..cecd8dce --- /dev/null +++ b/specs/03-Data-and-Storage/deltas/2026-06-02-add-temp-attachment-id-to-migration-review-queue.rollback.sql @@ -0,0 +1,11 @@ +-- File: specs/03-Data-and-Storage/deltas/2026-06-02-add-temp-attachment-id-to-migration-review-queue.rollback.sql +-- Change Log: +-- - 2026-06-02: ลบคอลัมน์ temp_attachment_id ออกจากตาราง migration_review_queue + +-- Rollback Delta: ลบคอลัมน์ temp_attachment_id ออกจากตาราง migration_review_queue +-- Date: 2026-06-02 +-- Related ADR: ADR-028, ADR-023A +-- Applied in: v1.9.8 + +ALTER TABLE migration_review_queue +DROP COLUMN temp_attachment_id; diff --git a/specs/03-Data-and-Storage/deltas/2026-06-02-add-temp-attachment-id-to-migration-review-queue.sql b/specs/03-Data-and-Storage/deltas/2026-06-02-add-temp-attachment-id-to-migration-review-queue.sql new file mode 100644 index 00000000..eb4b46b5 --- /dev/null +++ b/specs/03-Data-and-Storage/deltas/2026-06-02-add-temp-attachment-id-to-migration-review-queue.sql @@ -0,0 +1,16 @@ +-- File: specs/03-Data-and-Storage/deltas/2026-06-02-add-temp-attachment-id-to-migration-review-queue.sql +-- Change Log: +-- - 2026-06-02: เพิ่มคอลัมน์ temp_attachment_id ในตาราง migration_review_queue เพื่อแก้บั๊ก CleanupTempFilesWorker + +-- Delta: เพิ่มคอลัมน์ temp_attachment_id ในตาราง migration_review_queue +-- Date: 2026-06-02 +-- Related ADR: ADR-028, ADR-023A +-- Applied in: v1.9.8 + +-- ------------------------------------------------------------ +-- การปรับปรุงตาราง migration_review_queue (Schema changes) +-- ------------------------------------------------------------ + +ALTER TABLE migration_review_queue +ADD COLUMN temp_attachment_id INT NULL COMMENT 'Temporary attachment ID referencing attachments.id (ADR-028)' +AFTER STATUS; diff --git a/specs/03-Data-and-Storage/lcbp3-v1.9.0-schema-02-tables.sql b/specs/03-Data-and-Storage/lcbp3-v1.9.0-schema-02-tables.sql index c1cd080d..2d6e8a92 100644 --- a/specs/03-Data-and-Storage/lcbp3-v1.9.0-schema-02-tables.sql +++ b/specs/03-Data-and-Storage/lcbp3-v1.9.0-schema-02-tables.sql @@ -1512,6 +1512,7 @@ CREATE TABLE migration_review_queue ( confidence_score DECIMAL(5, 4) NOT NULL COMMENT 'AI confidence score 0.0000-1.0000', ocr_used TINYINT(1) NOT NULL DEFAULT 0 COMMENT 'ระบุว่าใช้ OCR path หรือไม่', STATUS ENUM('PENDING', 'APPROVED', 'IMPORTED', 'REJECTED') NOT NULL DEFAULT 'PENDING', + temp_attachment_id INT NULL COMMENT 'Temporary attachment ID referencing attachments.id (ADR-028)', reviewed_by INT NULL COMMENT 'Internal users.user_id ของผู้ review', reviewed_at DATETIME NULL COMMENT 'เวลาที่ review record', rejection_reason VARCHAR(500) NULL COMMENT 'เหตุผลเมื่อ reject',