feat(jobs): purge file binaries when a job is delivered, keep metadata
Active scan + image traffic was going to bloat Storage fast — every
delivered case has tens of MB of STL hanging around forever. Now closing
a case via 'Teslim Aldım' fires a background archive sweep that deletes
the binary from the bucket but keeps the job_files row, so audit
('kim, ne, ne zaman yükledi') is preserved.
- DB: job_files.archivedAt datetime (nullable).
- archiveJobFiles(jobId) (lib/appwrite/job-file-archive.ts):
lists rows, storage.deleteFile each, stamps archivedAt on the row.
All in try/catch so partial Storage failures don't roll back the
'delivered' transition.
- markDeliveredAction fires it as 'void archiveJobFiles(jobId)' — same
fire-and-forget pattern as audit/notifications/finance sync.
UI / API
- Job detail file row dims to 60% opacity, shows 'Arşivlendi
{tarih}' inline, and disables both the download dialog trigger and
the STL viewer button.
- /api/jobs/[jobId]/files/[fileId]/download returns 410 Gone with a
Turkish message when archivedAt is set — direct-URL hot links can't
fish the file back either.
This commit is contained in:
@@ -6,6 +6,7 @@ import { z } from "zod";
|
||||
|
||||
import { logAudit } from "./audit";
|
||||
import { syncFinanceForJob } from "./finance-sync";
|
||||
import { archiveJobFiles } from "./job-file-archive";
|
||||
import { createNotification } from "./notification-helpers";
|
||||
import { calculateJobPriceForProsthetic } from "./pricing";
|
||||
import {
|
||||
@@ -564,6 +565,9 @@ export async function markDeliveredAction(
|
||||
jobId,
|
||||
message: `Hasta ${job.patientCode} işi teslim alındı.`,
|
||||
});
|
||||
// Free up Storage now that the case is closed. Metadata rows stay for
|
||||
// the audit trail; only the binaries go.
|
||||
void archiveJobFiles(jobId);
|
||||
} catch (e) {
|
||||
return { ok: false, error: appwriteError(e, "Teslim alınamadı.") };
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import "server-only";
|
||||
|
||||
import { Query } from "node-appwrite";
|
||||
|
||||
import { BUCKETS, DATABASE_ID, TABLES, type JobFile } from "./schema";
|
||||
import { createAdminClient } from "./server";
|
||||
|
||||
/**
|
||||
* Purge the binary scan/image/document objects backing a finished job from
|
||||
* Appwrite Storage and stamp archivedAt on the corresponding rows. The row
|
||||
* itself stays — the lab and clinic still need the audit trail (which file
|
||||
* was uploaded, by whom, when) long after delivery.
|
||||
*
|
||||
* Best-effort: a single Storage error must not block the calling action.
|
||||
* The function never throws.
|
||||
*/
|
||||
export async function archiveJobFiles(jobId: string): Promise<void> {
|
||||
const { tablesDB, storage } = createAdminClient();
|
||||
try {
|
||||
const result = await tablesDB.listRows({
|
||||
databaseId: DATABASE_ID,
|
||||
tableId: TABLES.jobFiles,
|
||||
queries: [Query.equal("jobId", jobId), Query.limit(500)],
|
||||
});
|
||||
const rows = result.rows as unknown as JobFile[];
|
||||
const now = new Date().toISOString();
|
||||
await Promise.all(
|
||||
rows.map(async (r) => {
|
||||
if (r.archivedAt) return;
|
||||
try {
|
||||
await storage.deleteFile(BUCKETS.jobFiles, r.fileId);
|
||||
} catch {
|
||||
// file already gone, or storage unreachable — still flip archivedAt
|
||||
// so the UI doesn't keep teasing a download button.
|
||||
}
|
||||
try {
|
||||
await tablesDB.updateRow(DATABASE_ID, TABLES.jobFiles, r.$id, {
|
||||
archivedAt: now,
|
||||
});
|
||||
} catch {
|
||||
// row update failed; leave it for the next call to retry.
|
||||
}
|
||||
}),
|
||||
);
|
||||
} catch {
|
||||
// List itself failed — nothing to do.
|
||||
}
|
||||
}
|
||||
@@ -137,6 +137,9 @@ export interface JobFile extends Row {
|
||||
name: string;
|
||||
size: number;
|
||||
mimeType?: string;
|
||||
/** Set when the binary is purged from object storage after a job closes.
|
||||
* The row stays for audit; downloads/previews are disabled past this point. */
|
||||
archivedAt?: string;
|
||||
}
|
||||
|
||||
export interface JobStatusHistory extends Row {
|
||||
|
||||
Reference in New Issue
Block a user