diff --git a/backend/daos/movie_dao.go b/backend/daos/movie_dao.go index 741c1a3..e918530 100644 --- a/backend/daos/movie_dao.go +++ b/backend/daos/movie_dao.go @@ -10,6 +10,7 @@ type MovieDAO interface { FindByIDAndType(id int64, mediaType string) (*models.Movie, error) FindByID(id int64) (*models.Movie, error) CreateMovieWithGenres(movie *models.Movie, genres []models.Genre) error + UpdateImdbID(id int64, imdbID *string) error } type movieDAO struct { @@ -55,3 +56,7 @@ func (d *movieDAO) CreateMovieWithGenres(movie *models.Movie, genres []models.Ge return nil }) } + +func (d *movieDAO) UpdateImdbID(id int64, imdbID *string) error { + return d.db.Model(&models.Movie{}).Where("id = ?", id).Update("imdb_id", imdbID).Error +} diff --git a/backend/models/movie.go b/backend/models/movie.go index 7841320..0bec093 100644 --- a/backend/models/movie.go +++ b/backend/models/movie.go @@ -13,6 +13,7 @@ type Movie struct { ReleaseDate *time.Time `gorm:"type:date;column:release_date" json:"release_date"` PosterPath *string `gorm:"type:text;column:poster_path" json:"poster_path"` Popularity *float64 `gorm:"type:numeric;column:popularity" json:"popularity"` + ImdbID *string `gorm:"size:20;column:imdb_id" json:"imdb_id"` MediaType string `gorm:"type:enum('movie','tv');not null;default:'movie';column:media_type" json:"media_type"` SeasonsCount *int `gorm:"column:seasons_count" json:"seasons_count"` diff --git a/backend/services/list_service.go b/backend/services/list_service.go index 8ad9b30..62e502b 100644 --- a/backend/services/list_service.go +++ b/backend/services/list_service.go @@ -264,6 +264,10 @@ type tmdbTVResponse struct { } `json:"genres"` } +type tmdbExternalIDsResponse struct { + ImdbID *string `json:"imdb_id"` +} + func (s *listService) AddMediaToList(ctx context.Context, listID int64, userID int64, mediaID int64, mediaType string) (*models.ListMovie, *models.Movie, error) { if mediaType != "movie" && mediaType != "tv" { return nil, nil, ErrInvalidMediaType @@ -290,6 +294,17 @@ func (s *listService) AddMediaToList(ctx context.Context, listID int64, userID i var movie *models.Movie if existingMovie != nil { movie = existingMovie + // If movie exists but has no IMDB ID, fetch and update it + if movie.ImdbID == nil { + if imdbID, _ := s.fetchIMDBID(ctx, mediaID, mediaType); imdbID != nil { + movie.ImdbID = imdbID + // Update the movie in the database with the IMDB ID + if updateErr := s.movies.UpdateImdbID(movie.ID, imdbID); updateErr != nil { + // Log error but don't fail the operation + fmt.Println("Warning: failed to update IMDB ID for movie", movie.ID, ":", updateErr) + } + } + } } else { var fetchErr error movie, fetchErr = s.fetchAndStoreFromTMDB(ctx, mediaID, mediaType) @@ -315,6 +330,36 @@ func (s *listService) AddMediaToList(ctx context.Context, listID int64, userID i return lm, movie, nil } +func (s *listService) fetchIMDBID(ctx context.Context, id int64, mediaType string) (*string, error) { + var url string + if mediaType == "movie" { + url = fmt.Sprintf("https://api.themoviedb.org/3/movie/%d/external_ids", id) + } else { + url = fmt.Sprintf("https://api.themoviedb.org/3/tv/%d/external_ids", id) + } + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + return nil, err + } + req.Header.Set("Accept", "application/json") + if s.tmdbToken != "" { + req.Header.Set("Authorization", "Bearer "+s.tmdbToken) + } + resp, err := s.httpClient.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return nil, nil + } + var externalIDs tmdbExternalIDsResponse + if err := json.NewDecoder(resp.Body).Decode(&externalIDs); err != nil { + return nil, nil + } + return externalIDs.ImdbID, nil +} + func (s *listService) fetchAndStoreFromTMDB(ctx context.Context, id int64, mediaType string) (*models.Movie, error) { var url string if mediaType == "movie" { @@ -345,6 +390,8 @@ func (s *listService) fetchAndStoreFromTMDB(ctx context.Context, id int64, media return nil, ErrTMDBUnavailable } + imdbID, _ := s.fetchIMDBID(ctx, id, mediaType) + if mediaType == "movie" { var m tmdbMovieResponse if err := json.NewDecoder(resp.Body).Decode(&m); err != nil { @@ -366,6 +413,7 @@ func (s *listService) fetchAndStoreFromTMDB(ctx context.Context, id int64, media ReleaseDate: release, PosterPath: m.PosterPath, Popularity: m.Popularity, + ImdbID: imdbID, } genres := make([]models.Genre, 0, len(m.Genres)) for _, g := range m.Genres { @@ -400,6 +448,7 @@ func (s *listService) fetchAndStoreFromTMDB(ctx context.Context, id int64, media SeasonsCount: tv.NumberOfSeasons, EpisodesCount: tv.NumberOfEpisodes, SeriesStatus: tv.Status, + ImdbID: imdbID, } genres := make([]models.Genre, 0, len(tv.Genres)) for _, g := range tv.Genres { diff --git a/frontend/src/components/movie/MovieOverlay.tsx b/frontend/src/components/movie/MovieOverlay.tsx index 5ca0d76..ed4ed74 100644 --- a/frontend/src/components/movie/MovieOverlay.tsx +++ b/frontend/src/components/movie/MovieOverlay.tsx @@ -1,5 +1,5 @@ import { useState } from 'react' -import { X, Film, Calendar, Languages, TrendingUp, Tv } from 'lucide-react' +import { X, Film, Calendar, Languages, TrendingUp, Tv, ExternalLink } from 'lucide-react' import { StarRating } from './StarRating' import { CommentSection } from './CommentSection' import { UserAvatar } from '@/components/UserAvatar' @@ -31,6 +31,7 @@ interface MovieOverlayProps { overview?: string | null original_lang?: string | null popularity?: number | null + imdb_id?: string | null seasons_count?: number | null episodes_count?: number | null series_status?: string | null @@ -160,6 +161,21 @@ export function MovieOverlay({ {media.media_type === 'movie' ? : } {media.media_type === 'movie' ? 'Filme' : 'Série'} + {media.imdb_id && ( + + + IMDB + + )} {original && (

diff --git a/frontend/src/services/lists.ts b/frontend/src/services/lists.ts index d69f3b0..0f32b8f 100644 --- a/frontend/src/services/lists.ts +++ b/frontend/src/services/lists.ts @@ -115,6 +115,7 @@ export interface MovieDTO { release_date?: string | null poster_path?: string | null popularity?: number | null + imdb_id?: string | null media_type: 'movie' | 'tv' seasons_count?: number | null episodes_count?: number | null