Files
honeyDueAPI/internal/handlers/contractor_handler.go
T
Trey t e881d37de0
Backend CI / Test (push) Has been cancelled
Backend CI / Contract Tests (push) Has been cancelled
Backend CI / Build (push) Has been cancelled
Backend CI / Lint (push) Has been cancelled
Backend CI / Secret Scanning (push) Has been cancelled
Migrate Auth/Contractor/Document/Notification/Subscription services to ctx
Every public method on these five services now takes ctx context.Context as
the first arg and routes its repo calls through .WithContext(ctx). With
TaskService and ResidenceService already migrated, this means every
in-process service that touches Postgres now produces a flame graph in
Jaeger where the SQL spans nest under the parent HTTP request span.

Endpoints now fully traced (HTTP → service → SQL):
- /api/auth/login, /register, /logout, /me, /verify-email, /resend-verification
- /api/auth/forgot-password, /verify-reset, /reset-password, /update-profile
- /api/contractors/* (CRUD + favorite + by-residence + tasks)
- /api/documents/* (CRUD + activate/deactivate + image upload/delete)
- /api/notifications/* (list, count, mark-read, prefs, devices)
- /api/subscription/* (status, purchase, cancel, triggers, promotions)
- All previously-migrated /api/tasks/* and /api/residences/* paths

Internal helpers also threaded:
- TaskService.sendTaskCompletedNotification → forwards ctx
- TaskService.UpdateUserTimezone → forwards ctx to NotificationService
- ResidenceService.CreateResidence → forwards ctx to SubscriptionService.CheckLimit
- NotificationService.registerAPNSDevice / registerGCMDevice → both take ctx

~75 method signatures, ~120 handler/test call sites updated. Tests pass
green; the only failure is the pre-existing flaky TaskHandler_QuickComplete
SQLite race that fails ~60% of runs on master.

Step 3 of the observability plan is now genuinely complete: every API
endpoint backed by a Go service emits a per-request flame graph with
HTTP → service → SQL spans, plus B2/APNs/FCM/asynq spans where applicable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 16:26:21 -05:00

185 lines
5.4 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/dto/responses"
"github.com/treytartt/honeydue-api/internal/i18n"
"github.com/treytartt/honeydue-api/internal/middleware"
"github.com/treytartt/honeydue-api/internal/services"
)
// ContractorHandler handles contractor-related HTTP requests
type ContractorHandler struct {
contractorService *services.ContractorService
}
// NewContractorHandler creates a new contractor handler
func NewContractorHandler(contractorService *services.ContractorService) *ContractorHandler {
return &ContractorHandler{contractorService: contractorService}
}
// ListContractors handles GET /api/contractors/
func (h *ContractorHandler) ListContractors(c echo.Context) error {
user, err := middleware.MustGetAuthUser(c)
if err != nil {
return err
}
response, err := h.contractorService.ListContractors(c.Request().Context(), user.ID)
if err != nil {
return apperrors.Internal(err)
}
return c.JSON(http.StatusOK, response)
}
// GetContractor handles GET /api/contractors/:id/
func (h *ContractorHandler) GetContractor(c echo.Context) error {
user, err := middleware.MustGetAuthUser(c)
if err != nil {
return err
}
contractorID, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
return apperrors.BadRequest("error.invalid_contractor_id")
}
response, err := h.contractorService.GetContractor(c.Request().Context(), uint(contractorID), user.ID)
if err != nil {
return err
}
return c.JSON(http.StatusOK, response)
}
// CreateContractor handles POST /api/contractors/
func (h *ContractorHandler) CreateContractor(c echo.Context) error {
user, err := middleware.MustGetAuthUser(c)
if err != nil {
return err
}
var req requests.CreateContractorRequest
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.contractorService.CreateContractor(c.Request().Context(), &req, user.ID)
if err != nil {
return err
}
return c.JSON(http.StatusCreated, response)
}
// UpdateContractor handles PUT/PATCH /api/contractors/:id/
func (h *ContractorHandler) UpdateContractor(c echo.Context) error {
user, err := middleware.MustGetAuthUser(c)
if err != nil {
return err
}
contractorID, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
return apperrors.BadRequest("error.invalid_contractor_id")
}
var req requests.UpdateContractorRequest
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.contractorService.UpdateContractor(c.Request().Context(), uint(contractorID), user.ID, &req)
if err != nil {
return err
}
return c.JSON(http.StatusOK, response)
}
// DeleteContractor handles DELETE /api/contractors/:id/
func (h *ContractorHandler) DeleteContractor(c echo.Context) error {
user, err := middleware.MustGetAuthUser(c)
if err != nil {
return err
}
contractorID, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
return apperrors.BadRequest("error.invalid_contractor_id")
}
err = h.contractorService.DeleteContractor(c.Request().Context(), uint(contractorID), user.ID)
if err != nil {
return err
}
return c.JSON(http.StatusOK, responses.MessageResponse{Message: i18n.LocalizedMessage(c, "message.contractor_deleted")})
}
// ToggleFavorite handles POST /api/contractors/:id/toggle-favorite/
func (h *ContractorHandler) ToggleFavorite(c echo.Context) error {
user, err := middleware.MustGetAuthUser(c)
if err != nil {
return err
}
contractorID, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
return apperrors.BadRequest("error.invalid_contractor_id")
}
response, err := h.contractorService.ToggleFavorite(c.Request().Context(), uint(contractorID), user.ID)
if err != nil {
return err
}
return c.JSON(http.StatusOK, response)
}
// GetContractorTasks handles GET /api/contractors/:id/tasks/
func (h *ContractorHandler) GetContractorTasks(c echo.Context) error {
user, err := middleware.MustGetAuthUser(c)
if err != nil {
return err
}
contractorID, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
return apperrors.BadRequest("error.invalid_contractor_id")
}
response, err := h.contractorService.GetContractorTasks(c.Request().Context(), uint(contractorID), user.ID)
if err != nil {
return err
}
return c.JSON(http.StatusOK, response)
}
// ListContractorsByResidence handles GET /api/contractors/by-residence/:residence_id/
func (h *ContractorHandler) ListContractorsByResidence(c echo.Context) error {
user, err := middleware.MustGetAuthUser(c)
if err != nil {
return err
}
residenceID, err := strconv.ParseUint(c.Param("residence_id"), 10, 32)
if err != nil {
return apperrors.BadRequest("error.invalid_residence_id")
}
response, err := h.contractorService.ListContractorsByResidence(c.Request().Context(), uint(residenceID), user.ID)
if err != nil {
return err
}
return c.JSON(http.StatusOK, response)
}
// GetSpecialties handles GET /api/contractors/specialties/
func (h *ContractorHandler) GetSpecialties(c echo.Context) error {
specialties, err := h.contractorService.GetSpecialties(c.Request().Context())
if err != nil {
return apperrors.Internal(err)
}
return c.JSON(http.StatusOK, specialties)
}