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
This commit is contained in:
398
prompt/prompt-database-models.md
Normal file
398
prompt/prompt-database-models.md
Normal file
@@ -0,0 +1,398 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user