import "server-only"; import { archiveTemporaryImage } from "@/lib/storage/image-archive"; import { getImageArchiveTaskStats, getImageArchiveTasksToReplace, getImageArchiveTasksToStore, markImageArchiveTaskArchiving, markImageArchiveTaskCompleted, markImageArchiveTaskFailed, markImageArchiveTaskReplaceFailed, markImageArchiveTaskReplacing, markImageArchiveTaskStored, } from "@/lib/storage/image-archive-db"; import { replaceImageUrlInDatabase } from "@/lib/storage/image-url-replacement"; const DEFAULT_BATCH_SIZE = 10; function getBatchSize() { const parsed = Number(process.env.IMAGE_ARCHIVE_BATCH_SIZE || DEFAULT_BATCH_SIZE); if (!Number.isFinite(parsed)) return DEFAULT_BATCH_SIZE; return Math.max(1, Math.min(50, Math.round(parsed))); } export async function runImageArchiveJob() { const batchSize = getBatchSize(); console.log(`[ImageArchiveJob] Start image archive job (batchSize=${batchSize})`); const storeTasks = await getImageArchiveTasksToStore({ limit: batchSize }); let storeSucceeded = 0; let storeFailed = 0; for (const task of storeTasks) { try { await markImageArchiveTaskArchiving(task.id); const archived = await archiveTemporaryImage(task.sourceUrl); await markImageArchiveTaskStored({ id: task.id, targetUrl: archived.url, }); storeSucceeded += 1; console.log("[ImageArchiveJob] stored", { taskId: task.id, bytes: archived.bytes, sourceUrl: task.sourceUrl, targetUrl: archived.url, }); } catch (error) { storeFailed += 1; await markImageArchiveTaskFailed({ id: task.id, error }); console.error("[ImageArchiveJob] store failed", { taskId: task.id, sourceUrl: task.sourceUrl, error: error instanceof Error ? error.message : String(error), }); } } const remainingSlots = Math.max(1, batchSize - storeTasks.length); const replaceTasks = await getImageArchiveTasksToReplace({ limit: remainingSlots, }); let replaceSucceeded = 0; let replaceFailed = 0; for (const task of replaceTasks) { if (!task.targetUrl) { await markImageArchiveTaskReplaceFailed({ id: task.id, error: new Error("Missing targetUrl for stored image archive task"), }); replaceFailed += 1; continue; } try { await markImageArchiveTaskReplacing(task.id); const result = await replaceImageUrlInDatabase(task.sourceUrl, task.targetUrl); await markImageArchiveTaskCompleted({ id: task.id, replacedCount: result.changed, }); replaceSucceeded += 1; console.log("[ImageArchiveJob] replaced", { taskId: task.id, sourceUrl: task.sourceUrl, targetUrl: task.targetUrl, replacedCount: result.changed, }); } catch (error) { replaceFailed += 1; await markImageArchiveTaskReplaceFailed({ id: task.id, error }); console.error("[ImageArchiveJob] replace failed", { taskId: task.id, sourceUrl: task.sourceUrl, targetUrl: task.targetUrl, error: error instanceof Error ? error.message : String(error), }); } } const stats = await getImageArchiveTaskStats(); return { success: true, message: "Image archive job completed", store: { total: storeTasks.length, succeeded: storeSucceeded, failed: storeFailed, }, replace: { total: replaceTasks.length, succeeded: replaceSucceeded, failed: replaceFailed, }, stats, }; }