package handlers import ( "errors" "net/http" "time" "github.com/go-chi/chi/v5" "github.com/google/uuid" "git.threesix.ai/jordan/slack5-1770544098/pkg/app" "git.threesix.ai/jordan/slack5-1770544098/pkg/auth" "git.threesix.ai/jordan/slack5-1770544098/pkg/httperror" "git.threesix.ai/jordan/slack5-1770544098/pkg/httpresponse" "git.threesix.ai/jordan/slack5-1770544098/pkg/logging" "git.threesix.ai/jordan/slack5-1770544098/services/preferences-api/internal/domain" "git.threesix.ai/jordan/slack5-1770544098/services/preferences-api/internal/service" ) // Preferences handles HTTP requests for user preferences. type Preferences struct { svc *service.PreferencesService logger *logging.Logger } // NewPreferences creates a new Preferences handler with injected dependencies. func NewPreferences(svc *service.PreferencesService, logger *logging.Logger) *Preferences { return &Preferences{ svc: svc, logger: logger.WithComponent("PreferencesHandler"), } } // UpdatePreferencesRequest is the request body for updating preferences. type UpdatePreferencesRequest struct { Preferences map[string]any `json:"preferences" validate:"required"` } // PreferencesResponse is the response DTO for user preferences. type PreferencesResponse struct { UserID string `json:"user_id"` Preferences map[string]any `json:"preferences"` UpdatedAt *time.Time `json:"updated_at"` } // Get returns preferences for a user. func (h *Preferences) 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, toPreferencesResponse(prefs)) return nil } // Update creates or updates preferences for a user. func (h *Preferences) 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") } var req UpdatePreferencesRequest if err := app.BindAndValidate(r, &req); err != nil { return err } if err := h.checkOwnership(r, userID); err != nil { return err } prefs, err := h.svc.Update(r.Context(), userID, req.Preferences) if err != nil { return mapPreferencesDomainError(err) } httpresponse.OK(w, r, toPreferencesResponse(prefs)) return nil } // checkOwnership verifies the authenticated user owns the requested resource. func (h *Preferences) checkOwnership(r *http.Request, userID string) error { user := auth.MustGetUser(r.Context()) if user.ID != userID { return httperror.Forbidden("access denied") } return nil } // toPreferencesResponse converts a domain UserPreferences to an API response. func toPreferencesResponse(p *domain.UserPreferences) PreferencesResponse { resp := PreferencesResponse{ UserID: p.UserID, Preferences: p.Preferences, } if !p.UpdatedAt.IsZero() { t := p.UpdatedAt resp.UpdatedAt = &t } return resp } // mapPreferencesDomainError converts domain errors to HTTP errors. func mapPreferencesDomainError(err error) error { switch { case errors.Is(err, domain.ErrInvalidPreferenceKey): return httperror.BadRequest(err.Error()) case errors.Is(err, domain.ErrInvalidPreferenceValue): return httperror.BadRequest(err.Error()) default: return err } }