package handlers import ( "errors" "net/http" "strconv" "github.com/gin-gonic/gin" "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" ) // ResidenceHandler handles residence-related HTTP requests type ResidenceHandler struct { residenceService *services.ResidenceService pdfService *services.PDFService emailService *services.EmailService } // NewResidenceHandler creates a new residence handler func NewResidenceHandler(residenceService *services.ResidenceService, pdfService *services.PDFService, emailService *services.EmailService) *ResidenceHandler { return &ResidenceHandler{ residenceService: residenceService, pdfService: pdfService, emailService: emailService, } } // ListResidences handles GET /api/residences/ func (h *ResidenceHandler) ListResidences(c *gin.Context) { user := c.MustGet(middleware.AuthUserKey).(*models.User) response, err := h.residenceService.ListResidences(user.ID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, response) } // GetMyResidences handles GET /api/residences/my-residences/ func (h *ResidenceHandler) GetMyResidences(c *gin.Context) { user := c.MustGet(middleware.AuthUserKey).(*models.User) response, err := h.residenceService.GetMyResidences(user.ID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, response) } // GetResidence handles GET /api/residences/:id/ func (h *ResidenceHandler) GetResidence(c *gin.Context) { user := c.MustGet(middleware.AuthUserKey).(*models.User) residenceID, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid residence ID"}) return } response, err := h.residenceService.GetResidence(uint(residenceID), user.ID) if err != nil { switch { case errors.Is(err, services.ErrResidenceNotFound): c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) case errors.Is(err, services.ErrResidenceAccessDenied): c.JSON(http.StatusForbidden, gin.H{"error": err.Error()}) default: c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) } return } c.JSON(http.StatusOK, response) } // CreateResidence handles POST /api/residences/ func (h *ResidenceHandler) CreateResidence(c *gin.Context) { user := c.MustGet(middleware.AuthUserKey).(*models.User) var req requests.CreateResidenceRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } response, err := h.residenceService.CreateResidence(&req, user.ID) if err != nil { if errors.Is(err, services.ErrPropertiesLimitReached) { c.JSON(http.StatusForbidden, gin.H{"error": err.Error()}) return } c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusCreated, response) } // UpdateResidence handles PUT/PATCH /api/residences/:id/ func (h *ResidenceHandler) UpdateResidence(c *gin.Context) { user := c.MustGet(middleware.AuthUserKey).(*models.User) residenceID, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid residence ID"}) return } var req requests.UpdateResidenceRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } response, err := h.residenceService.UpdateResidence(uint(residenceID), user.ID, &req) if err != nil { switch { case errors.Is(err, services.ErrResidenceNotFound): c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) case errors.Is(err, services.ErrNotResidenceOwner): c.JSON(http.StatusForbidden, gin.H{"error": err.Error()}) default: c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) } return } c.JSON(http.StatusOK, response) } // DeleteResidence handles DELETE /api/residences/:id/ func (h *ResidenceHandler) DeleteResidence(c *gin.Context) { user := c.MustGet(middleware.AuthUserKey).(*models.User) residenceID, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid residence ID"}) return } err = h.residenceService.DeleteResidence(uint(residenceID), user.ID) if err != nil { switch { case errors.Is(err, services.ErrResidenceNotFound): c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) case errors.Is(err, services.ErrNotResidenceOwner): c.JSON(http.StatusForbidden, gin.H{"error": err.Error()}) default: c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) } return } c.JSON(http.StatusOK, gin.H{"message": "Residence deleted successfully"}) } // GenerateShareCode handles POST /api/residences/:id/generate-share-code/ func (h *ResidenceHandler) GenerateShareCode(c *gin.Context) { user := c.MustGet(middleware.AuthUserKey).(*models.User) residenceID, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid residence ID"}) return } var req requests.GenerateShareCodeRequest // Request body is optional c.ShouldBindJSON(&req) response, err := h.residenceService.GenerateShareCode(uint(residenceID), user.ID, req.ExpiresInHours) if err != nil { switch { case errors.Is(err, services.ErrResidenceNotFound): c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) case errors.Is(err, services.ErrNotResidenceOwner): c.JSON(http.StatusForbidden, gin.H{"error": err.Error()}) default: c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) } return } c.JSON(http.StatusOK, response) } // JoinWithCode handles POST /api/residences/join-with-code/ func (h *ResidenceHandler) JoinWithCode(c *gin.Context) { user := c.MustGet(middleware.AuthUserKey).(*models.User) var req requests.JoinWithCodeRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } response, err := h.residenceService.JoinWithCode(req.Code, user.ID) if err != nil { switch { case errors.Is(err, services.ErrShareCodeInvalid): c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) case errors.Is(err, services.ErrShareCodeExpired): c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) case errors.Is(err, services.ErrUserAlreadyMember): c.JSON(http.StatusConflict, gin.H{"error": err.Error()}) default: c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) } return } c.JSON(http.StatusOK, response) } // GetResidenceUsers handles GET /api/residences/:id/users/ func (h *ResidenceHandler) GetResidenceUsers(c *gin.Context) { user := c.MustGet(middleware.AuthUserKey).(*models.User) residenceID, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid residence ID"}) return } users, err := h.residenceService.GetResidenceUsers(uint(residenceID), user.ID) if err != nil { switch { case errors.Is(err, services.ErrResidenceNotFound): c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) case errors.Is(err, services.ErrResidenceAccessDenied): c.JSON(http.StatusForbidden, gin.H{"error": err.Error()}) default: c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) } return } c.JSON(http.StatusOK, users) } // RemoveResidenceUser handles DELETE /api/residences/:id/users/:user_id/ func (h *ResidenceHandler) RemoveResidenceUser(c *gin.Context) { user := c.MustGet(middleware.AuthUserKey).(*models.User) residenceID, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid residence ID"}) return } userIDToRemove, err := strconv.ParseUint(c.Param("user_id"), 10, 32) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"}) return } err = h.residenceService.RemoveUser(uint(residenceID), uint(userIDToRemove), user.ID) if err != nil { switch { case errors.Is(err, services.ErrResidenceNotFound): c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) case errors.Is(err, services.ErrNotResidenceOwner): c.JSON(http.StatusForbidden, gin.H{"error": err.Error()}) case errors.Is(err, services.ErrCannotRemoveOwner): c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) default: c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) } return } c.JSON(http.StatusOK, gin.H{"message": "User removed from residence"}) } // GetResidenceTypes handles GET /api/residences/types/ func (h *ResidenceHandler) GetResidenceTypes(c *gin.Context) { types, err := h.residenceService.GetResidenceTypes() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, types) } // GenerateTasksReport handles POST /api/residences/:id/generate-tasks-report/ // Generates a PDF report of tasks for the residence and emails it func (h *ResidenceHandler) GenerateTasksReport(c *gin.Context) { user := c.MustGet(middleware.AuthUserKey).(*models.User) residenceID, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid residence ID"}) return } // Optional request body for email recipient var req struct { Email string `json:"email"` } c.ShouldBindJSON(&req) // Generate the report data report, err := h.residenceService.GenerateTasksReport(uint(residenceID), user.ID) if err != nil { switch { case errors.Is(err, services.ErrResidenceNotFound): c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) case errors.Is(err, services.ErrResidenceAccessDenied): c.JSON(http.StatusForbidden, gin.H{"error": err.Error()}) default: c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) } return } // Determine recipient email recipientEmail := req.Email if recipientEmail == "" { recipientEmail = user.Email } // Get recipient name recipientName := user.FirstName if recipientName == "" { recipientName = user.Username } // Generate PDF if PDF service is available var pdfGenerated bool var emailSent bool if h.pdfService != nil && h.emailService != nil { pdfData, pdfErr := h.pdfService.GenerateTasksReportPDF(report) if pdfErr == nil { pdfGenerated = true // Send email with PDF attachment emailErr := h.emailService.SendTasksReportEmail( recipientEmail, recipientName, report.ResidenceName, report.TotalTasks, report.Completed, report.Pending, report.Overdue, pdfData, ) if emailErr == nil { emailSent = true } } } // Build response message message := "Tasks report generated successfully" if pdfGenerated && emailSent { message = "Tasks report generated and sent to " + recipientEmail } else if pdfGenerated && !emailSent { message = "Tasks report generated but email could not be sent" } c.JSON(http.StatusOK, gin.H{ "message": message, "residence_name": report.ResidenceName, "recipient_email": recipientEmail, "pdf_generated": pdfGenerated, "email_sent": emailSent, "report": report, }) }