slate-test-1770505673/services/preferences-api/internal/api/handlers/preference.go
rdev-worker 868f79c67a
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
build: /implement-feature user-preferences
2026-02-07 23:47:42 +00:00

109 lines
2.9 KiB
Go

package handlers
import (
"errors"
"net/http"
"github.com/go-chi/chi/v5"
"github.com/google/uuid"
"git.threesix.ai/jordan/slate-test-1770505673/pkg/app"
"git.threesix.ai/jordan/slate-test-1770505673/pkg/auth"
"git.threesix.ai/jordan/slate-test-1770505673/pkg/httperror"
"git.threesix.ai/jordan/slate-test-1770505673/pkg/httpresponse"
"git.threesix.ai/jordan/slate-test-1770505673/pkg/logging"
"git.threesix.ai/jordan/slate-test-1770505673/services/preferences-api/internal/domain"
"git.threesix.ai/jordan/slate-test-1770505673/services/preferences-api/internal/service"
)
// Preference handles HTTP requests for user preference resources.
type Preference struct {
svc *service.PreferenceService
logger *logging.Logger
}
// NewPreference creates a new Preference handler with injected dependencies.
func NewPreference(svc *service.PreferenceService, logger *logging.Logger) *Preference {
return &Preference{
svc: svc,
logger: logger.WithComponent("PreferenceHandler"),
}
}
// Get returns all preferences for a user.
func (h *Preference) Get(w http.ResponseWriter, r *http.Request) error {
userID := chi.URLParam(r, "user_id")
if _, err := uuid.Parse(userID); err != nil {
return httperror.BadRequest("invalid user ID format")
}
if err := h.checkOwnership(r, userID); err != nil {
return err
}
prefs, err := h.svc.Get(r.Context(), userID)
if err != nil {
return err
}
httpresponse.OK(w, r, prefs)
return nil
}
// Update creates or updates preferences for a user.
func (h *Preference) Update(w http.ResponseWriter, r *http.Request) error {
userID := chi.URLParam(r, "user_id")
if _, err := uuid.Parse(userID); err != nil {
return httperror.BadRequest("invalid user ID format")
}
if err := h.checkOwnership(r, userID); err != nil {
return err
}
var prefs map[string]string
if err := app.Bind(r, &prefs); err != nil {
return err
}
if len(prefs) == 0 {
return httperror.BadRequest("request body is required")
}
result, err := h.svc.Upsert(r.Context(), userID, prefs)
if err != nil {
return mapDomainError(err)
}
httpresponse.OK(w, r, result)
return nil
}
// checkOwnership verifies that the authenticated user matches the requested user_id.
func (h *Preference) checkOwnership(r *http.Request, userID string) error {
user := auth.GetUser(r.Context())
if user == nil {
return httperror.Unauthorized("authentication required")
}
if user.ID != userID {
return httperror.Forbidden("cannot access preferences for another user")
}
return nil
}
// mapDomainError converts domain errors to HTTP errors.
func mapDomainError(err error) error {
switch {
case errors.Is(err, domain.ErrUnknownKey):
return httperror.BadRequest(err.Error())
case errors.Is(err, domain.ErrInvalidValue):
return httperror.BadRequest(err.Error())
case errors.Is(err, domain.ErrForbidden):
return httperror.Forbidden(err.Error())
default:
return err
}
}