@@ -32,6 +32,29 @@ export default class UploadPlugin extends AdminForthPlugin {
3232 }
3333 }
3434
35+ private normalizePaths ( value : any ) : string [ ] {
36+ if ( ! value ) return [ ] ;
37+ if ( Array . isArray ( value ) ) return value . filter ( Boolean ) . map ( String ) ;
38+ return [ String ( value ) ] ;
39+ }
40+
41+ private async callStorageAdapter ( primaryMethod : string , fallbackMethod : string , filePath : string ) {
42+ const adapter : any = this . options . storageAdapter as any ;
43+ const fn = adapter ?. [ primaryMethod ] ?? adapter ?. [ fallbackMethod ] ;
44+ if ( typeof fn !== 'function' ) {
45+ throw new Error ( `Storage adapter is missing method "${ primaryMethod } " (fallback "${ fallbackMethod } ")` ) ;
46+ }
47+ await fn . call ( adapter , filePath ) ;
48+ }
49+
50+ private markKeyForNotDeletion ( filePath : string ) {
51+ return this . callStorageAdapter ( 'markKeyForNotDeletion' , 'markKeyForNotDeletation' , filePath ) ;
52+ }
53+
54+ private markKeyForDeletion ( filePath : string ) {
55+ return this . callStorageAdapter ( 'markKeyForDeletion' , 'markKeyForDeletation' , filePath ) ;
56+ }
57+
3558 instanceUniqueRepresentation ( pluginOptions : any ) : string {
3659 return `${ pluginOptions . pathColumnName } ` ;
3760 }
@@ -42,13 +65,19 @@ export default class UploadPlugin extends AdminForthPlugin {
4265 }
4366
4467 async genPreviewUrl ( record : any ) {
45- if ( this . options . preview ?. previewUrl ) {
46- record [ `previewUrl_${ this . pluginInstanceId } ` ] = this . options . preview . previewUrl ( { filePath : record [ this . options . pathColumnName ] } ) ;
47- return ;
48- }
49- const previewUrl = await this . options . storageAdapter . getDownloadUrl ( record [ this . options . pathColumnName ] , 1800 ) ;
68+ const value = record ?. [ this . options . pathColumnName ] ;
69+ const paths = this . normalizePaths ( value ) ;
70+ if ( ! paths . length ) return ;
5071
51- record [ `previewUrl_${ this . pluginInstanceId } ` ] = previewUrl ;
72+ const makeUrl = async ( filePath : string ) => {
73+ if ( this . options . preview ?. previewUrl ) {
74+ return this . options . preview . previewUrl ( { filePath } ) ;
75+ }
76+ return await this . options . storageAdapter . getDownloadUrl ( filePath , 1800 ) ;
77+ } ;
78+
79+ const urls = await Promise . all ( paths . map ( makeUrl ) ) ;
80+ record [ `previewUrl_${ this . pluginInstanceId } ` ] = Array . isArray ( value ) ? urls : urls [ 0 ] ;
5281 }
5382
5483 async modifyResourceConfig ( adminforth : IAdminForth , resourceConfig : AdminForthResource ) {
@@ -128,15 +157,12 @@ export default class UploadPlugin extends AdminForthPlugin {
128157 resourceConfig . hooks . create . afterSave . push ( async ( { record } : { record : any } ) => {
129158 process . env . HEAVY_DEBUG && console . log ( '💾💾 after save ' , record ?. id ) ;
130159
131- if ( record [ pathColumnName ] ) {
132- process . env . HEAVY_DEBUG && console . log ( '🪥🪥 remove ObjectTagging' , record [ pathColumnName ] ) ;
160+ const paths = this . normalizePaths ( record ?. [ pathColumnName ] ) ;
161+ await Promise . all ( paths . map ( async ( p ) => {
162+ process . env . HEAVY_DEBUG && console . log ( '🪥🪥 remove ObjectTagging' , p ) ;
133163 // let it crash if it fails: this is a new file which just was uploaded.
134- if ( this . options . storageAdapter . markKeyForNotDeletion !== undefined ) {
135- await this . options . storageAdapter . markKeyForNotDeletion ( record [ pathColumnName ] ) ;
136- } else {
137- await this . options . storageAdapter . markKeyForNotDeletation ( record [ pathColumnName ] ) ;
138- }
139- }
164+ await this . markKeyForNotDeletion ( p ) ;
165+ } ) ) ;
140166 return { ok : true } ;
141167 } ) ;
142168
@@ -176,18 +202,15 @@ export default class UploadPlugin extends AdminForthPlugin {
176202
177203 // add delete hook which sets tag adminforth-candidate-for-cleanup to true
178204 resourceConfig . hooks . delete . afterSave . push ( async ( { record } : { record : any } ) => {
179- if ( record [ pathColumnName ] ) {
205+ const paths = this . normalizePaths ( record ?. [ pathColumnName ] ) ;
206+ await Promise . all ( paths . map ( async ( p ) => {
180207 try {
181- if ( this . options . storageAdapter . markKeyForDeletion !== undefined ) {
182- await this . options . storageAdapter . markKeyForDeletion ( record [ pathColumnName ] ) ;
183- } else {
184- await this . options . storageAdapter . markKeyForDeletation ( record [ pathColumnName ] ) ;
185- }
208+ await this . markKeyForDeletion ( p ) ;
186209 } catch ( e ) {
187210 // file might be e.g. already deleted, so we catch error
188- console . error ( `Error setting tag ${ ADMINFORTH_NOT_YET_USED_TAG } to true for object ${ record [ pathColumnName ] } . File will not be auto-cleaned up` , e ) ;
211+ console . error ( `Error setting tag ${ ADMINFORTH_NOT_YET_USED_TAG } to true for object ${ p } . File will not be auto-cleaned up` , e ) ;
189212 }
190- }
213+ } ) ) ;
191214 return { ok : true } ;
192215 } ) ;
193216
@@ -200,28 +223,33 @@ export default class UploadPlugin extends AdminForthPlugin {
200223 resourceConfig . hooks . edit . afterSave . push ( async ( { updates, oldRecord } : { updates : any , oldRecord : any } ) => {
201224
202225 if ( updates [ pathColumnName ] || updates [ pathColumnName ] === null ) {
203- if ( oldRecord [ pathColumnName ] ) {
226+ const oldValue = oldRecord ?. [ pathColumnName ] ;
227+ const newValue = updates ?. [ pathColumnName ] ;
228+
229+ const oldPaths = this . normalizePaths ( oldValue ) ;
230+ const newPaths = newValue === null ? [ ] : this . normalizePaths ( newValue ) ;
231+
232+ const oldSet = new Set ( oldPaths ) ;
233+ const newSet = new Set ( newPaths ) ;
234+
235+ const toDelete = oldPaths . filter ( ( p ) => ! newSet . has ( p ) ) ;
236+ const toKeep = newPaths . filter ( ( p ) => ! oldSet . has ( p ) ) ;
237+
238+ await Promise . all ( toDelete . map ( async ( p ) => {
204239 // put tag to delete old file
205240 try {
206- if ( this . options . storageAdapter . markKeyForDeletion !== undefined ) {
207- await this . options . storageAdapter . markKeyForDeletion ( oldRecord [ pathColumnName ] ) ;
208- } else {
209- await this . options . storageAdapter . markKeyForDeletation ( oldRecord [ pathColumnName ] ) ;
210- }
241+ await this . markKeyForDeletion ( p ) ;
211242 } catch ( e ) {
212243 // file might be e.g. already deleted, so we catch error
213- console . error ( `Error setting tag ${ ADMINFORTH_NOT_YET_USED_TAG } to true for object ${ oldRecord [ pathColumnName ] } . File will not be auto-cleaned up` , e ) ;
244+ console . error ( `Error setting tag ${ ADMINFORTH_NOT_YET_USED_TAG } to true for object ${ p } . File will not be auto-cleaned up` , e ) ;
214245 }
215- }
216- if ( updates [ pathColumnName ] !== null ) {
246+ } ) ) ;
247+
248+ await Promise . all ( toKeep . map ( async ( p ) => {
217249 // remove tag from new file
218- // in this case we let it crash if it fails: this is a new file which just was uploaded.
219- if ( this . options . storageAdapter . markKeyForNotDeletion !== undefined ) {
220- await this . options . storageAdapter . markKeyForNotDeletion ( updates [ pathColumnName ] ) ;
221- } else {
222- await this . options . storageAdapter . markKeyForNotDeletation ( updates [ pathColumnName ] ) ;
223- }
224- }
250+ // in this case we let it crash if it fails: this is a new file which just was uploaded.
251+ await this . markKeyForNotDeletion ( p ) ;
252+ } ) ) ;
225253 }
226254 return { ok : true } ;
227255 } ) ;
0 commit comments