- 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
399 lines
11 KiB
Markdown
399 lines
11 KiB
Markdown
# 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
|
|
```sql
|
|
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
|
|
```sql
|
|
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
|
|
```sql
|
|
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
|
|
```sql
|
|
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)
|
|
|
|
```python
|
|
@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
|
|
|
|
```python
|
|
@pytest.mark.unit # Logica pura
|
|
@pytest.mark.integration # Con database
|
|
@pytest.mark.asyncio # Funzioni async
|
|
```
|
|
|
|
### Fixtures Condivise (in conftest.py)
|
|
|
|
```python
|
|
@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
|
|
|
|
```python
|
|
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:
|
|
```markdown
|
|
### 🗄️ 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:
|
|
```bash
|
|
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
|