Refactor file handling to exclusively use Nextcloud WebDAV storage, removing local fallback logic
This commit is contained in:
@@ -1057,60 +1057,25 @@ func createOrgFileHandler(w http.ResponseWriter, r *http.Request, db *database.D
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attempt WebDAV upload when configured
|
// ONLY use Nextcloud WebDAV storage
|
||||||
storedPath := filepath.ToSlash(filepath.Join(parentPath, header.Filename))
|
storedPath := filepath.ToSlash(filepath.Join(parentPath, header.Filename))
|
||||||
if !strings.HasPrefix(storedPath, "/") {
|
if !strings.HasPrefix(storedPath, "/") {
|
||||||
storedPath = "/" + storedPath
|
storedPath = "/" + storedPath
|
||||||
}
|
}
|
||||||
written := int64(len(data))
|
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{
|
if storageClient == nil {
|
||||||
UserID: &userID,
|
errors.WriteError(w, errors.CodeInternal, "Storage not configured", http.StatusInternalServerError)
|
||||||
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)
|
|
||||||
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)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store metadata in DB; store path relative to workspace root
|
// Build remote path under /orgs/<orgId>
|
||||||
storedPath = filepath.ToSlash(filepath.Join(parentPath, header.Filename))
|
rel := strings.TrimPrefix(storedPath, "/")
|
||||||
if !strings.HasPrefix(storedPath, "/") {
|
remotePath := path.Join("/orgs", orgID.String(), rel)
|
||||||
storedPath = "/" + storedPath
|
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
|
||||||
}
|
}
|
||||||
f, err = db.CreateFile(r.Context(), &orgID, &userID, header.Filename, storedPath, "file", written)
|
f, err = db.CreateFile(r.Context(), &orgID, &userID, header.Filename, storedPath, "file", written)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1248,51 +1213,21 @@ func createUserFileHandler(w http.ResponseWriter, r *http.Request, db *database.
|
|||||||
}
|
}
|
||||||
written := int64(len(data))
|
written := int64(len(data))
|
||||||
fmt.Printf("[DEBUG] Upload: user=%s, file=%s, size=%d, path=%s\n", userID.String(), header.Filename, len(data), storedPath)
|
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{
|
// ONLY use Nextcloud WebDAV storage
|
||||||
UserID: &userID,
|
if storageClient == nil {
|
||||||
Action: "upload_user_file",
|
errors.WriteError(w, errors.CodeInternal, "Storage not configured", http.StatusInternalServerError)
|
||||||
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)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
outPath := filepath.Join(targetDir, header.Filename)
|
|
||||||
fmt.Printf("[DEBUG] Writing file to: %s\n", outPath)
|
rel := strings.TrimPrefix(storedPath, "/")
|
||||||
if err = os.WriteFile(outPath, data, 0o644); err != nil {
|
remotePath := path.Join("/users", userID.String(), rel)
|
||||||
fmt.Printf("[DEBUG] Failed to write file: %v\n", err)
|
fmt.Printf("[DEBUG] Uploading to WebDAV: %s\n", remotePath)
|
||||||
errors.LogError(r, err, "Failed to write file to /tmp")
|
if err = storageClient.Upload(r.Context(), remotePath, bytes.NewReader(data), int64(len(data))); err != nil {
|
||||||
errors.WriteError(w, errors.CodeInternal, "Server error", http.StatusInternalServerError)
|
errors.LogError(r, err, "WebDAV upload failed")
|
||||||
|
errors.WriteError(w, errors.CodeInternal, "Failed to upload file to storage", http.StatusInternalServerError)
|
||||||
return
|
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)
|
f, err = db.CreateFile(r.Context(), nil, &userID, header.Filename, storedPath, "file", written)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1399,58 +1334,26 @@ func downloadOrgFileHandler(w http.ResponseWriter, r *http.Request, db *database
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to download from Nextcloud first
|
// ONLY use Nextcloud WebDAV storage
|
||||||
if storageClient != nil {
|
if storageClient == nil {
|
||||||
rel := strings.TrimPrefix(filePath, "/")
|
errors.WriteError(w, errors.CodeInternal, "Storage not configured", http.StatusInternalServerError)
|
||||||
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)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err := os.Open(localPath)
|
rel := strings.TrimPrefix(filePath, "/")
|
||||||
|
remotePath := path.Join("/orgs", orgID.String(), rel)
|
||||||
|
|
||||||
|
reader, size, err := storageClient.Download(r.Context(), remotePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
errors.LogError(r, err, "Failed to download from Nextcloud")
|
||||||
errors.WriteError(w, errors.CodeNotFound, "File not found", http.StatusNotFound)
|
errors.WriteError(w, errors.CodeNotFound, "File not found", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer reader.Close()
|
||||||
info, _ := f.Stat()
|
|
||||||
|
// Set appropriate headers for inline viewing
|
||||||
fileName := path.Base(filePath)
|
fileName := path.Base(filePath)
|
||||||
|
// Determine content type based on file extension
|
||||||
contentType := "application/octet-stream"
|
contentType := "application/octet-stream"
|
||||||
if strings.HasSuffix(strings.ToLower(fileName), ".pdf") {
|
if strings.HasSuffix(strings.ToLower(fileName), ".pdf") {
|
||||||
contentType = "application/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-Disposition", fmt.Sprintf("inline; filename=\"%s\"", fileName))
|
||||||
w.Header().Set("Content-Type", contentType)
|
w.Header().Set("Content-Type", contentType)
|
||||||
if info != nil {
|
if size > 0 {
|
||||||
w.Header().Set("Content-Length", fmt.Sprintf("%d", info.Size()))
|
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
|
// 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
|
// Log the requested file path for debugging
|
||||||
fmt.Printf("[DEBUG] Download request - User: %s, Path: %s\n", userID.String(), filePath)
|
fmt.Printf("[DEBUG] Download request - User: %s, Path: %s\n", userID.String(), filePath)
|
||||||
|
|
||||||
// Try to download from Nextcloud first
|
// ONLY use Nextcloud WebDAV storage
|
||||||
if storageClient != nil {
|
if storageClient == nil {
|
||||||
rel := strings.TrimPrefix(filePath, "/")
|
errors.WriteError(w, errors.CodeInternal, "Storage not configured", http.StatusInternalServerError)
|
||||||
// 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)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err := os.Open(localPath)
|
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 {
|
if err != nil {
|
||||||
|
errors.LogError(r, err, "Failed to download from Nextcloud")
|
||||||
errors.WriteError(w, errors.CodeNotFound, "File not found", http.StatusNotFound)
|
errors.WriteError(w, errors.CodeNotFound, "File not found", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer reader.Close()
|
||||||
info, _ := f.Stat()
|
|
||||||
|
// Set appropriate headers for inline viewing
|
||||||
fileName := path.Base(filePath)
|
fileName := path.Base(filePath)
|
||||||
|
// Determine content type based on file extension
|
||||||
contentType := "application/octet-stream"
|
contentType := "application/octet-stream"
|
||||||
if strings.HasSuffix(strings.ToLower(fileName), ".pdf") {
|
if strings.HasSuffix(strings.ToLower(fileName), ".pdf") {
|
||||||
contentType = "application/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-Disposition", fmt.Sprintf("inline; filename=\"%s\"", fileName))
|
||||||
w.Header().Set("Content-Type", contentType)
|
w.Header().Set("Content-Type", contentType)
|
||||||
if info != nil {
|
if size > 0 {
|
||||||
w.Header().Set("Content-Length", fmt.Sprintf("%d", info.Size()))
|
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