From 6717e2551d2514439b11a89b16b3a073c887b2d8 Mon Sep 17 00:00:00 2001 From: busya Date: Thu, 14 May 2026 23:51:25 +0300 Subject: [PATCH] fix(translate): add missing target_languages column to production DB Migration 2a7b8c9d0e1f adds target_languages (JSON) to translation_jobs. Uses ADD COLUMN IF NOT EXISTS for idempotent PostgreSQL deployment. Fixes 500 error on GET /api/translate/jobs: column translation_jobs.target_languages does not exist Migration chain is now clean: 5 linear revisions, no orphans. --- ...dd_target_languages_to_translation_jobs.py | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 backend/alembic/versions/2a7b8c9d0e1f_add_target_languages_to_translation_jobs.py diff --git a/backend/alembic/versions/2a7b8c9d0e1f_add_target_languages_to_translation_jobs.py b/backend/alembic/versions/2a7b8c9d0e1f_add_target_languages_to_translation_jobs.py new file mode 100644 index 00000000..246c14d6 --- /dev/null +++ b/backend/alembic/versions/2a7b8c9d0e1f_add_target_languages_to_translation_jobs.py @@ -0,0 +1,67 @@ +"""Add target_languages column to translation_jobs (multi-language support) + +Revision ID: 2a7b8c9d0e1f +Revises: 8dd0a93af539 +Create Date: 2026-05-14 23:55:00.000000 + +""" +from collections.abc import Sequence + +import sqlalchemy as sa + +from alembic import op + +# revision identifiers, used by Alembic. +revision: str = "2a7b8c9d0e1f" +down_revision: str | Sequence[str] | None = "8dd0a93af539" +branch_labels: str | Sequence[str] | None = None +depends_on: str | Sequence[str] | None = None + + +def upgrade() -> None: + """Add target_languages column to translation_jobs. + + Production PostgreSQL may be missing this column if the initial + migration (ed310b33f02c) was applied from a version that predated + the multi-language support feature. This migration safely adds it + with IF NOT EXISTS on PostgreSQL, and handles SQLite explicitly. + """ + bind = op.get_bind() + inspector = sa.inspect(bind) + columns = [c["name"] for c in inspector.get_columns("translation_jobs")] + + if "target_languages" in columns: + # Column already exists — nothing to do + return + + if bind.engine.name == "sqlite": + op.add_column( + "translation_jobs", + sa.Column( + "target_languages", + sa.JSON(), + nullable=True, + comment="List of BCP-47 target language codes (multi-language support)", + ), + ) + else: + # PostgreSQL and others: use IF NOT EXISTS for safety + op.execute( + "ALTER TABLE translation_jobs " + "ADD COLUMN IF NOT EXISTS target_languages JSON " + "DEFAULT NULL" + ) + + +def downgrade() -> None: + """Drop target_languages column from translation_jobs.""" + bind = op.get_bind() + if bind.engine.name == "sqlite": + inspector = sa.inspect(bind) + columns = [c["name"] for c in inspector.get_columns("translation_jobs")] + if "target_languages" in columns: + op.drop_column("translation_jobs", "target_languages") + else: + op.execute( + "ALTER TABLE translation_jobs DROP COLUMN IF EXISTS target_languages" + )