- Update Go module from mycrib-api to casera-api - Update all import statements across 69 Go files - Update admin panel branding (title, sidebar, login form) - Update email templates (subjects, bodies, signatures) - Update PDF report generation branding - Update Docker container names and network - Update config defaults (database name, email sender, APNS topic) - Update README and documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
379 lines
11 KiB
Go
379 lines
11 KiB
Go
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,
|
|
})
|
|
}
|