@@ -2,6 +2,7 @@ package handler
22
33import (
44 "bytes"
5+ "context"
56 "crypto/rand"
67 "encoding/hex"
78 "errors"
@@ -99,24 +100,25 @@ func (h *BrandingHandler) storeBrandingFile(slot string, fh *multipart.FileHeade
99100 return "" , err
100101 }
101102
102- // Best-effort: drop older files for this slot so they don't accumulate.
103- deleteBrandingFilesExcept (dir , slot + "-" , filename )
104103 return brandingURLPrefix + "/" + filename , nil
105104}
106105
107- func deleteBrandingFilesExcept (dir , prefix , keep string ) {
108- entries , err := os .ReadDir (dir )
109- if err != nil {
106+ // removeIfUnreferenced deletes the file backing `url` when no tenant's branding
107+ // row still references it. `url` must be a stored branding URL (returned by
108+ // storeBrandingFile) — external URLs are ignored.
109+ func (h * BrandingHandler ) removeIfUnreferenced (ctx context.Context , url string ) {
110+ if ! strings .HasPrefix (url , brandingURLPrefix + "/" ) {
110111 return
111112 }
112- for _ , e := range entries {
113- if e . IsDir () {
114- continue
115- }
116- if name := e . Name (); strings .HasPrefix ( name , prefix ) && name != keep {
117- _ = os . Remove ( filepath . Join ( dir , name ))
118- }
113+ referenced , err := h . usecase . IsBrandingAssetReferenced ( ctx , url )
114+ if err != nil || referenced {
115+ return
116+ }
117+ name := strings .TrimPrefix ( url , brandingURLPrefix + "/" )
118+ if name == "" || strings . ContainsAny ( name , "/ \\ " ) {
119+ return
119120 }
121+ _ = os .Remove (filepath .Join (h .uploadDir , brandingSubdir , name ))
120122}
121123
122124// UploadAsset godoc
@@ -155,7 +157,7 @@ func (h *BrandingHandler) UploadAsset(c *gin.Context) {
155157 c .JSON (http .StatusUnsupportedMediaType , gin.H {"error" : err .Error ()})
156158 return
157159 }
158- resp , err := h .usecase .SetAsset (c .Request .Context (), c .GetString ("user_email" ), slot , url )
160+ resp , previous , err := h .usecase .SetAsset (c .Request .Context (), c .GetString ("user_email" ), slot , url )
159161 audit .Record (c , audit_connectors.Event {Action : "branding.asset.uploaded" , ResourceType : "branding" , ResourceID : slot },
160162 audit_domain .CONFIG_CHANGED , audit_domain .CONFIG_CHANGED , err )
161163 if errors .Is (err , usecase .ErrUnknownAssetSlot ) {
@@ -167,6 +169,9 @@ func (h *BrandingHandler) UploadAsset(c *gin.Context) {
167169 c .JSON (http .StatusInternalServerError , gin.H {"error" : "could not save asset" })
168170 return
169171 }
172+ if previous != "" && previous != url {
173+ h .removeIfUnreferenced (c .Request .Context (), previous )
174+ }
170175 c .JSON (http .StatusOK , resp )
171176}
172177
0 commit comments