import { blobGcMessage } from "@/worker/queues/blob-gc"; import { enqueueBlobGc } from "@/worker/r2/gc"; export interface RepairJobMessage { type: "storage_repair"; subject_id: string; storage_id: string; reason: string; created_at_ms: number; } export function repairJobMessage(input: { subjectId: string; storageId: string; reason: string; createdAtMs: number; }): RepairJobMessage { return { type: "storage_repair", subject_id: input.subjectId, storage_id: input.storageId, reason: input.reason, created_at_ms: input.createdAtMs, }; } export function isRepairJobMessage(value: unknown): value is RepairJobMessage { if (!value || typeof value !== "object") return false; const record = value as Record; return ( record.type === "storage_repair" && typeof record.subject_id === "string" && record.subject_id.length > 0 && typeof record.storage_id === "string" && record.storage_id.length > 0 && typeof record.reason === "string" && record.reason.length > 0 && typeof record.created_at_ms === "number" && Number.isFinite(record.created_at_ms) ); } export async function handleRepairJob(env: Env, message: RepairJobMessage, nowMs = Date.now()) { const subject = { subjectId: message.subject_id, storageId: message.storage_id, nowMs }; const fileRepair = await env.FILE_DAV.getByName(message.storage_id).repair({ ...subject, limit: 100 }); await env.CAL_DAV.getByName(message.storage_id).ensureInitialized(subject); await env.CARD_DAV.getByName(message.storage_id).ensureInitialized(subject); await enqueueBlobGc( env, fileRepair.blobGc.map((blob) => blobGcMessage({ subjectId: message.subject_id, storageId: message.storage_id, blobId: blob.blobId, blobKey: blob.blobKey, notBeforeMs: nowMs, }), ), ); return { action: "ack" as const, cleanedPendingUploads: fileRepair.cleanedPendingUploads, cleanedLocks: fileRepair.cleanedLocks, queuedBlobGc: fileRepair.blobGc.length, }; }