Add functionality to download folders as ZIP archives for both org and user files
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
@@ -1740,6 +1741,24 @@ func downloadOrgFileHandler(w http.ResponseWriter, r *http.Request, db *database
|
||||
return
|
||||
}
|
||||
|
||||
// Check if it's a folder
|
||||
file, err := db.GetOrgFileByPath(r.Context(), orgID, userID, filePath)
|
||||
if err != nil && err.Error() != "sql: no rows in result set" {
|
||||
errors.LogError(r, err, "Failed to get file info")
|
||||
errors.WriteError(w, errors.CodeInternal, "Server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if file != nil && file.Type == "folder" {
|
||||
// Download folder as ZIP
|
||||
err = downloadOrgFolderAsZip(w, r, db, cfg, orgID, userID, filePath, storageClient)
|
||||
if err != nil {
|
||||
errors.LogError(r, err, "Failed to download folder")
|
||||
errors.WriteError(w, errors.CodeInternal, "Failed to download folder", http.StatusInternalServerError)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Download from user's Nextcloud space under /orgs/<orgID>/
|
||||
rel := strings.TrimPrefix(filePath, "/")
|
||||
remotePath := path.Join("/orgs", orgID.String(), rel)
|
||||
@@ -1785,6 +1804,64 @@ func downloadOrgFileHandler(w http.ResponseWriter, r *http.Request, db *database
|
||||
|
||||
}
|
||||
|
||||
// downloadOrgFolderAsZip downloads a folder as ZIP archive
|
||||
func downloadOrgFolderAsZip(w http.ResponseWriter, r *http.Request, db *database.DB, cfg *config.Config, orgID, userID uuid.UUID, folderPath string, storageClient *storage.WebDAVClient) error {
|
||||
// Get all files under the folder
|
||||
files, err := db.GetAllOrgFilesUnderPath(r.Context(), orgID, userID, folderPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Filter only files, not folders
|
||||
var fileList []database.File
|
||||
for _, f := range files {
|
||||
if f.Type == "file" {
|
||||
fileList = append(fileList, f)
|
||||
}
|
||||
}
|
||||
|
||||
// Set headers for ZIP download
|
||||
folderName := path.Base(folderPath)
|
||||
if folderName == "" || folderName == "/" {
|
||||
folderName = "org_files"
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/zip")
|
||||
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s.zip\"", folderName))
|
||||
|
||||
// Create ZIP writer
|
||||
zipWriter := zip.NewWriter(w)
|
||||
defer zipWriter.Close()
|
||||
|
||||
// Add each file to ZIP
|
||||
for _, file := range fileList {
|
||||
// Calculate relative path in ZIP
|
||||
relPath := strings.TrimPrefix(file.Path, folderPath)
|
||||
if relPath[0] == '/' {
|
||||
relPath = relPath[1:]
|
||||
}
|
||||
|
||||
// Download file from WebDAV
|
||||
remoteRel := strings.TrimPrefix(file.Path, "/")
|
||||
remotePath := path.Join("/orgs", orgID.String(), remoteRel)
|
||||
resp, err := storageClient.Download(r.Context(), remotePath, "")
|
||||
if err != nil {
|
||||
continue // Skip files that can't be downloaded
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Create ZIP entry
|
||||
zipFile, err := zipWriter.Create(relPath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Copy file content to ZIP
|
||||
io.Copy(zipFile, resp.Body)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// downloadUserFileHandler downloads a file from user's personal workspace
|
||||
func downloadUserFileHandler(w http.ResponseWriter, r *http.Request, db *database.DB, cfg *config.Config) {
|
||||
// Try to get userID from context (Bearer token), fallback to query parameter
|
||||
@@ -1818,6 +1895,24 @@ func downloadUserFileHandler(w http.ResponseWriter, r *http.Request, db *databas
|
||||
return
|
||||
}
|
||||
|
||||
// Check if it's a folder
|
||||
file, err := db.GetUserFileByPath(r.Context(), userID, filePath)
|
||||
if err != nil && err.Error() != "sql: no rows in result set" {
|
||||
errors.LogError(r, err, "Failed to get file info")
|
||||
errors.WriteError(w, errors.CodeInternal, "Server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if file != nil && file.Type == "folder" {
|
||||
// Download folder as ZIP
|
||||
err = downloadUserFolderAsZip(w, r, db, cfg, userID, filePath, storageClient)
|
||||
if err != nil {
|
||||
errors.LogError(r, err, "Failed to download folder")
|
||||
errors.WriteError(w, errors.CodeInternal, "Failed to download folder", http.StatusInternalServerError)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Download from user's personal Nextcloud space
|
||||
remotePath := strings.TrimPrefix(filePath, "/")
|
||||
|
||||
@@ -1863,6 +1958,63 @@ func downloadUserFileHandler(w http.ResponseWriter, r *http.Request, db *databas
|
||||
|
||||
}
|
||||
|
||||
// downloadUserFolderAsZip downloads a folder as ZIP archive
|
||||
func downloadUserFolderAsZip(w http.ResponseWriter, r *http.Request, db *database.DB, cfg *config.Config, userID uuid.UUID, folderPath string, storageClient *storage.WebDAVClient) error {
|
||||
// Get all files under the folder
|
||||
files, err := db.GetAllUserFilesUnderPath(r.Context(), userID, folderPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Filter only files, not folders
|
||||
var fileList []database.File
|
||||
for _, f := range files {
|
||||
if f.Type == "file" {
|
||||
fileList = append(fileList, f)
|
||||
}
|
||||
}
|
||||
|
||||
// Set headers for ZIP download
|
||||
folderName := path.Base(folderPath)
|
||||
if folderName == "" || folderName == "/" {
|
||||
folderName = "user_files"
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/zip")
|
||||
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s.zip\"", folderName))
|
||||
|
||||
// Create ZIP writer
|
||||
zipWriter := zip.NewWriter(w)
|
||||
defer zipWriter.Close()
|
||||
|
||||
// Add each file to ZIP
|
||||
for _, file := range fileList {
|
||||
// Calculate relative path in ZIP
|
||||
relPath := strings.TrimPrefix(file.Path, folderPath)
|
||||
if relPath[0] == '/' {
|
||||
relPath = relPath[1:]
|
||||
}
|
||||
|
||||
// Download file from WebDAV
|
||||
remotePath := strings.TrimPrefix(file.Path, "/")
|
||||
resp, err := storageClient.Download(r.Context(), "/"+remotePath, "")
|
||||
if err != nil {
|
||||
continue // Skip files that can't be downloaded
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Create ZIP entry
|
||||
zipFile, err := zipWriter.Create(relPath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Copy file content to ZIP
|
||||
io.Copy(zipFile, resp.Body)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getMimeType returns the MIME type based on file extension
|
||||
func getMimeType(filename string) string {
|
||||
lower := strings.ToLower(filename)
|
||||
|
||||
Reference in New Issue
Block a user