Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions backend/daos/movie_dao.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
1 change: 1 addition & 0 deletions backend/models/movie.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down
49 changes: 49 additions & 0 deletions backend/services/list_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
}
}
}
Comment on lines +297 to +307
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Replace fmt.Println with structured logging.

Line 304 uses fmt.Println for error logging, which is not suitable for production code. Consider using a proper logging framework (e.g., log/slog, zap, or logrus) to ensure logs are properly structured, leveled, and routed.

Additionally, the error from fetchIMDBID (line 299) is silently ignored. While this may be acceptable for non-critical supplementary data, consider logging a warning if the fetch fails.

🔎 Example refactor using structured logging
 		// 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 {
+			if imdbID, fetchErr := 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)
+					logger.Warn("failed to update IMDB ID", "movie_id", movie.ID, "error", updateErr)
 				}
+			} else if fetchErr != nil {
+				logger.Debug("failed to fetch IMDB ID", "media_id", mediaID, "media_type", mediaType, "error", fetchErr)
 			}
 		}

Note: Replace logger with your application's logging instance.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In @backend/services/list_service.go around lines 297 - 307, Replace the
fmt.Println call and the ignored error from s.fetchIMDBID with structured
warning logs: capture the error returned by s.fetchIMDBID (call signature
s.fetchIMDBID(ctx, mediaID, mediaType)) and, if non-nil, log a warning with
context (mediaID, mediaType) rather than ignoring it; when UpdateImdbID
(s.movies.UpdateImdbID(movie.ID, imdbID)) fails, log that failure as a warning
or error via your app logger instead of fmt.Println, including movie.ID and the
updateErr; use the existing logger instance (e.g., s.logger or a logger
retrieved from ctx) so logs are structured and leveled consistently across the
service.

} else {
var fetchErr error
movie, fetchErr = s.fetchAndStoreFromTMDB(ctx, mediaID, mediaType)
Expand All @@ -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
}
Comment on lines +333 to +361
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Inconsistent error handling loses diagnostic information.

The fetchIMDBID method returns nil, nil on non-200 HTTP responses (line 354) and JSON decode failures (line 358), while returning actual errors for request construction and network failures (lines 341-342, 349-350). This inconsistency makes it difficult to distinguish between "no IMDb ID available" versus "an error occurred."

This approach silently swallows legitimate errors like network timeouts, malformed JSON responses, or rate limiting (429 status), which could mask operational issues.

🔎 Recommended fix

Consider one of these approaches:

Option 1: Return errors for better diagnostics

 	if resp.StatusCode != http.StatusOK {
-		return nil, nil
+		return nil, fmt.Errorf("TMDB external_ids returned status %d", resp.StatusCode)
 	}
 	var externalIDs tmdbExternalIDsResponse
 	if err := json.NewDecoder(resp.Body).Decode(&externalIDs); err != nil {
-		return nil, nil
+		return nil, fmt.Errorf("failed to decode TMDB external_ids: %w", err)
 	}

Option 2: Log errors while returning nil

 	if resp.StatusCode != http.StatusOK {
+		logger.Warn("TMDB external_ids non-OK status", "status", resp.StatusCode, "media_id", id, "media_type", mediaType)
 		return nil, nil
 	}
 	var externalIDs tmdbExternalIDsResponse
 	if err := json.NewDecoder(resp.Body).Decode(&externalIDs); err != nil {
+		logger.Warn("failed to decode TMDB external_ids", "error", err, "media_id", id, "media_type", mediaType)
 		return nil, nil
 	}


func (s *listService) fetchAndStoreFromTMDB(ctx context.Context, id int64, mediaType string) (*models.Movie, error) {
var url string
if mediaType == "movie" {
Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
18 changes: 17 additions & 1 deletion frontend/src/components/movie/MovieOverlay.tsx
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -160,6 +161,21 @@ export function MovieOverlay({
{media.media_type === 'movie' ? <Film className="w-3 h-3" /> : <Tv className="w-3 h-3" />}
{media.media_type === 'movie' ? 'Filme' : 'Série'}
</span>
{media.imdb_id && (
<a
href={`https://www.imdb.com/title/${media.imdb_id}`}
target="_blank"
rel="noopener noreferrer"
className={cn(
"inline-flex items-center gap-1 text-xs px-2 py-1 rounded-full border font-medium transition-colors",
"bg-yellow-500/10 border-yellow-500/30 text-yellow-300 hover:bg-yellow-500/20 hover:border-yellow-500/50"
)}
title="Ver no IMDB"
>
<ExternalLink className="w-3 h-3" />
IMDB
</a>
)}
</div>
{original && (
<p className="text-sm text-neutral-400 italic mb-3">
Expand Down
1 change: 1 addition & 0 deletions frontend/src/services/lists.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down