From cd9a7503b527dd070006430811876e53c3e16b6a Mon Sep 17 00:00:00 2001 From: bagas Date: Tue, 17 Sep 2024 11:21:04 +0700 Subject: [PATCH] feat: implement file deletion --- db/database.go | 16 ++ handler/file/delete/delete.go | 45 ++++ routes/client/routes.go | 5 + types/types.go | 1 + view/client/file/file.templ | 56 ++++- view/client/file/file_templ.go | 369 +++++++++++++++++++++------------ 6 files changed, 355 insertions(+), 137 deletions(-) create mode 100644 handler/file/delete/delete.go diff --git a/db/database.go b/db/database.go index 9b6774a..7001117 100644 --- a/db/database.go +++ b/db/database.go @@ -243,6 +243,14 @@ func (db *mySQLdb) GetFile(fileID string) (*models.File, error) { return &file, nil } +func (db *mySQLdb) DeleteFile(fileID string) error { + err := db.DB.Table("files").Where("id = ?", fileID).Delete(&models.File{}).Error + if err != nil { + return err + } + return nil +} + func (db *mySQLdb) GetUserFile(name string, ownerID string) (*models.File, error) { var file models.File err := db.DB.Table("files").Where("name = ? AND owner_id = ?", name, ownerID).First(&file).Error @@ -403,6 +411,14 @@ func (db *postgresDB) GetFile(fileID string) (*models.File, error) { return &file, nil } +func (db *postgresDB) DeleteFile(fileID string) error { + err := db.DB.Table("files").Where("id = $1", fileID).Delete(&models.File{}).Error + if err != nil { + return err + } + return nil +} + func (db *postgresDB) GetUserFile(name string, ownerID string) (*models.File, error) { var file models.File err := db.DB.Table("files").Where("name = $1 AND owner_id = $2", name, ownerID).First(&file).Error diff --git a/handler/file/delete/delete.go b/handler/file/delete/delete.go new file mode 100644 index 0000000..da4f1a5 --- /dev/null +++ b/handler/file/delete/delete.go @@ -0,0 +1,45 @@ +package deleteHandler + +import ( + "github.com/fossyy/filekeeper/app" + "github.com/fossyy/filekeeper/types" + "net/http" + "os" + "path/filepath" +) + +func DELETE(w http.ResponseWriter, r *http.Request) { + fileID := r.PathValue("id") + consent := r.URL.Query().Get("consent") + userSession := r.Context().Value("user").(types.User) + + file, err := app.Server.Database.GetFile(fileID) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + if userSession.UserID != file.OwnerID || consent != "true" { + w.WriteHeader(http.StatusUnauthorized) + return + } + + err = app.Server.Database.DeleteFile(fileID) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + uploadDir := "uploads" + currentDir, _ := os.Getwd() + basePath := filepath.Join(currentDir, uploadDir) + fileFolder := filepath.Join(basePath, file.OwnerID.String(), file.ID.String()) + err = os.RemoveAll(fileFolder) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) + return +} diff --git a/routes/client/routes.go b/routes/client/routes.go index fec6b44..9d2ee05 100644 --- a/routes/client/routes.go +++ b/routes/client/routes.go @@ -6,6 +6,7 @@ import ( googleOauthSetupHandler "github.com/fossyy/filekeeper/handler/auth/google/setup" totpHandler "github.com/fossyy/filekeeper/handler/auth/totp" fileHandler "github.com/fossyy/filekeeper/handler/file" + deleteHandler "github.com/fossyy/filekeeper/handler/file/delete" downloadHandler "github.com/fossyy/filekeeper/handler/file/download" uploadHandler "github.com/fossyy/filekeeper/handler/file/upload" visibilityHandler "github.com/fossyy/filekeeper/handler/file/visibility" @@ -118,6 +119,10 @@ func SetupRoutes() *http.ServeMux { middleware.Auth(uploadHandler.POST, w, r) }) + handler.HandleFunc("DELETE /file/{id}", func(w http.ResponseWriter, r *http.Request) { + middleware.Auth(deleteHandler.DELETE, w, r) + }) + handler.HandleFunc("GET /file/{id}", func(w http.ResponseWriter, r *http.Request) { downloadHandler.GET(w, r) }) diff --git a/types/types.go b/types/types.go index 98eef82..83817fd 100644 --- a/types/types.go +++ b/types/types.go @@ -60,6 +60,7 @@ type Database interface { CreateFile(file *models.File) error GetFile(fileID string) (*models.File, error) + DeleteFile(fileID string) error GetUserFile(name string, ownerID string) (*models.File, error) GetFiles(ownerID string) ([]*models.File, error) IncrementDownloadCount(fileID string) error diff --git a/view/client/file/file.templ b/view/client/file/file.templ index 646faee..d90d788 100644 --- a/view/client/file/file.templ +++ b/view/client/file/file.templ @@ -54,6 +54,26 @@ templ MainContent(files []types.FileData, user types.User, allowance *types.Allo +
@@ -205,7 +225,7 @@ templ MainContent(files []types.FileData, user types.User, allowance *types.Allo Rename - @@ -451,6 +471,40 @@ templ FileIcon(fileType string) { } } +script showDeletionModal(name string, id string) { + const deleteButton = document.getElementById('deleteButton'); + const modal = document.getElementById('deleteModal'); + const modalContent = modal.querySelector('div'); + const confirmDelete = document.getElementById('confirmDelete'); + const cancelDelete = document.getElementById('cancelDelete'); + const fileNameToDelete = document.getElementById('fileNameToDelete'); + + confirmDelete.setAttribute("hx-delete", "/file/" + id + "?consent=true"); + confirmDelete.setAttribute("hx-target", "#file-" + id); + htmx.process(confirmDelete); + modal.classList.remove('hidden'); + setTimeout(() => { + modal.classList.remove('opacity-0'); + modalContent.classList.remove('-translate-y-full', 'scale-95', 'opacity-0'); + }, 50); + fileNameToDelete.textContent = name; +} + +script hideDeletionModal() { + const deleteButton = document.getElementById('deleteButton'); + const modal = document.getElementById('deleteModal'); + const modalContent = modal.querySelector('div'); + const confirmDelete = document.getElementById('confirmDelete'); + const cancelDelete = document.getElementById('cancelDelete'); + const fileNameToDelete = document.getElementById('fileNameToDelete'); + + modal.classList.add('opacity-0'); + modalContent.classList.add('-translate-y-full', 'scale-95', 'opacity-0'); + setTimeout(() => { + modal.classList.add('hidden'); + }, 300); +} + templ Main(title string, files []types.FileData, user types.User, allowance *types.Allowance) { @component(title, files, user, allowance) } diff --git a/view/client/file/file_templ.go b/view/client/file/file_templ.go index d672957..ddaf093 100644 --- a/view/client/file/file_templ.go +++ b/view/client/file/file_templ.go @@ -86,7 +86,41 @@ func MainContent(files []types.FileData, user types.User, allowance *types.Allow if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
Back
") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
Back
File NameFile SizeDownloadsStatusAction
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -95,12 +129,12 @@ func MainContent(files []types.FileData, user types.User, allowance *types.Allow if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var4 string - templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs("file-" + file.ID) + var templ_7745c5c3_Var6 string + templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs("file-" + file.ID) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/client/file/file.templ`, Line: 73, Col: 37} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/client/file/file.templ`, Line: 93, Col: 37} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -121,12 +155,12 @@ func MainContent(files []types.FileData, user types.User, allowance *types.Allow if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var5 string - templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(file.Name) + var templ_7745c5c3_Var7 string + templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(file.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/client/file/file.templ`, Line: 78, Col: 134} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/client/file/file.templ`, Line: 98, Col: 134} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -134,12 +168,12 @@ func MainContent(files []types.FileData, user types.User, allowance *types.Allow if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var6 string - templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(file.Name) + var templ_7745c5c3_Var8 string + templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(file.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/client/file/file.templ`, Line: 79, Col: 74} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/client/file/file.templ`, Line: 99, Col: 74} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -160,12 +194,12 @@ func MainContent(files []types.FileData, user types.User, allowance *types.Allow if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var7 string - templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(file.Name) + var templ_7745c5c3_Var9 string + templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(file.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/client/file/file.templ`, Line: 93, Col: 134} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/client/file/file.templ`, Line: 113, Col: 134} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -173,12 +207,12 @@ func MainContent(files []types.FileData, user types.User, allowance *types.Allow if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var8 string - templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(file.Name) + var templ_7745c5c3_Var10 string + templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(file.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/client/file/file.templ`, Line: 94, Col: 74} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/client/file/file.templ`, Line: 114, Col: 74} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -191,12 +225,12 @@ func MainContent(files []types.FileData, user types.User, allowance *types.Allow if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var9 string - templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(file.Size) + var templ_7745c5c3_Var11 string + templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(file.Size) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/client/file/file.templ`, Line: 100, Col: 45} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/client/file/file.templ`, Line: 120, Col: 45} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -204,12 +238,12 @@ func MainContent(files []types.FileData, user types.User, allowance *types.Allow if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var10 string - templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(file.Downloaded) + var templ_7745c5c3_Var12 string + templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(file.Downloaded) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/client/file/file.templ`, Line: 108, Col: 31} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/client/file/file.templ`, Line: 128, Col: 31} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -240,8 +274,8 @@ func MainContent(files []types.FileData, user types.User, allowance *types.Allow if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var11 templ.ComponentScript = toggleDropDown() - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var11.Call) + var templ_7745c5c3_Var13 templ.ComponentScript = toggleDropDown() + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var13.Call) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -254,8 +288,8 @@ func MainContent(files []types.FileData, user types.User, allowance *types.Allow if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var12 templ.SafeURL = templ.SafeURL("/file/" + file.ID) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var12))) + var templ_7745c5c3_Var14 templ.SafeURL = templ.SafeURL("/file/" + file.ID) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var14))) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -264,37 +298,6 @@ func MainContent(files []types.FileData, user types.User, allowance *types.Allow return templ_7745c5c3_Err } if file.IsPrivate { - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } else { _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err @@ -331,7 +365,24 @@ func MainContent(files []types.FileData, user types.User, allowance *types.Allow return templ_7745c5c3_Err } } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, showDeletionModal(file.Name, file.ID)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -340,12 +391,12 @@ func MainContent(files []types.FileData, user types.User, allowance *types.Allow if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var17 string - templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(len(files))) + var templ_7745c5c3_Var20 string + templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(len(files))) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/client/file/file.templ`, Line: 224, Col: 120} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/client/file/file.templ`, Line: 244, Col: 120} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -353,12 +404,12 @@ func MainContent(files []types.FileData, user types.User, allowance *types.Allow if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var18 string - templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(allowance.AllowanceUsedByte) + var templ_7745c5c3_Var21 string + templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(allowance.AllowanceUsedByte) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/client/file/file.templ`, Line: 225, Col: 123} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/client/file/file.templ`, Line: 245, Col: 123} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -366,12 +417,12 @@ func MainContent(files []types.FileData, user types.User, allowance *types.Allow if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var19 string - templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(allowance.AllowanceByte) + var templ_7745c5c3_Var22 string + templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(allowance.AllowanceByte) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/client/file/file.templ`, Line: 226, Col: 123} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/client/file/file.templ`, Line: 246, Col: 123} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -425,21 +476,21 @@ func JustFile(file types.FileData) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var20 := templ.GetChildren(ctx) - if templ_7745c5c3_Var20 == nil { - templ_7745c5c3_Var20 = templ.NopComponent + templ_7745c5c3_Var23 := templ.GetChildren(ctx) + if templ_7745c5c3_Var23 == nil { + templ_7745c5c3_Var23 = templ.NopComponent } ctx = templ.ClearChildren(ctx) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" { + modal.classList.remove('opacity-0'); + modalContent.classList.remove('-translate-y-full', 'scale-95', 'opacity-0'); + }, 50); + fileNameToDelete.textContent = name; +}`, + Call: templ.SafeScript(`__templ_showDeletionModal_c813`, name, id), + CallInline: templ.SafeScriptInline(`__templ_showDeletionModal_c813`, name, id), + } +} + +func hideDeletionModal() templ.ComponentScript { + return templ.ComponentScript{ + Name: `__templ_hideDeletionModal_a203`, + Function: `function __templ_hideDeletionModal_a203(){const deleteButton = document.getElementById('deleteButton'); + const modal = document.getElementById('deleteModal'); + const modalContent = modal.querySelector('div'); + const confirmDelete = document.getElementById('confirmDelete'); + const cancelDelete = document.getElementById('cancelDelete'); + const fileNameToDelete = document.getElementById('fileNameToDelete'); + + modal.classList.add('opacity-0'); + modalContent.classList.add('-translate-y-full', 'scale-95', 'opacity-0'); + setTimeout(() => { + modal.classList.add('hidden'); + }, 300); +}`, + Call: templ.SafeScript(`__templ_hideDeletionModal_a203`), + CallInline: templ.SafeScriptInline(`__templ_hideDeletionModal_a203`), + } +} + func Main(title string, files []types.FileData, user types.User, allowance *types.Allowance) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context @@ -750,9 +847,9 @@ func Main(title string, files []types.FileData, user types.User, allowance *type }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var35 := templ.GetChildren(ctx) - if templ_7745c5c3_Var35 == nil { - templ_7745c5c3_Var35 = templ.NopComponent + templ_7745c5c3_Var38 := templ.GetChildren(ctx) + if templ_7745c5c3_Var38 == nil { + templ_7745c5c3_Var38 = templ.NopComponent } ctx = templ.ClearChildren(ctx) templ_7745c5c3_Err = component(title, files, user, allowance).Render(ctx, templ_7745c5c3_Buffer)
File NameFile SizeDownloadsStatusAction