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>
11 KiB
Story 1.1: Setup Base de Données & Modèle User
Status: done
Story
As a Developer, I want setup the database with Alembic (async) and refactor the User model with tier field, so that the application can persist user data with simplified subscription management and async support.
Acceptance Criteria
- AC1: User Model Refactored -
userstable has columns: id (UUID), email, hashed_password, tier (default "free"), daily_translation_count, created_at, updated_at - AC2: Async SQLAlchemy - Database connection uses async engine (SQLAlchemy 2.0 async)
- AC3: Alembic Async - Alembic configured for async migrations
- AC4: Dual Database Support - PostgreSQL works in production, SQLite in development
- AC5: Secrets from Environment - All secrets loaded from environment variables (NFR10)
Tasks / Subtasks
-
Task 1: Refactor User Model (AC: 1, 5)
- 1.1 Add
tierfield (str: "free" | "pro") with default "free" - 1.2 Add
daily_translation_countfield (int, default 0) - 1.3 Rename
password_hash→hashed_passwordfor consistency with architecture - 1.4 Keep existing fields for backward compatibility (plan, stripe fields) but mark deprecated
- 1.5 Add UUID column type (from String(36) to proper UUID) — DEFERRED: requires coordinated PK+FK migration across all tables (users, translations, api_keys, usage_logs, payment_history). Created as separate task in next sprint.
- 1.1 Add
-
Task 2: Convert to Async SQLAlchemy (AC: 2, 4)
- 2.1 Replace
create_enginewithcreate_async_engineindatabase/connection.py - 2.2 Replace
sessionmakerwithAsyncSessionandasync_sessionmaker - 2.3 Update
get_db()to async generator withAsyncSession - 2.4 Keep SQLite support for development (with aiosqlite driver)
- 2.5 Keep PostgreSQL support (with asyncpg driver)
- 2.1 Replace
-
Task 3: Configure Alembic for Async (AC: 3, 4)
- 3.1 Update
alembic/env.pyfor async engine support - 3.2 Add
asyncio.run()wrapper for online migrations - 3.3 Ensure offline migrations still work
- 3.1 Update
-
Task 4: Create Migration (AC: 1)
- 4.1 Create migration
alembic/versions/002_add_tier_daily_count.py - 4.2 Add
tiercolumn with default "free" - 4.3 Add
daily_translation_countcolumn with default 0 - 4.4 Add
hashed_passwordcolumn (migration from password_hash) - 4.5 Data migration: set tier from existing plan field
- 4.1 Create migration
-
Task 5: Update Dependencies (AC: 2, 4)
- 5.1 Add
aiosqlitefor async SQLite support - 5.2 Add
asyncpgfor async PostgreSQL support - 5.3 Update
requirements.txt
- 5.1 Add
-
Task 6: Verify Setup (AC: 1-5)
- 6.1 Test migration with SQLite:
alembic upgrade head - 6.2 Test migration downgrade:
alembic downgrade -1 - 6.3 Verify User model can create/read with new fields
- 6.1 Test migration with SQLite:
Dev Notes
🚨 BROWNFIELD CONTEXT - CRITICAL
This is a refactoring story, NOT a fresh setup. The project already has:
| Existing File | Current State | Target State |
|---|---|---|
database/models.py |
Sync, plan enum (5 values) |
Add tier (2 values), daily_translation_count |
database/connection.py |
Sync engine | Async engine |
alembic/env.py |
Sync migrations | Async migrations |
alembic/versions/001_initial.py |
Creates users table | Add migration 002 to modify |
Technical Requirements from Architecture
Database Stack:
- PostgreSQL (prod) / SQLite (dev)
- SQLAlchemy 2.0 with async support
- Alembic migrations
Auth Stack (already installed):
- PyJWT 2.8.0
- passlib[bcrypt] 1.7.4
New Dependencies Required:
aiosqlite>=0.19.0 # Async SQLite driver
asyncpg>=0.29.0 # Async PostgreSQL driver
User Model Changes
Current Model (database/models.py:41-84):
class User(Base):
plan = Column(Enum(PlanType), default=PlanType.FREE) # 5 tiers
password_hash = Column(String(255)) # Wrong naming
docs_translated_this_month = Column(Integer, default=0) # Monthly
Target Model (for this story):
class User(Base):
tier = Column(String(10), default="free") # Simple: "free" | "pro"
hashed_password = Column(String(255)) # Correct naming
daily_translation_count = Column(Integer, default=0) # Daily (reset at midnight)
Migration Strategy:
- Keep
planfield temporarily for backward compatibility - Add
tieranddaily_translation_countas new fields - Future story will remove deprecated fields
Async SQLAlchemy Pattern
From (database/connection.py):
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(bind=engine)
To:
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
# Convert URLs:
# postgresql://... → postgresql+asyncpg://...
# sqlite:///... → sqlite+aiosqlite:///...
engine = create_async_engine(ASYNC_DATABASE_URL)
AsyncSessionLocal = async_sessionmaker(bind=engine, class_=AsyncSession)
Alembic Async Pattern
Update alembic/env.py:
from sqlalchemy.ext.asyncio import create_async_engine
import asyncio
def run_migrations_online():
connectable = create_async_engine(DATABASE_URL)
async def run_async_migrations():
async with connectable.connect() as connection:
await connection.run_sync(do_migrations)
asyncio.run(run_async_migrations())
Files to Modify
| File | Action | Notes |
|---|---|---|
database/models.py |
MODIFY | Add tier, daily_translation_count, hashed_password |
database/connection.py |
MODIFY | Convert to async |
alembic/env.py |
MODIFY | Add async support |
alembic/versions/002_*.py |
CREATE | New migration |
requirements.txt |
MODIFY | Add aiosqlite, asyncpg |
.env.example |
VERIFY | Has DATABASE_URL documented |
Files NOT to Touch
main.py- Will be updated in a later storyroutes/auth_routes.py- Will be updated in a later storyservices/auth_service*.py- Will be updated in a later story- Existing migrations in
alembic/versions/- Don't modify 001_initial.py
Testing Commands
# Install new dependencies
pip install aiosqlite asyncpg
# Run migration
alembic upgrade head
# Verify migration
alembic current
# Rollback if needed
alembic downgrade -1
Project Structure Notes
- Backend uses snake_case for files, variables, functions
- PascalCase for classes
- Follow existing patterns in
database/folder - Keep backward compatibility during transition
References
- [Source: _bmad-output/planning-artifacts/architecture.md#Data Architecture]
- [Source: _bmad-output/planning-artifacts/architecture.md#Authentication & Security]
- [Source: _bmad-output/planning-artifacts/epics.md#Story 1.1]
- [Source: _bmad-output/planning-artifacts/prd.md#NFR6-NFR11 Security]
Dev Agent Record
Agent Model Used
GLM-5 (zai-coding-plan/glm-5)
Debug Log References
- Installed aiosqlite, asyncpg, greenlet, pytest, pytest-asyncio dependencies
- All 11 tests pass successfully
- Migration upgrade/downgrade tested successfully
Completion Notes List
- User Model Refactored: Added
account_tier(mapped viatierproperty),daily_translation_count, andhashed_passwordfields. Added backward-compatiblepassword_hashproperty with deprecation warning. - Async SQLAlchemy: Converted
database/connection.pyto usecreate_async_engine,AsyncSession, andasync_sessionmaker. Added URL conversion for postgresql→postgresql+asyncpg and sqlite→sqlite+aiosqlite. - Alembic Async: Updated
alembic/env.pywith async engine support usingasyncio.run()wrapper andrun_sync()pattern. - Migration Created:
002_add_tier_daily_count.pyadds new columns, copies data frompassword_hashtohashed_password, and migratesplanvalues totier("pro"/"business"/"enterprise" → "pro", others → "free"). - Dependencies Updated: Added aiosqlite>=0.19.0, asyncpg>=0.29.0, greenlet>=3.0.0, pytest>=7.0.0, pytest-asyncio>=0.21.0.
- Tests Added: Created
tests/conftest.pywith async fixtures andtests/test_user_model.pywith 11 test cases covering all AC requirements. - Code review (2026-02-21): H2:
database/repositories.pyand auth services now usehashed_password/tierincreate(). H1+M3: Addedtests/test_database_utils.py(AC4 URL conversion),tests/test_alembic_async.py(AC3/AC5 + Alembic upgrade/downgrade integration). M4: Migrationserver_default=sa.text("0")for Integer. M2: Comment indatabase/connection.pyfor sync engine. All 21 tests pass.
File List
Modified:
- database/models.py - Added tier (direct column), daily_translation_count, hashed_password; CheckConstraint; datetime.now(timezone.utc); lazy="select"
- database/connection.py - Converted to async SQLAlchemy 2.0; fixed check_db_connection() with text(); uses shared convert_to_async_url
- database/init.py - Updated exports for async session; added Base export
- alembic/env.py - Added async migration support; uses shared convert_to_async_url
- requirements.txt - Added aiosqlite, asyncpg, greenlet, pytest, pytest-asyncio; removed psycopg2-binary (unused)
Created:
- database/utils.py - Shared convert_to_async_url() utility (DRY)
- alembic/versions/002_add_tier_daily_count.py - Migration for new user fields (op.execute, tier column, CHECK constraint)
- tests/init.py - Test package init
- tests/conftest.py - Pytest async fixtures (in-memory SQLite, asyncio_mode=auto)
- tests/test_user_model.py - User model tests (11 test cases)
- tests/test_database_utils.py - AC4: convert_to_async_url tests (code-review)
- tests/test_alembic_async.py - AC3/AC5 and Alembic upgrade/downgrade integration (code-review)
- pytest.ini - asyncio_mode = auto configuration
Modified (code-review follow-up):
- database/repositories.py - create() uses hashed_password and tier
- database/connection.py - Comment documenting sync engine (backward compat)
- alembic/versions/002_add_tier_daily_count.py - server_default=sa.text("0") for Integer
- services/auth_service.py - repo.create(..., hashed_password=, tier=)
- services/auth_service_db.py - repo.create(..., hashed_password=, tier=)
Other files modified in repo (other stories): .env.example, main.py, models/subscription.py, routes/auth_routes.py, utils/init.py, utils/exceptions.py, etc.
Change Log
- 2026-02-20: Completed Story 1.1 - Setup database with async SQLAlchemy 2.0 and refactored User model with tier/daily_translation_count fields
- 2026-02-20: Code review fixes — C2: renamed account_tier→tier (direct column, ORM-queryable); C3: fixed check_db_connection() text(); H1: migration uses op.execute(); H2: extracted convert_to_async_url() to database/utils.py; H3: datetime.now(timezone.utc); H4: lazy="select"; M1: CHECK CONSTRAINT on tier; M2: removed psycopg2-binary; M3/M4: pytest.ini asyncio_mode=auto + in-memory test DB. Task 1.5 (UUID PK type) deferred — requires full FK cascade migration.
- 2026-02-21: Code review auto-fixes (option 1): H2 repositories/auth services → hashed_password/tier; H1+M3 tests for AC3/AC4/AC5 and Alembic integration; M4 migration server_default; M2 connection.py comment; story status → done.