Files
openrouter-watcher/prompt/prompt-database-models.md
Luca Sacchi Ricciardi 60d9228d91 feat(db): T06 create database connection and session management
- Add database.py with SQLAlchemy engine and session
- Implement get_db() for FastAPI dependency injection
- Implement init_db() for table creation
- Use SQLAlchemy 2.0 declarative_base() syntax
- Add comprehensive tests with 100% coverage

Tests: 11 passed, 100% coverage
2026-04-07 10:53:13 +02:00

11 KiB

Prompt: Database & Models Implementation (T06-T11)

🎯 OBIETTIVO

Implementare la fase Database & Models del progetto OpenRouter API Key Monitor seguendo rigorosamente TDD (Test-Driven Development).

Task da completare: T06, T07, T08, T09, T10, T11


📋 CONTESTO

  • Repository: /home/google/Sources/LucaSacchiNet/openrouter-watcher
  • Specifiche: /home/google/Sources/LucaSacchiNet/openrouter-watcher/export/architecture.md
  • Kanban: /home/google/Sources/LucaSacchiNet/openrouter-watcher/export/kanban.md
  • Stato Attuale: Setup completato (T01-T05), 59 test passanti

🗄️ SCHEMA DATABASE (Da architecture.md)

Tabelle

1. users

CREATE TABLE users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    email VARCHAR(255) NOT NULL UNIQUE,
    password_hash VARCHAR(255) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    is_active BOOLEAN DEFAULT 1,
    CONSTRAINT chk_email_format CHECK (email LIKE '%_@__%.__%')
);

2. api_keys

CREATE TABLE api_keys (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    user_id INTEGER NOT NULL,
    name VARCHAR(100) NOT NULL,
    key_encrypted TEXT NOT NULL,  -- AES-256-GCM encrypted
    is_active BOOLEAN DEFAULT 1,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    last_used_at TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);

3. usage_stats

CREATE TABLE usage_stats (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    api_key_id INTEGER NOT NULL,
    date DATE NOT NULL,
    model VARCHAR(100) NOT NULL,
    requests_count INTEGER DEFAULT 0,
    tokens_input INTEGER DEFAULT 0,
    tokens_output INTEGER DEFAULT 0,
    cost DECIMAL(10, 6) DEFAULT 0.0,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (api_key_id) REFERENCES api_keys(id) ON DELETE CASCADE,
    CONSTRAINT uniq_key_date_model UNIQUE (api_key_id, date, model)
);

4. api_tokens

CREATE TABLE api_tokens (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    user_id INTEGER NOT NULL,
    token_hash VARCHAR(255) NOT NULL,
    name VARCHAR(100) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    last_used_at TIMESTAMP,
    is_active BOOLEAN DEFAULT 1,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);

🔧 TASK DETTAGLIATI

T06: Creare database.py (connection & session)

Requisiti:

  • Creare src/openrouter_monitor/database.py
  • Implementare SQLAlchemy engine con SQLite
  • Configurare session maker con expire_on_commit=False
  • Implementare funzione get_db() per dependency injection FastAPI
  • Implementare init_db() per creazione tabelle
  • Usare check_same_thread=False per SQLite

Test richiesti:

  • Test connessione database
  • Test creazione engine
  • Test session creation
  • Test init_db crea tabelle

T07: Creare model User (SQLAlchemy)

Requisiti:

  • Creare src/openrouter_monitor/models/user.py
  • Implementare class User con tutti i campi
  • Configurare relationships con ApiKey e ApiToken
  • Implementare check_email_format constraint
  • Campi: id, email, password_hash, created_at, updated_at, is_active
  • Index su email

Test richiesti:

  • Test creazione utente
  • Test vincolo email unique
  • Test validazione email format
  • Test relationship con api_keys
  • Test relationship con api_tokens

T08: Creare model ApiKey (SQLAlchemy)

Requisiti:

  • Creare src/openrouter_monitor/models/api_key.py
  • Implementare class ApiKey
  • Configurare relationship con User e UsageStats
  • Foreign key su user_id con ON DELETE CASCADE
  • Campi: id, user_id, name, key_encrypted, is_active, created_at, last_used_at
  • Index su user_id e is_active

Test richiesti:

  • Test creazione API key
  • Test relationship con user
  • Test relationship con usage_stats
  • Test cascade delete

T09: Creare model UsageStats (SQLAlchemy)

Requisiti:

  • Creare src/openrouter_monitor/models/usage_stats.py
  • Implementare class UsageStats
  • Configurare relationship con ApiKey
  • Unique constraint: (api_key_id, date, model)
  • Campi: id, api_key_id, date, model, requests_count, tokens_input, tokens_output, cost, created_at
  • Index su api_key_id, date, model
  • Usare Numeric(10, 6) per cost

Test richiesti:

  • Test creazione usage stats
  • Test unique constraint
  • Test relationship con api_key
  • Test valori default (0)

T10: Creare model ApiToken (SQLAlchemy)

Requisiti:

  • Creare src/openrouter_monitor/models/api_token.py
  • Implementare class ApiToken
  • Configurare relationship con User
  • Foreign key su user_id con ON DELETE CASCADE
  • Campi: id, user_id, token_hash, name, created_at, last_used_at, is_active
  • Index su user_id, token_hash, is_active

Test richiesti:

  • Test creazione API token
  • Test relationship con user
  • Test cascade delete

T11: Setup Alembic e migrazione iniziale

