dealroom/internal/handler/files.go

165 lines
4.5 KiB
Go

package handler
import (
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"strings"
)
const uploadsDir = "data/uploads"
func (h *Handler) handleFileUpload(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
profile := getProfile(r.Context())
err := r.ParseMultipartForm(32 << 20) // 32MB max
if err != nil {
http.Error(w, "Error parsing form", 400)
return
}
dealID := r.FormValue("deal_id")
folderID := r.FormValue("folder_id")
requestItemID := r.FormValue("request_item_id")
if dealID == "" {
http.Error(w, "Deal ID is required", 400)
return
}
file, header, err := r.FormFile("file")
if err != nil {
http.Error(w, "File is required", 400)
return
}
defer file.Close()
// Create upload directory
dealDir := filepath.Join(uploadsDir, dealID)
os.MkdirAll(dealDir, 0755)
// Generate unique filename
fileID := generateID("file")
ext := filepath.Ext(header.Filename)
storageName := fileID + ext
storagePath := filepath.Join(dealDir, storageName)
// Save file to disk
dst, err := os.Create(storagePath)
if err != nil {
http.Error(w, "Error saving file", 500)
return
}
defer dst.Close()
io.Copy(dst, file)
// Detect MIME type
mimeType := header.Header.Get("Content-Type")
if mimeType == "" {
mimeType = "application/octet-stream"
}
// Insert file record
_, err = h.db.Exec(`INSERT INTO files (id, deal_id, folder_id, name, file_size, mime_type, status, storage_path, uploaded_by) VALUES (?, ?, ?, ?, ?, ?, 'uploaded', ?, ?)`,
fileID, dealID, folderID, header.Filename, header.Size, mimeType, storagePath, profile.ID)
if err != nil {
os.Remove(storagePath)
http.Error(w, "Error saving file record", 500)
return
}
// Link to request item if specified
if requestItemID != "" {
var existing string
h.db.QueryRow("SELECT linked_file_ids FROM diligence_requests WHERE id = ?", requestItemID).Scan(&existing)
if existing == "" {
existing = fileID
} else {
existing = existing + "," + fileID
}
h.db.Exec("UPDATE diligence_requests SET linked_file_ids = ? WHERE id = ?", existing, requestItemID)
}
// Log activity
h.logActivity(dealID, profile.ID, profile.OrganizationID, "upload", "file", header.Filename, fileID)
http.Redirect(w, r, "/deals/"+dealID, http.StatusSeeOther)
}
func (h *Handler) handleFileDelete(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
profile := getProfile(r.Context())
if profile.Role != "owner" && profile.Role != "admin" {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
fileID := r.FormValue("file_id")
dealID := r.FormValue("deal_id")
if fileID == "" {
http.Error(w, "Missing file ID", 400)
return
}
// Get storage path and delete from disk
var storagePath string
h.db.QueryRow("SELECT COALESCE(storage_path, '') FROM files WHERE id = ?", fileID).Scan(&storagePath)
if storagePath != "" {
os.Remove(storagePath)
}
h.db.Exec("DELETE FROM files WHERE id = ? AND deal_id = ?", fileID, dealID)
http.Redirect(w, r, "/deals/"+dealID, http.StatusSeeOther)
}
func (h *Handler) handleFileDownload(w http.ResponseWriter, r *http.Request) {
profile := getProfile(r.Context())
// Parse: /deals/files/download/{fileID}
fileID := strings.TrimPrefix(r.URL.Path, "/deals/files/download/")
if fileID == "" {
http.Error(w, "Missing file ID", 400)
return
}
var name, storagePath, dealID string
err := h.db.QueryRow("SELECT name, COALESCE(storage_path, ''), deal_id FROM files WHERE id = ?", fileID).Scan(&name, &storagePath, &dealID)
if err != nil {
http.NotFound(w, r)
return
}
if storagePath == "" || !fileExists(storagePath) {
http.Error(w, "File not found on disk", 404)
return
}
// Log download activity
h.logActivity(dealID, profile.ID, profile.OrganizationID, "download", "file", name, fileID)
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, name))
http.ServeFile(w, r, storagePath)
}
func (h *Handler) logActivity(dealID, userID, orgID, actType, resourceType, resourceName, resourceID string) {
id := generateID("act")
h.db.Exec(`INSERT INTO deal_activity (id, organization_id, deal_id, user_id, activity_type, resource_type, resource_name, resource_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
id, orgID, dealID, userID, actType, resourceType, resourceName, resourceID)
}
func fileExists(path string) bool {
_, err := os.Stat(path)
return err == nil
}