Major changes across backend, frontend, infrastructure: - Provider system with model selection (Google, DeepL, OpenAI, Ollama, Google Cloud) - Admin panel: user management, pricing, settings - Glossary system with CSV import/export - Subscription and tier quota management - Security hardening (rate limiting, API key auth, path traversal fixes) - Docker compose for dev, prod, and IONOS deployment - Alembic migrations for new tables - Frontend: dashboard, pricing page, landing page, i18n (en/fr) - Test suite and verification scripts Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
78 lines
2.1 KiB
Python
78 lines
2.1 KiB
Python
"""Add tier, daily_translation_count, and hashed_password fields
|
|
|
|
Revision ID: 002_add_tier_daily_count
|
|
Revises: 001_initial
|
|
Create Date: 2026-02-20
|
|
|
|
"""
|
|
|
|
from typing import Sequence, Union
|
|
|
|
from alembic import op
|
|
import sqlalchemy as sa
|
|
|
|
|
|
revision: str = "002_add_tier_daily_count"
|
|
down_revision: Union[str, None] = "001_initial"
|
|
branch_labels: Union[str, Sequence[str], None] = None
|
|
depends_on: Union[str, Sequence[str], None] = None
|
|
|
|
|
|
def upgrade() -> None:
|
|
conn = op.get_bind()
|
|
dialect = conn.dialect.name
|
|
|
|
op.add_column(
|
|
"users",
|
|
sa.Column("tier", sa.String(10), nullable=True, server_default="free"),
|
|
)
|
|
op.add_column(
|
|
"users",
|
|
sa.Column(
|
|
"daily_translation_count",
|
|
sa.Integer(),
|
|
nullable=True,
|
|
server_default=sa.text("0"),
|
|
),
|
|
)
|
|
op.add_column("users", sa.Column("hashed_password", sa.String(255), nullable=True))
|
|
|
|
op.execute(
|
|
sa.text(
|
|
"UPDATE users SET hashed_password = password_hash WHERE hashed_password IS NULL"
|
|
)
|
|
)
|
|
|
|
op.execute(
|
|
sa.text(
|
|
"UPDATE users SET tier = CASE "
|
|
"WHEN plan IN ('pro', 'business', 'enterprise') THEN 'pro' "
|
|
"ELSE 'free' END "
|
|
"WHERE tier IS NULL OR tier = 'free'"
|
|
)
|
|
)
|
|
|
|
op.execute(
|
|
sa.text(
|
|
"UPDATE users SET daily_translation_count = 0 WHERE daily_translation_count IS NULL"
|
|
)
|
|
)
|
|
|
|
# SQLite does not support ALTER COLUMN — skip nullable enforcement there
|
|
if dialect != "sqlite":
|
|
op.alter_column("users", "tier", nullable=False)
|
|
op.alter_column("users", "daily_translation_count", nullable=False)
|
|
op.create_check_constraint("ck_users_tier", "users", "tier IN ('free', 'pro')")
|
|
|
|
|
|
def downgrade() -> None:
|
|
conn = op.get_bind()
|
|
dialect = conn.dialect.name
|
|
|
|
if dialect != "sqlite":
|
|
op.drop_constraint("ck_users_tier", "users", type_="check")
|
|
|
|
op.drop_column("users", "hashed_password")
|
|
op.drop_column("users", "daily_translation_count")
|
|
op.drop_column("users", "tier")
|