package handlers import ( "errors" "net/http" "strconv" "github.com/gin-gonic/gin" "github.com/treytartt/mycrib-api/internal/dto/requests" "github.com/treytartt/mycrib-api/internal/middleware" "github.com/treytartt/mycrib-api/internal/models" "github.com/treytartt/mycrib-api/internal/services" ) // ResidenceHandler handles residence-related HTTP requests type ResidenceHandler struct { residenceService *services.ResidenceService } // NewResidenceHandler creates a new residence handler func NewResidenceHandler(residenceService *services.ResidenceService) *ResidenceHandler { return &ResidenceHandler{ residenceService: residenceService, } } // 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 optionally 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 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 } c.JSON(http.StatusOK, gin.H{ "message": "Tasks report generated successfully", "report": report, }) }