package attachments import ( "database/sql" "encoding/json" "fmt" "io" "net/http" "os" "path/filepath" "strconv" "time" "code.springupsoftware.com/cmc/cmc-sales/internal/cmc/db" "github.com/gorilla/mux" ) type AttachmentHandler struct { queries *db.Queries } func NewAttachmentHandler(queries *db.Queries) *AttachmentHandler { return &AttachmentHandler{queries: queries} } func (h *AttachmentHandler) List(w http.ResponseWriter, r *http.Request) { limit := 50 offset := 0 if l := r.URL.Query().Get("limit"); l != "" { if val, err := strconv.Atoi(l); err == nil { limit = val } } if o := r.URL.Query().Get("offset"); o != "" { if val, err := strconv.Atoi(o); err == nil { offset = val } } attachments, err := h.queries.ListAttachments(r.Context(), db.ListAttachmentsParams{ Limit: int32(limit), Offset: int32(offset), }) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(attachments) } func (h *AttachmentHandler) Archived(w http.ResponseWriter, r *http.Request) { limit := 50 offset := 0 if l := r.URL.Query().Get("limit"); l != "" { if val, err := strconv.Atoi(l); err == nil { limit = val } } if o := r.URL.Query().Get("offset"); o != "" { if val, err := strconv.Atoi(o); err == nil { offset = val } } attachments, err := h.queries.ListArchivedAttachments(r.Context(), db.ListArchivedAttachmentsParams{ Limit: int32(limit), Offset: int32(offset), }) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(attachments) } func (h *AttachmentHandler) Get(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) id, err := strconv.Atoi(vars["id"]) if err != nil { http.Error(w, "Invalid attachment ID", http.StatusBadRequest) return } attachment, err := h.queries.GetAttachment(r.Context(), int32(id)) if err != nil { if err == sql.ErrNoRows { http.Error(w, "Attachment not found", http.StatusNotFound) return } http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(attachment) } func (h *AttachmentHandler) Create(w http.ResponseWriter, r *http.Request) { // Parse multipart form err := r.ParseMultipartForm(32 << 20) // 32MB max if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } // Get file from form - try both field names (CakePHP format and plain) file, handler, err := r.FormFile("data[Attachment][file]") if err != nil { // Try plain "file" field name as fallback file, handler, err = r.FormFile("file") if err != nil { http.Error(w, "No file uploaded", http.StatusBadRequest) return } } defer file.Close() // Generate unique filename ext := filepath.Ext(handler.Filename) filename := fmt.Sprintf("%d_%s%s", time.Now().Unix(), handler.Filename[:len(handler.Filename)-len(ext)], ext) // Create attachments directory if it doesn't exist attachDir := "webroot/attachments_files" if err := os.MkdirAll(attachDir, 0755); err != nil { http.Error(w, "Failed to create attachments directory", http.StatusInternalServerError) return } // Save file to disk filePath := filepath.Join(attachDir, filename) dst, err := os.Create(filePath) if err != nil { http.Error(w, "Failed to save file", http.StatusInternalServerError) return } defer dst.Close() if _, err := io.Copy(dst, file); err != nil { http.Error(w, "Failed to save file", http.StatusInternalServerError) return } // Parse principle_id - try CakePHP format first, then fallback principleID := 1 // Default pid := r.FormValue("data[Attachment][principle_id]") if pid == "" { pid = r.FormValue("principle_id") } if pid != "" { if id, err := strconv.Atoi(pid); err == nil { principleID = id } } // Get other form values - try CakePHP format first, then fallback name := r.FormValue("data[Attachment][name]") if name == "" { name = r.FormValue("name") } description := r.FormValue("data[Attachment][description]") if description == "" { description = r.FormValue("description") } // Create database record // Store path in PHP format: /var/www/cmc-sales/app/webroot/attachments_files/filename phpPath := "/var/www/cmc-sales/app/webroot/attachments_files/" + filename params := db.CreateAttachmentParams{ PrincipleID: int32(principleID), Name: name, Filename: handler.Filename, File: phpPath, // Store PHP container path for compatibility Type: handler.Header.Get("Content-Type"), Size: int32(handler.Size), Description: description, } if params.Name == "" { params.Name = handler.Filename } result, err := h.queries.CreateAttachment(r.Context(), params) if err != nil { // Clean up file on error os.Remove(filePath) http.Error(w, err.Error(), http.StatusInternalServerError) return } id, err := result.LastInsertId() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } // If HTMX request, return success message if r.Header.Get("HX-Request") == "true" { w.Header().Set("Content-Type", "text/html") w.Write([]byte(`
Attachment uploaded successfully
`)) return } // JSON response for API w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(map[string]interface{}{ "id": id, }) } func (h *AttachmentHandler) Update(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) id, err := strconv.Atoi(vars["id"]) if err != nil { http.Error(w, "Invalid attachment ID", http.StatusBadRequest) return } var params db.UpdateAttachmentParams if r.Header.Get("Content-Type") == "application/json" { if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } } else { // Handle form data if err := r.ParseForm(); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } params = db.UpdateAttachmentParams{ Name: r.FormValue("name"), Description: r.FormValue("description"), } } params.ID = int32(id) if err := h.queries.UpdateAttachment(r.Context(), params); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.WriteHeader(http.StatusNoContent) } func (h *AttachmentHandler) Delete(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) id, err := strconv.Atoi(vars["id"]) if err != nil { http.Error(w, "Invalid attachment ID", http.StatusBadRequest) return } // Soft delete (archive) if err := h.queries.DeleteAttachment(r.Context(), int32(id)); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.WriteHeader(http.StatusNoContent) }