Skip to content
Merged
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
2 changes: 1 addition & 1 deletion backend/backlog_app/servicies/ai_agent/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,5 @@ async def translate(self, text: str) -> str:
)

response.raise_for_status()
data = await response.json()
data = response.json()
return data["choices"][0]["message"]["content"]
8 changes: 5 additions & 3 deletions backend/backlog_app/tasks/movie_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ async def update_movie_rating(movie: MovieRead, db: AsyncSession, user: User):
movie_db.title, movie_db.year
)
except Exception as e:
logger.error("Failed to fetch rating for movie <%s>: %s", movie.id, e)
logger.exception("Failed to fetch rating for movie <%s>: %s", movie.id, e)
return

await partial_update_movie(
Expand Down Expand Up @@ -67,13 +67,15 @@ async def update_movie_description(
movie_db.title, movie_db.year
)
except Exception as e:
logger.error("Failed to fetch description for movie <%s>: %s", movie.id, e)
logger.exception("Failed to fetch description for movie <%s>: %s", movie.id, e)
return

try:
ru_description = await translator.translate(en_description)
except Exception as e:
logger.error("Failed to translate description for movie <%s>: %s", movie.id, e)
logger.exception(
"Failed to translate description for movie <%s>: %s", movie.id, e
)
return

await partial_update_movie(
Expand Down
4 changes: 2 additions & 2 deletions backend/tests/test_servicies/test_ai_agent/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ async def test_translate_success():
client = AIClient(model="gpt-test")

mock_response = AsyncMock()
mock_response.raise_for_status = AsyncMock()
mock_response.json = AsyncMock(
mock_response.raise_for_status = Mock()
mock_response.json = Mock(
return_value={"choices": [{"message": {"content": "Привет мир"}}]}
)

Expand Down
2 changes: 2 additions & 0 deletions frontend/src/api/movies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export interface MovieRead {
watchLink: string | null
kpId: number | null
imdbId: number | null
imdbRating: number | null
metacriticScore: number | null
published: boolean
user: UserRead
watched: boolean
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/components/ui/AddMovieModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@
:class="{ 'error': errors.description }"
/>
<p v-if="errors.description" class="text-sm text-accent font-body">{{ errors.description }}</p>
<p v-else class="flex items-center gap-1.5 text-xs font-body text-base-400">
<svg class="w-3.5 h-3.5 shrink-0 text-base-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
Если не заполнить и указан год — описание добавится автоматически
</p>
</div>

<BaseInput
Expand Down
25 changes: 24 additions & 1 deletion frontend/src/components/ui/MovieCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
</p>

<!-- Rating -->
<div v-if="movie.rating" class="flex items-center gap-1.5 mb-4">
<div v-if="movie.rating" class="flex items-center gap-1.5 mb-3">
<div class="flex">
<span
v-for="n in 5" :key="n"
Expand All @@ -56,6 +56,23 @@
</div>
<span class="font-mono text-xs text-base-400">{{ movie.rating }}/10</span>
</div>

<!-- External ratings -->
<div v-if="movie.imdbRating || movie.metacriticScore" class="flex items-center gap-1.5 mb-4 flex-wrap">
<span
v-if="movie.imdbRating"
class="inline-flex items-center gap-1 font-mono rounded"
style="background: #fef3c7; color: #92400e; font-size: 11px; padding: 2px 5px;"
title="IMDb рейтинг"
>IMDb {{ movie.imdbRating }}</span>
<span
v-if="movie.metacriticScore"
:style="metacriticBadgeStyle(movie.metacriticScore)"
class="inline-flex items-center font-mono rounded"
style="font-size: 11px; padding: 2px 5px;"
title="Metascore"
>Metascore {{ movie.metacriticScore }}</span>
</div>
</RouterLink>

<!-- Actions -->
Expand Down Expand Up @@ -175,6 +192,12 @@ function handleToggleWatched() {
emit('toggle-watched', props.movie)
}

function metacriticBadgeStyle(score: number) {
const bg = score >= 61 ? '#dcfce7' : score >= 40 ? '#fef9c3' : '#fee2e2'
const color = score >= 61 ? '#166534' : score >= 40 ? '#854d0e' : '#991b1b'
return `background: ${bg}; color: ${color};`
}

function formatDate(iso: string) {
return new Date(iso).toLocaleDateString('ru-RU', {
day: 'numeric',
Expand Down
32 changes: 31 additions & 1 deletion frontend/src/views/movies/MovieDetailView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
</h1>

<!-- Rating -->
<div v-if="movie.rating" class="flex items-center gap-2 mb-6">
<div v-if="movie.rating" class="flex items-center gap-2 mb-4">
<div class="flex">
<span v-for="n in 10" :key="n"
:class="n <= movie.rating ? 'text-amber-400' : 'text-base-200'"
Expand All @@ -59,6 +59,30 @@
<span class="font-mono text-sm text-base-600 font-medium">{{ movie.rating }} / 10</span>
</div>

<!-- External ratings -->
<div v-if="movie.imdbRating || movie.metacriticScore" class="flex flex-wrap items-center gap-2 mb-6">
<span
v-if="movie.imdbRating"
class="inline-flex items-baseline gap-1.5 font-mono rounded-md"
style="background: #fef3c7; color: #92400e; font-size: 12px; padding: 3px 8px;"
title="IMDb рейтинг"
>
<span>IMDb</span>
<span style="font-size: 17px; line-height: 1;">{{ movie.imdbRating }}</span>
<span style="color: #666;">/10</span>
</span>
<span
v-if="movie.metacriticScore"
:style="metacriticBadgeStyle(movie.metacriticScore)"
class="inline-flex items-baseline gap-1.5 font-mono rounded-md"
style="font-size: 12px; padding: 3px 8px;"
title="Metascore"
>
<span>Metascore</span>
<span style="font-size: 17px; line-height: 1;">{{ movie.metacriticScore }}</span>
</span>
</div>

<!-- Description -->
<p v-if="movie.description" class="font-body text-base-600 leading-relaxed text-lg">
{{ movie.description }}
Expand Down Expand Up @@ -253,6 +277,12 @@ async function handleDelete() {
}
}

function metacriticBadgeStyle(score: number) {
const bg = score >= 61 ? '#dcfce7' : score >= 40 ? '#fef9c3' : '#fee2e2'
const color = score >= 61 ? '#166534' : score >= 40 ? '#854d0e' : '#991b1b'
return `background: ${bg}; color: ${color};`
}

function formatDate(iso: string) {
return new Date(iso).toLocaleDateString('ru-RU', { day: 'numeric', month: 'long', year: 'numeric' })
}
Expand Down
10 changes: 8 additions & 2 deletions frontend/src/views/movies/MovieListView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
</button>

<div class="ml-auto">
<BaseToggle v-model="onlyMine" label="Только мои" @update:modelValue="loadMovies" />
<BaseToggle v-model="onlyMine" label="Только мои" @update:modelValue="toggleOnlyMine" />
</div>
</div>

Expand Down Expand Up @@ -133,7 +133,8 @@ const showAddModal = ref(false)
const editingMovie = ref<MovieRead | null>(null)
const deletingMovie = ref<MovieRead | null>(null)
const activeFilter = ref<'all' | 'watched' | 'pending'>('all')
const onlyMine = ref(false)
const ONLY_MINE_KEY = 'movies:onlyMine'
const onlyMine = ref(sessionStorage.getItem(ONLY_MINE_KEY) === 'true')

const watchedCount = computed(() => store.movies.filter((m) => m.watched).length)
const pendingCount = computed(() => store.movies.filter((m) => !m.watched).length)
Expand All @@ -158,6 +159,11 @@ async function loadMovies() {
await store.fetchMovies(onlyMine.value)
}

function toggleOnlyMine(value: boolean) {
sessionStorage.setItem(ONLY_MINE_KEY, String(value))
loadMovies()
}

onMounted(loadMovies)

function closeModal() {
Expand Down
Loading