package handlers import ( "mime/multipart" "net/http" "strconv" "strings" "time" "github.com/labstack/echo/v4" "github.com/shopspring/decimal" "github.com/treytartt/casera-api/internal/apperrors" "github.com/treytartt/casera-api/internal/dto/requests" "github.com/treytartt/casera-api/internal/middleware" "github.com/treytartt/casera-api/internal/models" "github.com/treytartt/casera-api/internal/services" ) // DocumentHandler handles document-related HTTP requests type DocumentHandler struct { documentService *services.DocumentService storageService *services.StorageService } // NewDocumentHandler creates a new document handler func NewDocumentHandler(documentService *services.DocumentService, storageService *services.StorageService) *DocumentHandler { return &DocumentHandler{ documentService: documentService, storageService: storageService, } } // ListDocuments handles GET /api/documents/ func (h *DocumentHandler) ListDocuments(c echo.Context) error { user := c.Get(middleware.AuthUserKey).(*models.User) response, err := h.documentService.ListDocuments(user.ID) if err != nil { return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": err.Error()}) } return c.JSON(http.StatusOK, response) } // GetDocument handles GET /api/documents/:id/ func (h *DocumentHandler) GetDocument(c echo.Context) error { user := c.Get(middleware.AuthUserKey).(*models.User) documentID, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { return apperrors.BadRequest("error.invalid_document_id") } response, err := h.documentService.GetDocument(uint(documentID), user.ID) if err != nil { return err } return c.JSON(http.StatusOK, response) } // ListWarranties handles GET /api/documents/warranties/ func (h *DocumentHandler) ListWarranties(c echo.Context) error { user := c.Get(middleware.AuthUserKey).(*models.User) response, err := h.documentService.ListWarranties(user.ID) if err != nil { return c.JSON(http.StatusInternalServerError, map[string]interface{}{"error": err.Error()}) } return c.JSON(http.StatusOK, response) } // CreateDocument handles POST /api/documents/ // Supports both JSON and multipart form data (for file uploads) func (h *DocumentHandler) CreateDocument(c echo.Context) error { user := c.Get(middleware.AuthUserKey).(*models.User) var req requests.CreateDocumentRequest contentType := c.Request().Header.Get("Content-Type") // Check if this is a multipart form request (file upload) if strings.HasPrefix(contentType, "multipart/form-data") { // Parse multipart form if err := c.Request().ParseMultipartForm(32 << 20); err != nil { // 32MB max return apperrors.BadRequest("error.failed_to_parse_form") } // Parse residence_id (required) residenceIDStr := c.FormValue("residence_id") if residenceIDStr == "" { return apperrors.BadRequest("error.residence_id_required") } residenceID, err := strconv.ParseUint(residenceIDStr, 10, 32) if err != nil { return apperrors.BadRequest("error.invalid_residence_id") } req.ResidenceID = uint(residenceID) // Parse title (required) req.Title = c.FormValue("title") if req.Title == "" { return apperrors.BadRequest("error.title_required") } // Parse optional fields req.Description = c.FormValue("description") req.Vendor = c.FormValue("vendor") req.SerialNumber = c.FormValue("serial_number") req.ModelNumber = c.FormValue("model_number") // Parse document_type if docType := c.FormValue("document_type"); docType != "" { dt := models.DocumentType(docType) req.DocumentType = dt } // Parse task_id (optional) if taskIDStr := c.FormValue("task_id"); taskIDStr != "" { if taskID, err := strconv.ParseUint(taskIDStr, 10, 32); err == nil { tid := uint(taskID) req.TaskID = &tid } } // Parse purchase_price (optional) if priceStr := c.FormValue("purchase_price"); priceStr != "" { if price, err := decimal.NewFromString(priceStr); err == nil { req.PurchasePrice = &price } } // Parse purchase_date (optional) if dateStr := c.FormValue("purchase_date"); dateStr != "" { if t, err := time.Parse(time.RFC3339, dateStr); err == nil { req.PurchaseDate = &t } else if t, err := time.Parse("2006-01-02", dateStr); err == nil { req.PurchaseDate = &t } } // Parse expiry_date (optional) if dateStr := c.FormValue("expiry_date"); dateStr != "" { if t, err := time.Parse(time.RFC3339, dateStr); err == nil { req.ExpiryDate = &t } else if t, err := time.Parse("2006-01-02", dateStr); err == nil { req.ExpiryDate = &t } } // Handle file upload (look for "file", "document", or "image" field) var uploadedFile *multipart.FileHeader for _, fieldName := range []string{"file", "document", "image", "images"} { if file, err := c.FormFile(fieldName); err == nil { uploadedFile = file break } } if uploadedFile != nil && h.storageService != nil { result, err := h.storageService.Upload(uploadedFile, "documents") if err != nil { return apperrors.BadRequest("error.failed_to_upload_file") } req.FileURL = result.URL req.FileName = result.FileName req.MimeType = result.MimeType fileSize := result.FileSize req.FileSize = &fileSize } } else { // Standard JSON request if err := c.Bind(&req); err != nil { return apperrors.BadRequest("error.invalid_request") } } response, err := h.documentService.CreateDocument(&req, user.ID) if err != nil { return err } return c.JSON(http.StatusCreated, response) } // UpdateDocument handles PUT/PATCH /api/documents/:id/ func (h *DocumentHandler) UpdateDocument(c echo.Context) error { user := c.Get(middleware.AuthUserKey).(*models.User) documentID, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { return apperrors.BadRequest("error.invalid_document_id") } var req requests.UpdateDocumentRequest if err := c.Bind(&req); err != nil { return apperrors.BadRequest("error.invalid_request") } response, err := h.documentService.UpdateDocument(uint(documentID), user.ID, &req) if err != nil { return err } return c.JSON(http.StatusOK, response) } // DeleteDocument handles DELETE /api/documents/:id/ func (h *DocumentHandler) DeleteDocument(c echo.Context) error { user := c.Get(middleware.AuthUserKey).(*models.User) documentID, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { return apperrors.BadRequest("error.invalid_document_id") } err = h.documentService.DeleteDocument(uint(documentID), user.ID) if err != nil { return err } return c.JSON(http.StatusOK, map[string]interface{}{"message": "Document deleted successfully"}) } // ActivateDocument handles POST /api/documents/:id/activate/ func (h *DocumentHandler) ActivateDocument(c echo.Context) error { user := c.Get(middleware.AuthUserKey).(*models.User) documentID, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { return apperrors.BadRequest("error.invalid_document_id") } response, err := h.documentService.ActivateDocument(uint(documentID), user.ID) if err != nil { return err } return c.JSON(http.StatusOK, map[string]interface{}{"message": "Document activated successfully", "document": response}) } // DeactivateDocument handles POST /api/documents/:id/deactivate/ func (h *DocumentHandler) DeactivateDocument(c echo.Context) error { user := c.Get(middleware.AuthUserKey).(*models.User) documentID, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { return apperrors.BadRequest("error.invalid_document_id") } response, err := h.documentService.DeactivateDocument(uint(documentID), user.ID) if err != nil { return err } return c.JSON(http.StatusOK, map[string]interface{}{"message": "Document deactivated successfully", "document": response}) }