package handlers import ( "errors" "net/http" "github.com/go-chi/chi/v5" "github.com/google/uuid" "git.threesix.ai/jordan/slack5-1770603014/pkg/app" "git.threesix.ai/jordan/slack5-1770603014/pkg/httperror" "git.threesix.ai/jordan/slack5-1770603014/pkg/httpresponse" "git.threesix.ai/jordan/slack5-1770603014/pkg/logging" "git.threesix.ai/jordan/slack5-1770603014/services/preferences-api/internal/domain" "git.threesix.ai/jordan/slack5-1770603014/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 PreferencesInput `json:"preferences" validate:"required"` } // PreferencesInput uses pointers to distinguish "not provided" from zero values. type PreferencesInput struct { Theme *string `json:"theme,omitempty"` Language *string `json:"language,omitempty"` Notifications *NotificationsInput `json:"notifications,omitempty"` } // NotificationsInput uses pointers for partial update semantics. type NotificationsInput struct { Email *bool `json:"email,omitempty"` Push *bool `json:"push,omitempty"` Digest *string `json:"digest,omitempty"` } // PreferencesResponse is the response for preferences endpoints. type PreferencesResponse struct { UserID string `json:"user_id"` Preferences domain.Preferences `json:"preferences"` UpdatedAt string `json:"updated_at"` } // toPreferencesResponse converts a domain UserPreferences to an API response. func toPreferencesResponse(p *domain.UserPreferences) PreferencesResponse { return PreferencesResponse{ UserID: p.UserID.String(), Preferences: p.Preferences, UpdatedAt: p.UpdatedAt.Format("2006-01-02T15:04:05Z"), } } // 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") } prefs, err := h.svc.GetPreferences(r.Context(), domain.UserID(userID)) if err != nil { return err } httpresponse.OK(w, r, toPreferencesResponse(prefs)) return nil } // Upsert creates or updates preferences for a user. func (h *Preferences) Upsert(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 } input := service.UpdateInput{ Theme: req.Preferences.Theme, Language: req.Preferences.Language, } if req.Preferences.Notifications != nil { input.Notifications = &service.NotificationsInput{ Email: req.Preferences.Notifications.Email, Push: req.Preferences.Notifications.Push, Digest: req.Preferences.Notifications.Digest, } } prefs, err := h.svc.UpdatePreferences(r.Context(), domain.UserID(userID), input) if err != nil { return mapPreferencesDomainError(err) } httpresponse.OK(w, r, toPreferencesResponse(prefs)) return nil } // mapPreferencesDomainError converts domain errors to HTTP errors. func mapPreferencesDomainError(err error) error { switch { case errors.Is(err, domain.ErrInvalidTheme): return httperror.BadRequest(err.Error()) case errors.Is(err, domain.ErrInvalidLanguage): return httperror.BadRequest(err.Error()) case errors.Is(err, domain.ErrInvalidDigest): return httperror.BadRequest(err.Error()) default: return err } }