package service import ( "encoding/json" "fmt" "net/http" "net/url" "os" "time" ) const ( TMDBBaseURL = "https://api.themoviedb.org/3" TMDBImageBaseURL = "https://image.tmdb.org/t/p" ) type TMDBService struct { client *http.Client apiKey string } func NewTMDBService() *TMDBService { return &TMDBService{ client: &http.Client{Timeout: 10 * time.Second}, apiKey: os.Getenv("TMDB_API_KEY"), } } type TMDBMovieResult struct { ID int `json:"id"` Title string `json:"title"` Overview string `json:"overview"` PosterPath string `json:"poster_path"` BackdropPath string `json:"backdrop_path"` ReleaseDate string `json:"release_date"` VoteAverage float64 `json:"vote_average"` } type TMDBSearchResponse struct { Results []TMDBMovieResult `json:"results"` } type TMDBMovieDetails struct { ID int `json:"id"` Title string `json:"title"` Overview string `json:"overview"` Runtime int `json:"runtime"` Budget int64 `json:"budget"` Revenue int64 `json:"revenue"` Tagline string `json:"tagline"` VoteAverage float64 `json:"vote_average"` PosterPath string `json:"poster_path"` BackdropPath string `json:"backdrop_path"` Credits struct { Cast []struct { Name string `json:"name"` Character string `json:"character"` ProfilePath string `json:"profile_path"` } `json:"cast"` Crew []struct { Name string `json:"name"` Job string `json:"job"` } `json:"crew"` } `json:"credits"` } func (s *TMDBService) SearchMovie(title string, year int) (*TMDBMovieResult, error) { if s.apiKey == "" { return nil, fmt.Errorf("TMDB_API_KEY not set") } params := url.Values{} params.Add("api_key", s.apiKey) params.Add("query", title) params.Add("language", "en-US") if year > 0 { params.Add("year", fmt.Sprintf("%d", year)) } resp, err := s.client.Get(fmt.Sprintf("%s/search/movie?%s", TMDBBaseURL, params.Encode())) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != 200 { return nil, fmt.Errorf("TMDB API returned status: %d", resp.StatusCode) } var searchResp TMDBSearchResponse if err := json.NewDecoder(resp.Body).Decode(&searchResp); err != nil { return nil, err } if len(searchResp.Results) > 0 { return &searchResp.Results[0], nil } return nil, nil } func (s *TMDBService) GetMovieDetails(tmdbID int) (*TMDBMovieDetails, error) { if s.apiKey == "" { return nil, fmt.Errorf("TMDB_API_KEY not set") } params := url.Values{} params.Add("api_key", s.apiKey) params.Add("append_to_response", "credits") params.Add("language", "en-US") resp, err := s.client.Get(fmt.Sprintf("%s/movie/%d?%s", TMDBBaseURL, tmdbID, params.Encode())) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != 200 { return nil, fmt.Errorf("TMDB API returned status: %d", resp.StatusCode) } var details TMDBMovieDetails if err := json.NewDecoder(resp.Body).Decode(&details); err != nil { return nil, err } return &details, nil } func (s *TMDBService) GetPosterURL(path string, size string) string { if path == "" { return "" } if size == "" { size = "w500" } return fmt.Sprintf("%s/%s%s", TMDBImageBaseURL, size, path) }