From e64925b43832a02fa04c68a23b6ab0de77f96333 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leon=20B=C3=B6sche?= Date: Sat, 10 Jan 2026 21:46:12 +0100 Subject: [PATCH] Refactor file handling to exclusively use Nextcloud WebDAV storage, removing local fallback logic --- go_cloud/internal/http/routes.go | 244 ++++++++----------------------- 1 file changed, 60 insertions(+), 184 deletions(-) diff --git a/go_cloud/internal/http/routes.go b/go_cloud/internal/http/routes.go index 69d639f..8e81a7c 100644 --- a/go_cloud/internal/http/routes.go +++ b/go_cloud/internal/http/routes.go @@ -1057,61 +1057,26 @@ func createOrgFileHandler(w http.ResponseWriter, r *http.Request, db *database.D return } - // Attempt WebDAV upload when configured + // ONLY use Nextcloud WebDAV storage storedPath := filepath.ToSlash(filepath.Join(parentPath, header.Filename)) if !strings.HasPrefix(storedPath, "/") { storedPath = "/" + storedPath } written := int64(len(data)) - if storageClient != nil { - // Build remote path under /orgs/ - rel := strings.TrimPrefix(storedPath, "/") - remotePath := path.Join("/orgs", orgID.String(), rel) - if err = storageClient.Upload(r.Context(), remotePath, bytes.NewReader(data), int64(len(data))); err != nil { - // Log and fallback to local disk - errors.LogError(r, err, "WebDAV upload failed, falling back to local disk") - } else { - // success -> persist metadata and return - f, err = db.CreateFile(r.Context(), &orgID, &userID, header.Filename, storedPath, "file", written) - if err != nil { - errors.LogError(r, err, "Failed to create org file") - errors.WriteError(w, errors.CodeInternal, "Server error", http.StatusInternalServerError) - return - } - - auditLogger.Log(r.Context(), audit.Entry{ - UserID: &userID, - OrgID: &orgID, - Action: "upload_file", - Success: true, - }) - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(map[string]interface{}{"id": f.ID}) - return - } - } - - // Fallback: Save to temp directory (WebDAV should be the primary storage) - baseDir := filepath.Join("/tmp", "uploads", "orgs", orgID.String()) - targetDir := filepath.Join(baseDir, parentPath) - if err = os.MkdirAll(targetDir, 0o755); err != nil { - errors.LogError(r, err, "Failed to create target dir in /tmp") - errors.WriteError(w, errors.CodeInternal, "Server error", http.StatusInternalServerError) + + if storageClient == nil { + errors.WriteError(w, errors.CodeInternal, "Storage not configured", http.StatusInternalServerError) return } - outPath := filepath.Join(targetDir, header.Filename) - if err = os.WriteFile(outPath, data, 0o644); err != nil { - errors.LogError(r, err, "Failed to write file to /tmp") - errors.WriteError(w, errors.CodeInternal, "Server error", http.StatusInternalServerError) + + // Build remote path under /orgs/ + rel := strings.TrimPrefix(storedPath, "/") + remotePath := path.Join("/orgs", orgID.String(), rel) + if err = storageClient.Upload(r.Context(), remotePath, bytes.NewReader(data), int64(len(data))); err != nil { + errors.LogError(r, err, "WebDAV upload failed") + errors.WriteError(w, errors.CodeInternal, "Failed to upload file to storage", http.StatusInternalServerError) return } - - // Store metadata in DB; store path relative to workspace root - storedPath = filepath.ToSlash(filepath.Join(parentPath, header.Filename)) - if !strings.HasPrefix(storedPath, "/") { - storedPath = "/" + storedPath - } f, err = db.CreateFile(r.Context(), &orgID, &userID, header.Filename, storedPath, "file", written) if err != nil { errors.LogError(r, err, "Failed to create org file") @@ -1248,51 +1213,21 @@ func createUserFileHandler(w http.ResponseWriter, r *http.Request, db *database. } written := int64(len(data)) fmt.Printf("[DEBUG] Upload: user=%s, file=%s, size=%d, path=%s\n", userID.String(), header.Filename, len(data), storedPath) - if storageClient != nil { - rel := strings.TrimPrefix(storedPath, "/") - remotePath := path.Join("/users", userID.String(), rel) - fmt.Printf("[DEBUG] Uploading to WebDAV: %s\n", remotePath) - if err = storageClient.Upload(r.Context(), remotePath, bytes.NewReader(data), int64(len(data))); err != nil { - errors.LogError(r, err, "WebDAV upload failed, falling back to local disk") - } else { - f, err = db.CreateFile(r.Context(), nil, &userID, header.Filename, storedPath, "file", written) - if err != nil { - errors.LogError(r, err, "Failed to create user file") - errors.WriteError(w, errors.CodeInternal, "Server error", http.StatusInternalServerError) - return - } - - auditLogger.Log(r.Context(), audit.Entry{ - UserID: &userID, - Action: "upload_user_file", - Success: true, - }) - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(map[string]interface{}{"id": f.ID}) - return - } - } - - // Fallback: write to temp directory (WebDAV should be the primary storage) - fmt.Printf("[DEBUG] WebDAV is nil or failed, using local storage fallback\n") - baseDir := filepath.Join("/tmp", "uploads", "users", userID.String()) - targetDir := filepath.Join(baseDir, parentPath) - fmt.Printf("[DEBUG] Creating directory: %s\n", targetDir) - if err = os.MkdirAll(targetDir, 0o755); err != nil { - errors.LogError(r, err, "Failed to create target dir in /tmp") - errors.WriteError(w, errors.CodeInternal, "Server error", http.StatusInternalServerError) + + // ONLY use Nextcloud WebDAV storage + if storageClient == nil { + errors.WriteError(w, errors.CodeInternal, "Storage not configured", http.StatusInternalServerError) return } - outPath := filepath.Join(targetDir, header.Filename) - fmt.Printf("[DEBUG] Writing file to: %s\n", outPath) - if err = os.WriteFile(outPath, data, 0o644); err != nil { - fmt.Printf("[DEBUG] Failed to write file: %v\n", err) - errors.LogError(r, err, "Failed to write file to /tmp") - errors.WriteError(w, errors.CodeInternal, "Server error", http.StatusInternalServerError) + + rel := strings.TrimPrefix(storedPath, "/") + remotePath := path.Join("/users", userID.String(), rel) + fmt.Printf("[DEBUG] Uploading to WebDAV: %s\n", remotePath) + if err = storageClient.Upload(r.Context(), remotePath, bytes.NewReader(data), int64(len(data))); err != nil { + errors.LogError(r, err, "WebDAV upload failed") + errors.WriteError(w, errors.CodeInternal, "Failed to upload file to storage", http.StatusInternalServerError) return } - fmt.Printf("[DEBUG] File written successfully to local storage: %s\n", outPath) f, err = db.CreateFile(r.Context(), nil, &userID, header.Filename, storedPath, "file", written) if err != nil { @@ -1399,58 +1334,26 @@ func downloadOrgFileHandler(w http.ResponseWriter, r *http.Request, db *database return } - // Try to download from Nextcloud first - if storageClient != nil { - rel := strings.TrimPrefix(filePath, "/") - remotePath := path.Join("/orgs", orgID.String(), rel) - - reader, size, err := storageClient.Download(r.Context(), remotePath) - if err == nil { - defer reader.Close() - - // Set appropriate headers for inline viewing - fileName := path.Base(filePath) - // Determine content type based on file extension - contentType := "application/octet-stream" - if strings.HasSuffix(strings.ToLower(fileName), ".pdf") { - contentType = "application/pdf" - } else if strings.HasSuffix(strings.ToLower(fileName), ".png") { - contentType = "image/png" - } else if strings.HasSuffix(strings.ToLower(fileName), ".jpg") || strings.HasSuffix(strings.ToLower(fileName), ".jpeg") { - contentType = "image/jpeg" - } - w.Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=\"%s\"", fileName)) - w.Header().Set("Content-Type", contentType) - if size > 0 { - w.Header().Set("Content-Length", fmt.Sprintf("%d", size)) - } - - // Stream the file - io.Copy(w, reader) - return - } - - errors.LogError(r, err, "Failed to download from Nextcloud, trying local storage") - } - - // Fallback to local disk (used when WebDAV is not configured) - baseDir := filepath.Clean(filepath.Join("/tmp/uploads/orgs", orgID.String())) - rel := strings.TrimPrefix(filePath, "/") - localPath := filepath.Join(baseDir, rel) - // Prevent path traversal escaping the baseDir - if !strings.HasPrefix(localPath, baseDir+string(os.PathSeparator)) && localPath != baseDir { - errors.WriteError(w, errors.CodeInvalidArgument, "Invalid path", http.StatusBadRequest) + // ONLY use Nextcloud WebDAV storage + if storageClient == nil { + errors.WriteError(w, errors.CodeInternal, "Storage not configured", http.StatusInternalServerError) return } + + rel := strings.TrimPrefix(filePath, "/") + remotePath := path.Join("/orgs", orgID.String(), rel) - f, err := os.Open(localPath) + reader, size, err := storageClient.Download(r.Context(), remotePath) if err != nil { + errors.LogError(r, err, "Failed to download from Nextcloud") errors.WriteError(w, errors.CodeNotFound, "File not found", http.StatusNotFound) return } - defer f.Close() - info, _ := f.Stat() + defer reader.Close() + + // Set appropriate headers for inline viewing fileName := path.Base(filePath) + // Determine content type based on file extension contentType := "application/octet-stream" if strings.HasSuffix(strings.ToLower(fileName), ".pdf") { contentType = "application/pdf" @@ -1461,10 +1364,13 @@ func downloadOrgFileHandler(w http.ResponseWriter, r *http.Request, db *database } w.Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=\"%s\"", fileName)) w.Header().Set("Content-Type", contentType) - if info != nil { - w.Header().Set("Content-Length", fmt.Sprintf("%d", info.Size())) + if size > 0 { + w.Header().Set("Content-Length", fmt.Sprintf("%d", size)) } - io.Copy(w, f) + + // Stream the file + io.Copy(w, reader) + } // downloadUserFileHandler downloads a file from user's personal workspace @@ -1485,61 +1391,28 @@ func downloadUserFileHandler(w http.ResponseWriter, r *http.Request, db *databas // Log the requested file path for debugging fmt.Printf("[DEBUG] Download request - User: %s, Path: %s\n", userID.String(), filePath) - // Try to download from Nextcloud first - if storageClient != nil { - rel := strings.TrimPrefix(filePath, "/") - // Keep remote user workspace path consistent with uploads: "/users//" - remotePath := path.Join("/users", userID.String(), rel) - fmt.Printf("[DEBUG] Trying WebDAV path: %s\n", remotePath) - - reader, size, err := storageClient.Download(r.Context(), remotePath) - if err == nil { - defer reader.Close() - - // Set appropriate headers for inline viewing - fileName := path.Base(filePath) - // Determine content type based on file extension - contentType := "application/octet-stream" - if strings.HasSuffix(strings.ToLower(fileName), ".pdf") { - contentType = "application/pdf" - } else if strings.HasSuffix(strings.ToLower(fileName), ".png") { - contentType = "image/png" - } else if strings.HasSuffix(strings.ToLower(fileName), ".jpg") || strings.HasSuffix(strings.ToLower(fileName), ".jpeg") { - contentType = "image/jpeg" - } - w.Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=\"%s\"", fileName)) - w.Header().Set("Content-Type", contentType) - if size > 0 { - w.Header().Set("Content-Length", fmt.Sprintf("%d", size)) - } - - // Stream the file - io.Copy(w, reader) - return - } - - errors.LogError(r, err, "Failed to download from Nextcloud, trying local storage") - } - - // Fallback to local disk (used when WebDAV is not configured) - baseDir := filepath.Clean(filepath.Join("/tmp/uploads/users", userID.String())) - rel := strings.TrimPrefix(filePath, "/") - localPath := filepath.Join(baseDir, rel) - fmt.Printf("[DEBUG] Trying local path: %s\n", localPath) - // Prevent path traversal escaping the baseDir - if !strings.HasPrefix(localPath, baseDir+string(os.PathSeparator)) && localPath != baseDir { - errors.WriteError(w, errors.CodeInvalidArgument, "Invalid path", http.StatusBadRequest) + // ONLY use Nextcloud WebDAV storage + if storageClient == nil { + errors.WriteError(w, errors.CodeInternal, "Storage not configured", http.StatusInternalServerError) return } + + rel := strings.TrimPrefix(filePath, "/") + // Keep remote user workspace path consistent with uploads: "/users//" + remotePath := path.Join("/users", userID.String(), rel) + fmt.Printf("[DEBUG] Trying WebDAV path: %s\n", remotePath) - f, err := os.Open(localPath) + reader, size, err := storageClient.Download(r.Context(), remotePath) if err != nil { + errors.LogError(r, err, "Failed to download from Nextcloud") errors.WriteError(w, errors.CodeNotFound, "File not found", http.StatusNotFound) return } - defer f.Close() - info, _ := f.Stat() + defer reader.Close() + + // Set appropriate headers for inline viewing fileName := path.Base(filePath) + // Determine content type based on file extension contentType := "application/octet-stream" if strings.HasSuffix(strings.ToLower(fileName), ".pdf") { contentType = "application/pdf" @@ -1550,8 +1423,11 @@ func downloadUserFileHandler(w http.ResponseWriter, r *http.Request, db *databas } w.Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=\"%s\"", fileName)) w.Header().Set("Content-Type", contentType) - if info != nil { - w.Header().Set("Content-Length", fmt.Sprintf("%d", info.Size())) + if size > 0 { + w.Header().Set("Content-Length", fmt.Sprintf("%d", size)) } - io.Copy(w, f) + + // Stream the file + io.Copy(w, reader) + }