Files
neomovies-api/pkg/handlers/unified.go

201 lines
6.4 KiB
Go

package handlers
import (
"encoding/json"
"net/http"
"strconv"
"strings"
"time"
"neomovies-api/pkg/models"
"neomovies-api/pkg/services"
)
type UnifiedHandler struct {
tmdb *services.TMDBService
kp *services.KinopoiskService
}
func NewUnifiedHandler(tmdb *services.TMDBService, kp *services.KinopoiskService) *UnifiedHandler {
return &UnifiedHandler{tmdb: tmdb, kp: kp}
}
// Parse source ID of form "kp_123" or "tmdb_456"
func parseSourceID(raw string) (source string, id int, err error) {
parts := strings.SplitN(raw, "_", 2)
if len(parts) != 2 {
return "", 0, strconv.ErrSyntax
}
src := strings.ToLower(parts[0])
if src != "kp" && src != "tmdb" {
return "", 0, strconv.ErrSyntax
}
num, err := strconv.Atoi(parts[1])
if err != nil {
return "", 0, err
}
return src, num, nil
}
func (h *UnifiedHandler) GetMovie(w http.ResponseWriter, r *http.Request) {
start := time.Now()
vars := muxVars(r)
rawID := vars["id"]
source, id, err := parseSourceID(rawID)
if err != nil {
writeUnifiedError(w, http.StatusBadRequest, "invalid SOURCE_ID format", start, "")
return
}
language := GetLanguage(r)
var data *models.UnifiedContent
if source == "kp" {
if h.kp == nil {
writeUnifiedError(w, http.StatusBadGateway, "Kinopoisk service not configured", start, source)
return
}
kpFilm, err := h.kp.GetFilmByKinopoiskId(id)
if err != nil {
writeUnifiedError(w, http.StatusBadGateway, err.Error(), start, source)
return
}
data = services.MapKPToUnified(kpFilm)
} else {
// tmdb
movie, err := h.tmdb.GetMovie(id, language)
if err != nil {
writeUnifiedError(w, http.StatusBadGateway, err.Error(), start, source)
return
}
ext, _ := h.tmdb.GetMovieExternalIDs(id)
data = services.MapTMDBToUnifiedMovie(movie, ext)
}
writeUnifiedOK(w, data, start, source, "")
}
func (h *UnifiedHandler) GetTV(w http.ResponseWriter, r *http.Request) {
start := time.Now()
vars := muxVars(r)
rawID := vars["id"]
source, id, err := parseSourceID(rawID)
if err != nil {
writeUnifiedError(w, http.StatusBadRequest, "invalid SOURCE_ID format", start, "")
return
}
language := GetLanguage(r)
var data *models.UnifiedContent
if source == "kp" {
if h.kp == nil {
writeUnifiedError(w, http.StatusBadGateway, "Kinopoisk service not configured", start, source)
return
}
kpFilm, err := h.kp.GetFilmByKinopoiskId(id)
if err != nil {
writeUnifiedError(w, http.StatusBadGateway, err.Error(), start, source)
return
}
data = services.MapKPToUnified(kpFilm)
} else {
tv, err := h.tmdb.GetTVShow(id, language)
if err != nil {
writeUnifiedError(w, http.StatusBadGateway, err.Error(), start, source)
return
}
ext, _ := h.tmdb.GetTVExternalIDs(id)
data = services.MapTMDBTVToUnified(tv, ext)
}
writeUnifiedOK(w, data, start, source, "")
}
func (h *UnifiedHandler) Search(w http.ResponseWriter, r *http.Request) {
start := time.Now()
query := r.URL.Query().Get("query")
if strings.TrimSpace(query) == "" {
writeUnifiedError(w, http.StatusBadRequest, "query is required", start, "")
return
}
source := strings.ToLower(r.URL.Query().Get("source")) // kp|tmdb
page := getIntQuery(r, "page", 1)
language := GetLanguage(r)
if source != "kp" && source != "tmdb" {
writeUnifiedError(w, http.StatusBadRequest, "source must be 'kp' or 'tmdb'", start, "")
return
}
if source == "kp" {
if h.kp == nil {
writeUnifiedError(w, http.StatusBadGateway, "Kinopoisk service not configured", start, source)
return
}
kpSearch, err := h.kp.SearchFilms(query, page)
if err != nil {
writeUnifiedError(w, http.StatusBadGateway, err.Error(), start, source)
return
}
items := services.MapKPSearchToUnifiedItems(kpSearch)
resp := models.UnifiedSearchResponse{
Success: true,
Data: items,
Source: source,
Pagination: models.UnifiedPagination{Page: page, TotalPages: kpSearch.PagesCount, TotalResults: kpSearch.SearchFilmsCountResult, PageSize: len(items)},
Metadata: models.UnifiedMetadata{FetchedAt: time.Now(), APIVersion: "3.0", ResponseTime: time.Since(start).Milliseconds(), Query: query},
}
writeJSON(w, http.StatusOK, resp)
return
}
// TMDB multi search
multi, err := h.tmdb.SearchMulti(query, page, language)
if err != nil {
writeUnifiedError(w, http.StatusBadGateway, err.Error(), start, source)
return
}
items := services.MapTMDBMultiToUnifiedItems(multi)
resp := models.UnifiedSearchResponse{
Success: true,
Data: items,
Source: source,
Pagination: models.UnifiedPagination{Page: multi.Page, TotalPages: multi.TotalPages, TotalResults: multi.TotalResults, PageSize: len(items)},
Metadata: models.UnifiedMetadata{FetchedAt: time.Now(), APIVersion: "3.0", ResponseTime: time.Since(start).Milliseconds(), Query: query},
}
writeJSON(w, http.StatusOK, resp)
}
func writeUnifiedOK(w http.ResponseWriter, data *models.UnifiedContent, start time.Time, source string, query string) {
resp := models.UnifiedAPIResponse{
Success: true,
Data: data,
Source: source,
Metadata: models.UnifiedMetadata{
FetchedAt: time.Now(),
APIVersion: "3.0",
ResponseTime: time.Since(start).Milliseconds(),
Query: query,
},
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp)
}
func writeUnifiedError(w http.ResponseWriter, code int, message string, start time.Time, source string) {
resp := models.UnifiedAPIResponse{
Success: false,
Error: message,
Source: source,
Metadata: models.UnifiedMetadata{
FetchedAt: time.Now(),
APIVersion: "3.0",
ResponseTime: time.Since(start).Milliseconds(),
},
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
json.NewEncoder(w).Encode(resp)
}