@@ -41,6 +41,29 @@ export default class UploadPlugin extends AdminForthPlugin {
4141 }
4242 }
4343
44+ private normalizePaths ( value : any ) : string [ ] {
45+ if ( ! value ) return [ ] ;
46+ if ( Array . isArray ( value ) ) return value . filter ( Boolean ) . map ( String ) ;
47+ return [ String ( value ) ] ;
48+ }
49+
50+ private async callStorageAdapter ( primaryMethod : string , fallbackMethod : string , filePath : string ) {
51+ const adapter : any = this . options . storageAdapter as any ;
52+ const fn = adapter ?. [ primaryMethod ] ?? adapter ?. [ fallbackMethod ] ;
53+ if ( typeof fn !== 'function' ) {
54+ throw new Error ( `Storage adapter is missing method "${ primaryMethod } " (fallback "${ fallbackMethod } ")` ) ;
55+ }
56+ await fn . call ( adapter , filePath ) ;
57+ }
58+
59+ private markKeyForNotDeletion ( filePath : string ) {
60+ return this . callStorageAdapter ( 'markKeyForNotDeletion' , 'markKeyForNotDeletation' , filePath ) ;
61+ }
62+
63+ private markKeyForDeletion ( filePath : string ) {
64+ return this . callStorageAdapter ( 'markKeyForDeletion' , 'markKeyForDeletation' , filePath ) ;
65+ }
66+
4467 private async generateImages ( jobId : string , prompt : string , recordId : any , adminUser : any , headers : any ) {
4568 if ( this . options . generation . rateLimit ?. limit ) {
4669 // rate limit
@@ -128,13 +151,19 @@ export default class UploadPlugin extends AdminForthPlugin {
128151 }
129152
130153 async genPreviewUrl ( record : any ) {
131- if ( this . options . preview ?. previewUrl ) {
132- record [ `previewUrl_${ this . pluginInstanceId } ` ] = this . options . preview . previewUrl ( { filePath : record [ this . options . pathColumnName ] } ) ;
133- return ;
134- }
135- const previewUrl = await this . options . storageAdapter . getDownloadUrl ( record [ this . options . pathColumnName ] , 1800 ) ;
154+ const value = record ?. [ this . options . pathColumnName ] ;
155+ const paths = this . normalizePaths ( value ) ;
156+ if ( ! paths . length ) return ;
136157
137- record [ `previewUrl_${ this . pluginInstanceId } ` ] = previewUrl ;
158+ const makeUrl = async ( filePath : string ) => {
159+ if ( this . options . preview ?. previewUrl ) {
160+ return this . options . preview . previewUrl ( { filePath } ) ;
161+ }
162+ return await this . options . storageAdapter . getDownloadUrl ( filePath , 1800 ) ;
163+ } ;
164+
165+ const urls = await Promise . all ( paths . map ( makeUrl ) ) ;
166+ record [ `previewUrl_${ this . pluginInstanceId } ` ] = Array . isArray ( value ) ? urls : urls [ 0 ] ;
138167 }
139168
140169 async modifyResourceConfig ( adminforth : IAdminForth , resourceConfig : AdminForthResource ) {
@@ -214,15 +243,12 @@ export default class UploadPlugin extends AdminForthPlugin {
214243 resourceConfig . hooks . create . afterSave . push ( async ( { record } : { record : any } ) => {
215244 process . env . HEAVY_DEBUG && console . log ( '💾💾 after save ' , record ?. id ) ;
216245
217- if ( record [ pathColumnName ] ) {
218- process . env . HEAVY_DEBUG && console . log ( '🪥🪥 remove ObjectTagging' , record [ pathColumnName ] ) ;
246+ const paths = this . normalizePaths ( record ?. [ pathColumnName ] ) ;
247+ await Promise . all ( paths . map ( async ( p ) => {
248+ process . env . HEAVY_DEBUG && console . log ( '🪥🪥 remove ObjectTagging' , p ) ;
219249 // let it crash if it fails: this is a new file which just was uploaded.
220- if ( this . options . storageAdapter . markKeyForNotDeletion !== undefined ) {
221- await this . options . storageAdapter . markKeyForNotDeletion ( record [ pathColumnName ] ) ;
222- } else {
223- await this . options . storageAdapter . markKeyForNotDeletation ( record [ pathColumnName ] ) ;
224- }
225- }
250+ await this . markKeyForNotDeletion ( p ) ;
251+ } ) ) ;
226252 return { ok : true } ;
227253 } ) ;
228254
@@ -262,18 +288,15 @@ export default class UploadPlugin extends AdminForthPlugin {
262288
263289 // add delete hook which sets tag adminforth-candidate-for-cleanup to true
264290 resourceConfig . hooks . delete . afterSave . push ( async ( { record } : { record : any } ) => {
265- if ( record [ pathColumnName ] ) {
291+ const paths = this . normalizePaths ( record ?. [ pathColumnName ] ) ;
292+ await Promise . all ( paths . map ( async ( p ) => {
266293 try {
267- if ( this . options . storageAdapter . markKeyForDeletion !== undefined ) {
268- await this . options . storageAdapter . markKeyForDeletion ( record [ pathColumnName ] ) ;
269- } else {
270- await this . options . storageAdapter . markKeyForDeletation ( record [ pathColumnName ] ) ;
271- }
294+ await this . markKeyForDeletion ( p ) ;
272295 } catch ( e ) {
273296 // file might be e.g. already deleted, so we catch error
274- console . error ( `Error setting tag ${ ADMINFORTH_NOT_YET_USED_TAG } to true for object ${ record [ pathColumnName ] } . File will not be auto-cleaned up` , e ) ;
297+ console . error ( `Error setting tag ${ ADMINFORTH_NOT_YET_USED_TAG } to true for object ${ p } . File will not be auto-cleaned up` , e ) ;
275298 }
276- }
299+ } ) ) ;
277300 return { ok : true } ;
278301 } ) ;
279302
@@ -286,28 +309,33 @@ export default class UploadPlugin extends AdminForthPlugin {
286309 resourceConfig . hooks . edit . afterSave . push ( async ( { updates, oldRecord } : { updates : any , oldRecord : any } ) => {
287310
288311 if ( updates [ pathColumnName ] || updates [ pathColumnName ] === null ) {
289- if ( oldRecord [ pathColumnName ] ) {
312+ const oldValue = oldRecord ?. [ pathColumnName ] ;
313+ const newValue = updates ?. [ pathColumnName ] ;
314+
315+ const oldPaths = this . normalizePaths ( oldValue ) ;
316+ const newPaths = newValue === null ? [ ] : this . normalizePaths ( newValue ) ;
317+
318+ const oldSet = new Set ( oldPaths ) ;
319+ const newSet = new Set ( newPaths ) ;
320+
321+ const toDelete = oldPaths . filter ( ( p ) => ! newSet . has ( p ) ) ;
322+ const toKeep = newPaths . filter ( ( p ) => ! oldSet . has ( p ) ) ;
323+
324+ await Promise . all ( toDelete . map ( async ( p ) => {
290325 // put tag to delete old file
291326 try {
292- if ( this . options . storageAdapter . markKeyForDeletion !== undefined ) {
293- await this . options . storageAdapter . markKeyForDeletion ( oldRecord [ pathColumnName ] ) ;
294- } else {
295- await this . options . storageAdapter . markKeyForDeletation ( oldRecord [ pathColumnName ] ) ;
296- }
327+ await this . markKeyForDeletion ( p ) ;
297328 } catch ( e ) {
298329 // file might be e.g. already deleted, so we catch error
299- console . error ( `Error setting tag ${ ADMINFORTH_NOT_YET_USED_TAG } to true for object ${ oldRecord [ pathColumnName ] } . File will not be auto-cleaned up` , e ) ;
330+ console . error ( `Error setting tag ${ ADMINFORTH_NOT_YET_USED_TAG } to true for object ${ p } . File will not be auto-cleaned up` , e ) ;
300331 }
301- }
302- if ( updates [ pathColumnName ] !== null ) {
332+ } ) ) ;
333+
334+ await Promise . all ( toKeep . map ( async ( p ) => {
303335 // remove tag from new file
304- // in this case we let it crash if it fails: this is a new file which just was uploaded.
305- if ( this . options . storageAdapter . markKeyForNotDeletion !== undefined ) {
306- await this . options . storageAdapter . markKeyForNotDeletion ( updates [ pathColumnName ] ) ;
307- } else {
308- await this . options . storageAdapter . markKeyForNotDeletation ( updates [ pathColumnName ] ) ;
309- }
310- }
336+ // in this case we let it crash if it fails: this is a new file which just was uploaded.
337+ await this . markKeyForNotDeletion ( p ) ;
338+ } ) ) ;
311339 }
312340 return { ok : true } ;
313341 } ) ;
0 commit comments