Files
silo-server/internal/api/handlers/subtitle_prefs.go
T

167 lines
5.7 KiB
Go

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
}