Total rebrand across all Go API source files: - Go module path: casera-api -> honeydue-api - All imports updated (130+ files) - Docker: containers, images, networks renamed - Email templates: support email, noreply, icon URL - Domains: casera.app/mycrib.treytartt.com -> honeyDue.treytartt.com - Bundle IDs: com.tt.casera -> com.tt.honeyDue - IAP product IDs updated - Landing page, admin panel, config defaults - Seeds, CI workflows, Makefile, docs - Database table names preserved (no migration needed) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
409 lines
11 KiB
Go
409 lines
11 KiB
Go
package handlers
|
|
|
|
import (
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"github.com/labstack/echo/v4"
|
|
|
|
"github.com/treytartt/honeydue-api/internal/apperrors"
|
|
"github.com/treytartt/honeydue-api/internal/dto/requests"
|
|
"github.com/treytartt/honeydue-api/internal/i18n"
|
|
"github.com/treytartt/honeydue-api/internal/middleware"
|
|
"github.com/treytartt/honeydue-api/internal/services"
|
|
"github.com/treytartt/honeydue-api/internal/validator"
|
|
)
|
|
|
|
// ResidenceHandler handles residence-related HTTP requests
|
|
type ResidenceHandler struct {
|
|
residenceService *services.ResidenceService
|
|
pdfService *services.PDFService
|
|
emailService *services.EmailService
|
|
pdfReportsEnabled bool
|
|
}
|
|
|
|
// NewResidenceHandler creates a new residence handler
|
|
func NewResidenceHandler(residenceService *services.ResidenceService, pdfService *services.PDFService, emailService *services.EmailService, pdfReportsEnabled bool) *ResidenceHandler {
|
|
return &ResidenceHandler{
|
|
residenceService: residenceService,
|
|
pdfService: pdfService,
|
|
emailService: emailService,
|
|
pdfReportsEnabled: pdfReportsEnabled,
|
|
}
|
|
}
|
|
|
|
// ListResidences handles GET /api/residences/
|
|
func (h *ResidenceHandler) ListResidences(c echo.Context) error {
|
|
user, err := middleware.MustGetAuthUser(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
response, err := h.residenceService.ListResidences(user.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, response)
|
|
}
|
|
|
|
// GetMyResidences handles GET /api/residences/my-residences/
|
|
func (h *ResidenceHandler) GetMyResidences(c echo.Context) error {
|
|
user, err := middleware.MustGetAuthUser(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
userNow := middleware.GetUserNow(c)
|
|
|
|
response, err := h.residenceService.GetMyResidences(user.ID, userNow)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, response)
|
|
}
|
|
|
|
// GetSummary handles GET /api/residences/summary/
|
|
// Returns just the task statistics summary without full residence data
|
|
func (h *ResidenceHandler) GetSummary(c echo.Context) error {
|
|
user, err := middleware.MustGetAuthUser(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
userNow := middleware.GetUserNow(c)
|
|
|
|
summary, err := h.residenceService.GetSummary(user.ID, userNow)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, summary)
|
|
}
|
|
|
|
// GetResidence handles GET /api/residences/:id/
|
|
func (h *ResidenceHandler) GetResidence(c echo.Context) error {
|
|
user, err := middleware.MustGetAuthUser(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
residenceID, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
|
if err != nil {
|
|
return apperrors.BadRequest("error.invalid_residence_id")
|
|
}
|
|
|
|
response, err := h.residenceService.GetResidence(uint(residenceID), user.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, response)
|
|
}
|
|
|
|
// CreateResidence handles POST /api/residences/
|
|
func (h *ResidenceHandler) CreateResidence(c echo.Context) error {
|
|
user, err := middleware.MustGetAuthUser(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var req requests.CreateResidenceRequest
|
|
if err := c.Bind(&req); err != nil {
|
|
return apperrors.BadRequest("error.invalid_request")
|
|
}
|
|
if err := c.Validate(&req); err != nil {
|
|
return c.JSON(http.StatusBadRequest, validator.FormatValidationErrors(err))
|
|
}
|
|
|
|
response, err := h.residenceService.CreateResidence(&req, user.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return c.JSON(http.StatusCreated, response)
|
|
}
|
|
|
|
// UpdateResidence handles PUT/PATCH /api/residences/:id/
|
|
func (h *ResidenceHandler) UpdateResidence(c echo.Context) error {
|
|
user, err := middleware.MustGetAuthUser(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
residenceID, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
|
if err != nil {
|
|
return apperrors.BadRequest("error.invalid_residence_id")
|
|
}
|
|
|
|
var req requests.UpdateResidenceRequest
|
|
if err := c.Bind(&req); err != nil {
|
|
return apperrors.BadRequest("error.invalid_request")
|
|
}
|
|
if err := c.Validate(&req); err != nil {
|
|
return c.JSON(http.StatusBadRequest, validator.FormatValidationErrors(err))
|
|
}
|
|
|
|
response, err := h.residenceService.UpdateResidence(uint(residenceID), user.ID, &req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, response)
|
|
}
|
|
|
|
// DeleteResidence handles DELETE /api/residences/:id/
|
|
func (h *ResidenceHandler) DeleteResidence(c echo.Context) error {
|
|
user, err := middleware.MustGetAuthUser(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
residenceID, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
|
if err != nil {
|
|
return apperrors.BadRequest("error.invalid_residence_id")
|
|
}
|
|
|
|
response, err := h.residenceService.DeleteResidence(uint(residenceID), user.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, response)
|
|
}
|
|
|
|
// GetShareCode handles GET /api/residences/:id/share-code/
|
|
// Returns the active share code for a residence, or null if none exists
|
|
func (h *ResidenceHandler) GetShareCode(c echo.Context) error {
|
|
user, err := middleware.MustGetAuthUser(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
residenceID, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
|
if err != nil {
|
|
return apperrors.BadRequest("error.invalid_residence_id")
|
|
}
|
|
|
|
shareCode, err := h.residenceService.GetShareCode(uint(residenceID), user.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if shareCode == nil {
|
|
return c.JSON(http.StatusOK, map[string]interface{}{"share_code": nil})
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, map[string]interface{}{"share_code": shareCode})
|
|
}
|
|
|
|
// GenerateShareCode handles POST /api/residences/:id/generate-share-code/
|
|
func (h *ResidenceHandler) GenerateShareCode(c echo.Context) error {
|
|
user, err := middleware.MustGetAuthUser(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
residenceID, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
|
if err != nil {
|
|
return apperrors.BadRequest("error.invalid_residence_id")
|
|
}
|
|
|
|
var req requests.GenerateShareCodeRequest
|
|
// Request body is optional
|
|
c.Bind(&req)
|
|
|
|
response, err := h.residenceService.GenerateShareCode(uint(residenceID), user.ID, req.ExpiresInHours)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, response)
|
|
}
|
|
|
|
// GenerateSharePackage handles POST /api/residences/:id/generate-share-package/
|
|
// Returns a share code with metadata for creating a .honeydue package file
|
|
func (h *ResidenceHandler) GenerateSharePackage(c echo.Context) error {
|
|
user, err := middleware.MustGetAuthUser(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
residenceID, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
|
if err != nil {
|
|
return apperrors.BadRequest("error.invalid_residence_id")
|
|
}
|
|
|
|
var req requests.GenerateShareCodeRequest
|
|
// Request body is optional (for expires_in_hours)
|
|
c.Bind(&req)
|
|
|
|
response, err := h.residenceService.GenerateSharePackage(uint(residenceID), user.ID, req.ExpiresInHours)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, response)
|
|
}
|
|
|
|
// JoinWithCode handles POST /api/residences/join-with-code/
|
|
func (h *ResidenceHandler) JoinWithCode(c echo.Context) error {
|
|
user, err := middleware.MustGetAuthUser(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var req requests.JoinWithCodeRequest
|
|
if err := c.Bind(&req); err != nil {
|
|
return apperrors.BadRequest("error.invalid_request")
|
|
}
|
|
if err := c.Validate(&req); err != nil {
|
|
return err
|
|
}
|
|
|
|
response, err := h.residenceService.JoinWithCode(req.Code, user.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, response)
|
|
}
|
|
|
|
// GetResidenceUsers handles GET /api/residences/:id/users/
|
|
func (h *ResidenceHandler) GetResidenceUsers(c echo.Context) error {
|
|
user, err := middleware.MustGetAuthUser(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
residenceID, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
|
if err != nil {
|
|
return apperrors.BadRequest("error.invalid_residence_id")
|
|
}
|
|
|
|
users, err := h.residenceService.GetResidenceUsers(uint(residenceID), user.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, users)
|
|
}
|
|
|
|
// RemoveResidenceUser handles DELETE /api/residences/:id/users/:user_id/
|
|
func (h *ResidenceHandler) RemoveResidenceUser(c echo.Context) error {
|
|
user, err := middleware.MustGetAuthUser(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
residenceID, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
|
if err != nil {
|
|
return apperrors.BadRequest("error.invalid_residence_id")
|
|
}
|
|
|
|
userIDToRemove, err := strconv.ParseUint(c.Param("user_id"), 10, 32)
|
|
if err != nil {
|
|
return apperrors.BadRequest("error.invalid_user_id")
|
|
}
|
|
|
|
err = h.residenceService.RemoveUser(uint(residenceID), uint(userIDToRemove), user.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, map[string]interface{}{"message": i18n.LocalizedMessage(c, "message.user_removed")})
|
|
}
|
|
|
|
// GetResidenceTypes handles GET /api/residences/types/
|
|
func (h *ResidenceHandler) GetResidenceTypes(c echo.Context) error {
|
|
types, err := h.residenceService.GetResidenceTypes()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
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 echo.Context) error {
|
|
if !h.pdfReportsEnabled {
|
|
return apperrors.BadRequest("error.feature_disabled")
|
|
}
|
|
|
|
user, err := middleware.MustGetAuthUser(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
residenceID, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
|
if err != nil {
|
|
return apperrors.BadRequest("error.invalid_residence_id")
|
|
}
|
|
|
|
// Optional request body for email recipient
|
|
var req struct {
|
|
Email string `json:"email"`
|
|
}
|
|
c.Bind(&req)
|
|
|
|
// Generate the report data
|
|
report, err := h.residenceService.GenerateTasksReport(uint(residenceID), user.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// 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 := i18n.LocalizedMessage(c, "message.tasks_report_generated")
|
|
if pdfGenerated && emailSent {
|
|
message = i18n.LocalizedMessageWithData(c, "message.tasks_report_sent", map[string]interface{}{"Email": recipientEmail})
|
|
} else if pdfGenerated && !emailSent {
|
|
message = i18n.LocalizedMessage(c, "message.tasks_report_email_failed")
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, map[string]interface{}{
|
|
"message": message,
|
|
"residence_name": report.ResidenceName,
|
|
"recipient_email": recipientEmail,
|
|
"pdf_generated": pdfGenerated,
|
|
"email_sent": emailSent,
|
|
"report": report,
|
|
})
|
|
}
|