From e2bc2254a6207b27b9c7dd0f5aabb3dd1935c241 Mon Sep 17 00:00:00 2001 From: Yashwin Date: Mon, 1 Jun 2026 10:58:56 +0530 Subject: [PATCH] file-service: frontend service \u2014 blob_id type, supplier_id upload param, keep_blob delete, purgeSoftDeletedBlobs admin --- src/services/file-attachment-service.ts | 33 ++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/src/services/file-attachment-service.ts b/src/services/file-attachment-service.ts index 7bcd60f..8c484d8 100644 --- a/src/services/file-attachment-service.ts +++ b/src/services/file-attachment-service.ts @@ -30,6 +30,8 @@ export interface FileAttachment { version: number; is_current_version: boolean; previous_version_id: string | null; + /** Reference to the physical file blob (dedup model) */ + blob_id: string | null; is_public: boolean; access_level: string; download_count: number; @@ -136,6 +138,8 @@ export interface UploadFilesParams { files: File[]; entity_type: string; // required entity_id: string; // required — must be valid UUID + /** UUID from the suppliers table. When provided, file deduplication is scoped to this supplier. */ + supplier_id?: string | null; category?: string; // optional string label category_id?: string; // optional description?: string; // max 500 chars @@ -226,6 +230,7 @@ export const fileAttachmentService = { formData.append('file', params.files[0]); formData.append('entity_type', params.entity_type); formData.append('entity_id', params.entity_id); + if (params.supplier_id) formData.append('supplier_id', params.supplier_id); if (params.category) formData.append('category', params.category); if (params.category_id) formData.append('category_id', params.category_id); if (params.description) formData.append('description', params.description); @@ -245,6 +250,7 @@ export const fileAttachmentService = { params.files.forEach((file) => formData.append('files', file)); formData.append('entity_type', params.entity_type); formData.append('entity_id', params.entity_id); + if (params.supplier_id) formData.append('supplier_id', params.supplier_id); if (params.category) formData.append('category', params.category); if (params.category_id) formData.append('category_id', params.category_id); if (params.description) formData.append('description', params.description); @@ -267,9 +273,18 @@ export const fileAttachmentService = { return response.data; }, - /** DELETE /files/:id */ - delete: async (id: string, hard = false): Promise<{ success: boolean }> => { - const response = await apiClient.delete(`/files/${id}`, { params: { hard } }); + /** + * DELETE /files/:id + * @param hard - true: remove reference row from DB; false (default): soft-delete + * @param keepBlob - true: keep binary on disk even if last reference (Layer 3) + */ + delete: async (id: string, hard = false, keepBlob = false): Promise<{ success: boolean }> => { + const response = await apiClient.delete(`/files/${id}`, { + params: { + ...(hard && { hard: 'true' }), + ...(keepBlob && { keep_blob: 'true' }), + }, + }); return response.data; }, @@ -387,6 +402,18 @@ export const fileAttachmentService = { }>(`/files/${id}/content`); return response.data; }, + + /** + * POST /files/blobs/purge-soft-deleted — admin only. + * Purges soft-deleted blobs with zero references older than olderThanHours. + * @param olderThanHours - 0 means purge all eligible blobs regardless of age. + */ + purgeSoftDeletedBlobs: async (olderThanHours = 0): Promise<{ success: boolean; data: { purged: number } }> => { + const response = await apiClient.post('/files/blobs/purge-soft-deleted', null, { + params: olderThanHours > 0 ? { older_than_hours: olderThanHours } : {}, + }); + return response.data; + }, }; export default fileAttachmentService;