package handlers import ( "encoding/json" "net/http" "github.com/go-chi/chi/v5" apimw "github.com/Silo-Server/silo-server/internal/api/middleware" "github.com/Silo-Server/silo-server/internal/userstore" ) // SubtitlePrefHandler handles per-series subtitle preference endpoints. type SubtitlePrefHandler struct { storeProvider userstore.UserStoreProvider } // NewSubtitlePrefHandler creates a new SubtitlePrefHandler. func NewSubtitlePrefHandler(provider userstore.UserStoreProvider) *SubtitlePrefHandler { return &SubtitlePrefHandler{storeProvider: provider} } // --- Request/Response types --- type setSubtitlePrefRequest struct { SubtitleLanguage string `json:"subtitle_language"` SubtitleTrackIndex int `json:"subtitle_track_index"` ExternalSubtitlePath string `json:"external_subtitle_path,omitempty"` SubtitleMode string `json:"subtitle_mode"` TrackSignature *userstore.SubtitleTrackSignature `json:"track_signature,omitempty"` ShowForcedSubtitles *bool `json:"show_forced_subtitles,omitempty"` } type subtitlePrefResponse struct { ProfileID string `json:"profile_id"` SeriesID string `json:"series_id"` SubtitleLanguage string `json:"subtitle_language"` SubtitleTrackIndex int `json:"subtitle_track_index"` ExternalSubtitlePath string `json:"external_subtitle_path,omitempty"` SubtitleMode string `json:"subtitle_mode"` TrackSignature *userstore.SubtitleTrackSignature `json:"track_signature,omitempty"` ShowForcedSubtitles *bool `json:"show_forced_subtitles,omitempty"` UpdatedAt string `json:"updated_at"` } // --- Handler methods --- // HandleGetSubtitlePref handles GET /subtitle-prefs/{series_id}. func (h *SubtitlePrefHandler) HandleGetSubtitlePref(w http.ResponseWriter, r *http.Request) { userID := apimw.GetUserID(r.Context()) profileID := apimw.GetProfileID(r.Context()) seriesID := chi.URLParam(r, "series_id") if seriesID == "" { writeError(w, http.StatusBadRequest, "bad_request", "Series ID is required") return } store, err := h.storeProvider.ForUser(r.Context(), userID) if err != nil { writeError(w, http.StatusInternalServerError, "internal_error", "Failed to access user store") return } pref, err := store.GetSubtitlePreference(r.Context(), profileID, seriesID) if err != nil { writeError(w, http.StatusInternalServerError, "internal_error", "Failed to get subtitle preference") return } if pref == nil { writeError(w, http.StatusNotFound, "not_found", "Subtitle preference not found") return } writeJSON(w, http.StatusOK, toSubtitlePrefResponse(*pref)) } // HandleSetSubtitlePref handles PUT /subtitle-prefs/{series_id}. func (h *SubtitlePrefHandler) HandleSetSubtitlePref(w http.ResponseWriter, r *http.Request) { userID := apimw.GetUserID(r.Context()) profileID := apimw.GetProfileID(r.Context()) seriesID := chi.URLParam(r, "series_id") if seriesID == "" { writeError(w, http.StatusBadRequest, "bad_request", "Series ID is required") return } var req setSubtitlePrefRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, "bad_request", "Invalid request body") return } store, err := h.storeProvider.ForUser(r.Context(), userID) if err != nil { writeError(w, http.StatusInternalServerError, "internal_error", "Failed to access user store") return } pref := userstore.SubtitlePreference{ ProfileID: profileID, SeriesID: seriesID, SubtitleLanguage: req.SubtitleLanguage, SubtitleTrackIndex: req.SubtitleTrackIndex, ExternalSubtitlePath: req.ExternalSubtitlePath, SubtitleMode: req.SubtitleMode, TrackSignature: req.TrackSignature, } if req.ShowForcedSubtitles != nil { pref.ShowForcedSubtitles = *req.ShowForcedSubtitles pref.HasShowForcedSubtitles = true } if err := store.SetSubtitlePreference(r.Context(), pref); err != nil { writeError(w, http.StatusInternalServerError, "internal_error", "Failed to set subtitle preference") return } w.WriteHeader(http.StatusNoContent) } // HandleDeleteSubtitlePref handles DELETE /subtitle-prefs/{series_id}. func (h *SubtitlePrefHandler) HandleDeleteSubtitlePref(w http.ResponseWriter, r *http.Request) { userID := apimw.GetUserID(r.Context()) profileID := apimw.GetProfileID(r.Context()) seriesID := chi.URLParam(r, "series_id") if seriesID == "" { writeError(w, http.StatusBadRequest, "bad_request", "Series ID is required") return } store, err := h.storeProvider.ForUser(r.Context(), userID) if err != nil { writeError(w, http.StatusInternalServerError, "internal_error", "Failed to access user store") return } if err := store.DeleteSubtitlePreference(r.Context(), profileID, seriesID); err != nil { writeError(w, http.StatusInternalServerError, "internal_error", "Failed to delete subtitle preference") return } w.WriteHeader(http.StatusNoContent) } // --- Helpers --- func toSubtitlePrefResponse(p userstore.SubtitlePreference) subtitlePrefResponse { resp := subtitlePrefResponse{ ProfileID: p.ProfileID, SeriesID: p.SeriesID, SubtitleLanguage: p.SubtitleLanguage, SubtitleTrackIndex: p.SubtitleTrackIndex, ExternalSubtitlePath: p.ExternalSubtitlePath, SubtitleMode: p.SubtitleMode, TrackSignature: p.TrackSignature, UpdatedAt: p.UpdatedAt, } if p.HasShowForcedSubtitles { resp.ShowForcedSubtitles = boolPtr(p.ShowForcedSubtitles) } return resp }