Requisiti:

  • Inizializzare Alembic: alembic init alembic
  • Configurare alembic.ini con DATABASE_URL
  • Configurare alembic/env.py con Base metadata
  • Creare migrazione iniziale che crea tutte le tabelle
  • Migrazione deve includere indici e constraints
  • Testare upgrade/downgrade

Test richiesti:

  • Test alembic init
  • Test creazione migration file
  • Test upgrade applica cambiamenti
  • Test downgrade rimuove cambiamenti
  • Test tutte le tabelle create correttamente

🔄 WORKFLOW TDD OBBLIGATORIO

Per OGNI task (T06-T11):

┌─────────────────────────────────────────┐
│  1. RED - Scrivi il test che fallisce   │
│     • Test prima del codice             │
│     • Pattern AAA (Arrange-Act-Assert)  │
│     • Nomi descrittivi                  │
└─────────────────────────────────────────┘
                    ↓
┌─────────────────────────────────────────┐
│  2. GREEN - Implementa codice minimo    │
│     • Solo codice necessario per test   │
│     • Nessun refactoring ancora         │
└─────────────────────────────────────────┘
                    ↓
┌─────────────────────────────────────────┐
│  3. REFACTOR - Migliora il codice       │
│     • Pulisci duplicazioni              │
│     • Migliora nomi variabili           │
│     • Test rimangono verdi              │
└─────────────────────────────────────────┘

📁 STRUTTURA FILE DA CREARE

src/openrouter_monitor/
├── database.py                    # T06
└── models/
    ├── __init__.py               # Esporta tutti i modelli
    ├── user.py                   # T07
    ├── api_key.py                # T08
    ├── usage_stats.py            # T09
    └── api_token.py              # T10

alembic/
├── alembic.ini                   # Configurazione
├── env.py                        # Configurato con metadata
└── versions/
    └── 001_initial_schema.py     # T11 - Migrazione iniziale

tests/unit/models/
├── test_database.py              # Test T06
├── test_user_model.py            # Test T07
├── test_api_key_model.py         # Test T08
├── test_usage_stats_model.py     # Test T09
├── test_api_token_model.py       # Test T10
└── test_migrations.py            # Test T11

🧪 REQUISITI TEST

Pattern AAA (Arrange-Act-Assert)

@pytest.mark.unit
async def test_create_user_valid_email_succeeds():
    # Arrange
    email = "test@example.com"
    password_hash = "hashed_password"
    
    # Act
    user = User(email=email, password_hash=password_hash)
    
    # Assert
    assert user.email == email
    assert user.password_hash == password_hash
    assert user.is_active is True
    assert user.created_at is not None

Marker Pytest

@pytest.mark.unit              # Logica pura
@pytest.mark.integration       # Con database
@pytest.mark.asyncio           # Funzioni async

Fixtures Condivise (in conftest.py)

@pytest.fixture
def db_session():
    # Sessione database per test
    
@pytest.fixture
def sample_user():
    # Utente di esempio
    
@pytest.fixture
def sample_api_key():
    # API key di esempio

🛡️ VINCOLI TECNICI

SQLAlchemy Configuration

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, Session

Base = declarative_base()

engine = create_engine(
    DATABASE_URL,
    connect_args={"check_same_thread": False}  # Solo per SQLite
)

SessionLocal = sessionmaker(
    autocommit=False,
    autoflush=False,
    bind=engine,
    expire_on_commit=False
)

Model Base Requirements

  • Tutti i modelli ereditano da Base
  • Usare type hints
  • Configurare __tablename__
  • Definire relationships esplicite
  • Usare ondelete="CASCADE" per FK

Alembic Requirements

  • Importare Base da models in env.py
  • Configurare target_metadata = Base.metadata
  • Generare migration: alembic revision --autogenerate -m "initial schema"

📊 AGGIORNAMENTO PROGRESS

Dopo ogni task completato, aggiorna: /home/google/Sources/LucaSacchiNet/openrouter-watcher/export/progress.md

Esempio:

### 🗄️ Database & Models (T06-T11)

- [x] T06: Creare database.py - Completato [timestamp]
- [x] T07: Creare model User - Completato [timestamp]
- [ ] T08: Creare model ApiKey - In progress
- [ ] T09: Creare model UsageStats
- [ ] T10: Creare model ApiToken
- [ ] T11: Setup Alembic e migrazione

**Progresso sezione:** 33% (2/6 task)

CRITERI DI ACCETTAZIONE

  • T06: database.py con engine, session, get_db(), init_db()
  • T07: Model User completo con relationships e constraints
  • T08: Model ApiKey completo con relationships
  • T09: Model UsageStats con unique constraint e defaults
  • T10: Model ApiToken completo con relationships
  • T11: Alembic inizializzato con migrazione funzionante
  • Tutti i test passano (pytest tests/unit/models/)
  • Coverage >= 90%
  • 6 commit atomici (uno per task)
  • progress.md aggiornato con tutti i task completati

🚀 COMANDO DI VERIFICA

Al termine, esegui:

cd /home/google/Sources/LucaSacchiNet/openrouter-watcher
pytest tests/unit/models/ -v --cov=src/openrouter_monitor/models
alembic upgrade head
alembic downgrade -1
alembic upgrade head

📝 NOTE

  • Usa SEMPRE path assoluti: /home/google/Sources/LucaSacchiNet/openrouter-watcher/
  • Segui le convenzioni in .opencode/agents/tdd-developer.md
  • Task devono essere verificabili in < 2 ore
  • Documenta bug complessi in /docs/bug_ledger.md
  • Usa conventional commits: feat(db): T06 create database connection

AGENTE: @tdd-developer
INIZIA CON: T06 - database.py