diff --git a/backend/backlog_app/alembic/versions/2026_06_05_2016-d6cf1a127f6c_add_field_note_to_movie_table.py b/backend/backlog_app/alembic/versions/2026_06_05_2016-d6cf1a127f6c_add_field_note_to_movie_table.py new file mode 100644 index 0000000..819872b --- /dev/null +++ b/backend/backlog_app/alembic/versions/2026_06_05_2016-d6cf1a127f6c_add_field_note_to_movie_table.py @@ -0,0 +1,32 @@ +"""add field note to movie table + +Revision ID: d6cf1a127f6c +Revises: 5ee6456bd1f5 +Create Date: 2026-06-05 20:16:19.752170 + +""" + +from typing import Sequence, Union + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision: str = "d6cf1a127f6c" +down_revision: Union[str, Sequence[str], None] = "5ee6456bd1f5" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + op.add_column("movies", sa.Column("note", sa.String(length=50), nullable=True)) + # ### end Alembic commands ### + + +def downgrade() -> None: + """Downgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("movies", "note") + # ### end Alembic commands ### diff --git a/backend/backlog_app/models/movie.py b/backend/backlog_app/models/movie.py index 3a9da96..b057e1a 100644 --- a/backend/backlog_app/models/movie.py +++ b/backend/backlog_app/models/movie.py @@ -28,6 +28,11 @@ class Movie(Base): nullable=True, ) + note: Mapped[str | None] = mapped_column( + String(50), + nullable=True, + ) + year: Mapped[int | None] = mapped_column( Integer, nullable=True, diff --git a/backend/backlog_app/schemas/movie.py b/backend/backlog_app/schemas/movie.py index decad4f..f5b9979 100644 --- a/backend/backlog_app/schemas/movie.py +++ b/backend/backlog_app/schemas/movie.py @@ -11,6 +11,7 @@ class MovieBase(BaseModel): title: Annotated[str, Len(min_length=3, max_length=255)] description: Annotated[str, Len(min_length=20, max_length=1000)] + note: Annotated[str, Len(min_length=2, max_length=50)] year: int rating: float watch_link: str | None = None @@ -27,6 +28,7 @@ class MovieBase(BaseModel): class MovieCreate(MovieBase): description: Annotated[str, Len(min_length=20, max_length=1000)] | None = None + note: Annotated[str, Len(min_length=2, max_length=50)] | None = None year: int | None = None rating: float | None = Field(default=None, ge=1.0, le=10.0) @@ -34,6 +36,7 @@ class MovieCreate(MovieBase): class MovieUpdate(MovieBase): title: Annotated[str, Len(min_length=3, max_length=255)] | None = None description: Annotated[str, Len(min_length=20, max_length=1000)] | None = None + note: Annotated[str, Len(min_length=2, max_length=50)] | None = None year: int | None = None watched: bool | None = None rating: float | None = Field(default=None, ge=1.0, le=10.0) @@ -43,6 +46,7 @@ class MovieRead(MovieBase): id: int user: UserRead description: Annotated[str, Len(min_length=20, max_length=1000)] | None + note: Annotated[str, Len(min_length=2, max_length=50)] | None = None year: int | None watched: bool rating: float | None diff --git a/backend/tests/test_schemas/test_movie.py b/backend/tests/test_schemas/test_movie.py index 54f331c..85620bf 100644 --- a/backend/tests/test_schemas/test_movie.py +++ b/backend/tests/test_schemas/test_movie.py @@ -60,6 +60,7 @@ def test_movie_read_fields_contract(): "id", "title", "description", + "note", "year", "watch_link", "rating", diff --git a/frontend/src/api/movies.ts b/frontend/src/api/movies.ts index 9ddbd53..368fff8 100644 --- a/frontend/src/api/movies.ts +++ b/frontend/src/api/movies.ts @@ -4,6 +4,7 @@ import type { UserRead } from './auth' export interface MovieCreate { title: string description?: string | null + note?: string | null year?: number | null rating?: number | null watchLink?: string | null @@ -20,6 +21,7 @@ export interface MovieRead { id: number title: string description: string | null + note: string | null year: number | null rating: number | null watchLink: string | null diff --git a/frontend/src/components/ui/AddMovieModal.vue b/frontend/src/components/ui/AddMovieModal.vue index b479999..c38b566 100644 --- a/frontend/src/components/ui/AddMovieModal.vue +++ b/frontend/src/components/ui/AddMovieModal.vue @@ -72,6 +72,23 @@ placeholder="https://..." /> +
+ + +
+

{{ errors.note }}

+
+
+
@@ -123,6 +140,7 @@ const emit = defineEmits<{ submit: [data: { title: string description: string | null + note: string | null year: number | null rating: number | null watchLink: string | null @@ -136,19 +154,21 @@ const loading = ref(false) const form = reactive({ title: '', description: '', + note: '', watchLink: '', published: false, watched: false, }) const yearStr = ref('') const ratingStr = ref('') -const errors = reactive({ title: '', description: '', year: '', rating: '' }) +const errors = reactive({ title: '', description: '', note: '', year: '', rating: '' }) // Populate form when editing watch(() => props.editMovie, (movie) => { if (movie) { form.title = movie.title form.description = movie.description || '' + form.note = movie.note || '' form.watchLink = movie.watchLink || '' form.published = movie.published form.watched = movie.watched @@ -162,12 +182,13 @@ watch(() => props.editMovie, (movie) => { function resetForm() { form.title = '' form.description = '' + form.note = '' form.watchLink = '' form.published = false form.watched = false yearStr.value = '' ratingStr.value = '' - Object.assign(errors, { title: '', description: '', year: '', rating: '' }) + Object.assign(errors, { title: '', description: '', note: '', year: '', rating: '' }) } function validate(): boolean { @@ -197,6 +218,16 @@ function validate(): boolean { } } + if (form.note && form.note.length > 0 && form.note.length < 2) { + errors.note = 'Заметка должна содержать не менее 2 символов' + valid = false + } + + if (form.note && form.note.length > 50) { + errors.note = 'Заметка не может быть длиннее 50 символов' + valid = false + } + return valid } @@ -207,6 +238,7 @@ async function handleSubmit() { emit('submit', { title: form.title, description: form.description || null, + note: form.note || null, year: yearStr.value ? Number(yearStr.value) : null, rating: ratingStr.value ? Number(ratingStr.value) : null, watchLink: form.watchLink || null, diff --git a/frontend/src/components/ui/MovieCard.vue b/frontend/src/components/ui/MovieCard.vue index b689920..5359b63 100644 --- a/frontend/src/components/ui/MovieCard.vue +++ b/frontend/src/components/ui/MovieCard.vue @@ -29,7 +29,7 @@
- +

@@ -41,10 +41,18 @@

-

+

{{ movie.description }}

+ +
+ + + + {{ movie.note }} +
+
diff --git a/frontend/src/views/movies/MovieDetailView.vue b/frontend/src/views/movies/MovieDetailView.vue index 7a28bcb..518f6af 100644 --- a/frontend/src/views/movies/MovieDetailView.vue +++ b/frontend/src/views/movies/MovieDetailView.vue @@ -87,6 +87,14 @@

{{ movie.description }}

+ + +
+ + + + {{ movie.note }} +