Refactor file handling to exclusively use Nextcloud WebDAV storage, removing local fallback logic
This commit is contained in:
@@ -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/<orgId>
|
||||
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/<orgId>
|
||||
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/<userID>/<rel>"
|
||||
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/<userID>/<rel>"
|
||||
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)
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user