From 75f40acb1761f12d8ef969dba3ef7e031a355c18 Mon Sep 17 00:00:00 2001 From: Luca Sacchi Ricciardi Date: Tue, 7 Apr 2026 09:44:41 +0200 Subject: [PATCH] feat(setup): T01 create project directory structure - Create src/openrouter_monitor/ package structure - Create models/, routers/, services/, utils/ subpackages - Create tests/unit/ and tests/integration/ structure - Create alembic/, docs/, scripts/ directories - Add test_project_structure.py with 13 unit tests - All tests passing (13/13) Refs: T01 --- .opencode/WORKFLOW.md | 164 +++ .opencode/agents/git-manager.md | 175 +++ .opencode/agents/security-reviewer.md | 88 ++ .opencode/agents/spec-architect.md | 73 ++ .opencode/agents/tdd-developer.md | 163 +++ .opencode/opencode.json | 29 + .opencode/skills/project-guidelines/SKILL.md | 221 ++++ export/architecture.md | 1094 +++++++++++++++++ export/kanban.md | 242 ++++ export/progress.md | 196 +++ prd.md | 333 +++++ prompt/prompt-zero.md | 226 ++++ src/openrouter_monitor/__init__.py | 0 src/openrouter_monitor/config.py | 0 src/openrouter_monitor/database.py | 0 src/openrouter_monitor/main.py | 0 src/openrouter_monitor/models/__init__.py | 0 src/openrouter_monitor/routers/__init__.py | 0 src/openrouter_monitor/services/__init__.py | 0 src/openrouter_monitor/utils/__init__.py | 0 .../conftest.cpython-313-pytest-9.0.2.pyc | Bin 0 -> 168 bytes tests/conftest.py | 0 tests/integration/__init__.py | 0 tests/unit/__init__.py | 0 .../unit/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 173 bytes ...ect_structure.cpython-313-pytest-9.0.2.pyc | Bin 0 -> 26357 bytes tests/unit/test_project_structure.py | 90 ++ 27 files changed, 3094 insertions(+) create mode 100644 .opencode/WORKFLOW.md create mode 100644 .opencode/agents/git-manager.md create mode 100644 .opencode/agents/security-reviewer.md create mode 100644 .opencode/agents/spec-architect.md create mode 100644 .opencode/agents/tdd-developer.md create mode 100644 .opencode/opencode.json create mode 100644 .opencode/skills/project-guidelines/SKILL.md create mode 100644 export/architecture.md create mode 100644 export/kanban.md create mode 100644 export/progress.md create mode 100644 prd.md create mode 100644 prompt/prompt-zero.md create mode 100644 src/openrouter_monitor/__init__.py create mode 100644 src/openrouter_monitor/config.py create mode 100644 src/openrouter_monitor/database.py create mode 100644 src/openrouter_monitor/main.py create mode 100644 src/openrouter_monitor/models/__init__.py create mode 100644 src/openrouter_monitor/routers/__init__.py create mode 100644 src/openrouter_monitor/services/__init__.py create mode 100644 src/openrouter_monitor/utils/__init__.py create mode 100644 tests/__pycache__/conftest.cpython-313-pytest-9.0.2.pyc create mode 100644 tests/conftest.py create mode 100644 tests/integration/__init__.py create mode 100644 tests/unit/__init__.py create mode 100644 tests/unit/__pycache__/__init__.cpython-313.pyc create mode 100644 tests/unit/__pycache__/test_project_structure.cpython-313-pytest-9.0.2.pyc create mode 100644 tests/unit/test_project_structure.py diff --git a/.opencode/WORKFLOW.md b/.opencode/WORKFLOW.md new file mode 100644 index 0000000..a6bb368 --- /dev/null +++ b/.opencode/WORKFLOW.md @@ -0,0 +1,164 @@ +# Flusso di Lavoro Obbligatorio - getNotebooklmPower + +> **Regola fondamentale:** *Safety first, little often, double check* + +## 1. Contesto (Prima di ogni task) + +**OBBLIGATORIO:** Prima di implementare qualsiasi funzionalità: + +1. **Leggi il PRD**: Leggi sempre `/home/google/Sources/LucaSacchiNet/getNotebooklmPower/prd.md` per capire i requisiti del task corrente +2. **Non implementare mai funzionalità non esplicitamente richieste** +3. **Scope check**: Verifica che il task rientri nello scope definito nel PRD + +## 2. TDD (Test-Driven Development) + +**Ciclo RED → GREEN → REFACTOR:** + +1. **RED**: Scrivi PRIMA il test fallimentare per la singola funzionalità +2. **GREEN**: Scrivi il codice applicativo minimo necessario per far passare il test +3. **REFACTOR**: Migliora il codice mantenendo i test verdi +4. **Itera** finché la funzionalità non è completa e tutti i test passano + +**Regole TDD:** +- Un test per singolo comportamento +- Testare prima i casi limite (errori, input invalidi) +- Coverage target: ≥90% +- Usa AAA pattern: Arrange → Act → Assert + +## 3. Memoria e Logging + +**Documentazione obbligatoria:** + +| Evento | Azione | File | +|--------|--------|------| +| Bug complesso risolto | Descrivi il bug e la soluzione | `/home/google/Sources/LucaSacchiNet/getNotebooklmPower/docs/bug_ledger.md` | +| Decisione di design | Documenta il pattern scelto | `/home/google/Sources/LucaSacchiNet/getNotebooklmPower/docs/architecture.md` | +| Cambio architetturale | Aggiorna le scelte architetturali | `/home/google/Sources/LucaSacchiNet/getNotebooklmPower/docs/architecture.md` | +| Inizio task | Aggiorna progresso corrente | `/home/google/Sources/LucaSacchiNet/getNotebooklmPower/export/progress.md` | +| Fine task | Registra completamento | `/home/google/Sources/LucaSacchiNet/getNotebooklmPower/export/progress.md` | +| Blocco riscontrato | Documenta problema e soluzione | `/home/google/Sources/LucaSacchiNet/getNotebooklmPower/export/progress.md` | + +**Formato bug_ledger.md:** +```markdown +## YYYY-MM-DD: [Titolo Bug] +**Sintomo:** [Descrizione sintomo] +**Causa:** [Root cause] +**Soluzione:** [Fix applicato] +**Prevenzione:** [Come evitare in futuro] +``` + +## 4. Git Flow (Commit) + +**Alla fine di ogni task completato con test verdi:** + +1. **Commit atomico**: Un commit per singola modifica funzionale +2. **Conventional Commits** obbligatorio: + ``` + (): + + [optional body] + + [optional footer] + ``` +3. **Tipi ammessi:** + - `feat:` - Nuova funzionalità + - `fix:` - Correzione bug + - `docs:` - Documentazione + - `test:` - Test + - `refactor:` - Refactoring + - `chore:` - Manutenzione +4. **Scope**: api, webhook, skill, notebook, source, artifact, auth, core +5. **Documenta il commit**: Aggiorna `/home/google/Sources/LucaSacchiNet/getNotebooklmPower/export/githistory.md` con contesto e spiegazione + +**Esempi:** +```bash +feat(api): add notebook creation endpoint + +- Implements POST /api/v1/notebooks +- Validates title length (max 100 chars) +- Returns 201 with notebook details + +Closes #123 +``` + +**Formato githistory.md:** +```markdown +## 2026-04-05 14:30 - feat(api): add notebook creation endpoint + +**Hash:** `a1b2c3d` +**Autore:** @tdd-developer +**Branch:** main + +### Contesto +Necessità di creare notebook programmaticamente via API per integrazione con altri agenti. + +### Cosa cambia +- Aggiunto endpoint POST /api/v1/notebooks +- Implementata validazione titolo (max 100 chars) +- Aggiunto test coverage 95% + +### Perché +Il PRD richiede CRUD operations su notebook. Questo è il primo endpoint implementato. + +### Impatto +- [x] Nuova feature +- [ ] Breaking change +- [ ] Modifica API + +### File modificati +- src/api/routes/notebooks.py - Nuovo endpoint +- src/services/notebook_service.py - Logica creazione +- tests/unit/test_notebook_service.py - Test unitari + +### Note +Closes #42 +``` + +## 5. Spec-Driven Development (SDD) + +**Prima di scrivere codice, definisci le specifiche:** + +### 5.1 Analisi Profonda +- Fai domande mirate per chiarire dubbi architetturali o di business +- Non procedere con specifiche vaghe +- Verifica vincoli tecnici e dipendenze + +### 5.2 Output Richiesti (cartella `/home/google/Sources/LucaSacchiNet/getNotebooklmPower/export/`) + +Tutto il lavoro di specifica si concretizza in questi file: + +| File | Contenuto | +|------|-----------| +| `/home/google/Sources/LucaSacchiNet/getNotebooklmPower/export/prd.md` | Product Requirements Document (obiettivi, user stories, requisiti tecnici) | +| `/home/google/Sources/LucaSacchiNet/getNotebooklmPower/export/architecture.md` | Scelte architetturali, stack tecnologico, diagrammi di flusso | +| `/home/google/Sources/LucaSacchiNet/getNotebooklmPower/export/kanban.md` | Scomposizione in task minimi e verificabili (regola "little often") | + +### 5.3 Principio "Little Often" +- Scomporre in task il più piccoli possibile +- Ogni task deve essere verificabile in modo indipendente +- Progresso incrementale, mai "big bang" + +### 5.4 Rigore +- **Sii diretto, conciso e tecnico** +- **Se una richiesta è vaga, non inventare: chiedi di precisare** +- Nessuna supposizione non verificata + +## Checklist Pre-Implementazione + +- [ ] Ho letto il PRD in `/home/google/Sources/LucaSacchiNet/getNotebooklmPower/prd.md` +- [ ] Ho compreso lo scope del task +- [ ] Ho scritto il test fallimentare (RED) +- [ ] Ho implementato il codice minimo (GREEN) +- [ ] Ho refactoring mantenendo test verdi +- [ ] Ho aggiornato `bug_ledger.md` se necessario +- [ ] Ho aggiornato `architecture.md` se necessario +- [ ] Ho creato un commit atomico con conventional commit + +## Checklist Spec-Driven (per nuove feature) + +- [ ] Ho analizzato in profondità i requisiti +- [ ] Ho chiesto chiarimenti sui punti vaghi +- [ ] Ho creato/aggiormaneto `/home/google/Sources/LucaSacchiNet/getNotebooklmPower/export/prd.md` +- [ ] Ho creato/aggiormaneto `/home/google/Sources/LucaSacchiNet/getNotebooklmPower/export/architecture.md` +- [ ] Ho creato/aggiormaneto `/home/google/Sources/LucaSacchiNet/getNotebooklmPower/export/kanban.md` +- [ ] I task sono scomposti secondo "little often" diff --git a/.opencode/agents/git-manager.md b/.opencode/agents/git-manager.md new file mode 100644 index 0000000..41316ea --- /dev/null +++ b/.opencode/agents/git-manager.md @@ -0,0 +1,175 @@ +# Agente: Git Flow Manager + +## Ruolo +Responsabile della gestione dei commit e del flusso Git. + +## Responsabilità + +1. **Commit Atomici** + - Un commit per singola modifica funzionale + - Mai commit parziali o "work in progress" + - Solo codice con test verdi + +2. **Conventional Commits** + - Formato rigoroso obbligatorio + - Tipi e scope corretti + - Messaggi descrittivi + +3. **Organizzazione Branch** + - Naming conventions + - Flusso feature branch + +## Formato Commit + +``` +(): + +[optional body: spiega cosa e perché, non come] + +[optional footer: BREAKING CHANGE, Fixes #123, etc.] +``` + +### Tipi (type) + +| Tipo | Uso | Esempio | +|------|-----|---------| +| `feat` | Nuova funzionalità | `feat(api): add notebook creation endpoint` | +| `fix` | Correzione bug | `fix(webhook): retry logic exponential backoff` | +| `docs` | Documentazione | `docs(api): update OpenAPI schema` | +| `style` | Formattazione | `style: format with ruff` | +| `refactor` | Refactoring | `refactor(notebook): extract validation logic` | +| `test` | Test | `test(source): add unit tests for URL validation` | +| `chore` | Manutenzione | `chore(deps): upgrade notebooklm-py` | +| `ci` | CI/CD | `ci: add GitHub Actions workflow` | + +### Scope + +- `api` - REST API endpoints +- `webhook` - Webhook system +- `skill` - AI skill interface +- `notebook` - Notebook operations +- `source` - Source management +- `artifact` - Artifact generation +- `auth` - Authentication +- `core` - Core utilities + +### Esempi + +**Feature:** +``` +feat(api): add POST /notebooks endpoint + +- Implements notebook creation with validation +- Returns 201 with notebook details +- Validates title length (max 100 chars) + +Closes #42 +``` + +**Bug fix:** +``` +fix(webhook): exponential backoff not working + +Retry attempts were using fixed 1s delay instead of +exponential backoff. Fixed calculation in retry.py. + +Fixes #55 +``` + +**Test:** +``` +test(notebook): add unit tests for create_notebook + +- Valid title returns notebook +- Empty title raises ValidationError +- Long title raises ValidationError +``` + +## Branch Naming + +| Tipo | Pattern | Esempio | +|------|---------|---------| +| Feature | `feat/` | `feat/notebook-crud` | +| Bugfix | `fix/` | `fix/webhook-retry` | +| Hotfix | `hotfix/` | `hotfix/auth-bypass` | +| Release | `release/v` | `release/v1.0.0` | + +## Checklist Pre-Commit + +- [ ] Tutti i test passano (`uv run pytest`) +- [ ] Code quality OK (`uv run ruff check`) +- [ ] Type checking OK (`uv run mypy`) +- [ ] Commit atomico (una sola funzionalità) +- [ ] Messaggio segue Conventional Commits +- [ ] Scope appropriato +- [ ] Body descrittivo se necessario + +## Flusso di Lavoro + +1. **Prepara il commit:** + ```bash + uv run pytest # Verifica test + uv run ruff check # Verifica linting + uv run pre-commit run # Verifica hook + ``` + +2. **Stage file:** + ```bash + git add # Non usare git add . + ``` + +3. **Commit:** + ```bash + git commit -m "feat(api): add notebook creation endpoint + + - Implements POST /api/v1/notebooks + - Validates title length + - Returns 201 with notebook details + + Closes #123" + ``` + +4. **Documenta in githistory.md:** + - Aggiorna `/home/google/Sources/LucaSacchiNet/openrouter-watcher/export/githistory.md` + - Aggiungi entry con contesto, motivazione, impatto + - Inserisci in cima (più recente prima) + +## Documentazione Commit (githistory.md) + +Ogni commit DEVE essere documentato in `/home/google/Sources/LucaSacchiNet/openrouter-watcher/export/githistory.md`: + +```markdown +## YYYY-MM-DD HH:MM - type(scope): description + +**Hash:** `commit-hash` +**Autore:** @agent +**Branch:** branch-name + +### Contesto +[Perché questo commit era necessario] + +### Cosa cambia +[Descrizione modifiche] + +### Perché +[Motivazione scelte] + +### Impatto +- [x] Nuova feature / Bug fix / Refactoring / etc + +### File modificati +- `file.py` - descrizione cambiamento + +### Note +[Riferimenti issue, considerazioni] +``` + +## Comportamento Vietato + +- ❌ Commit con test falliti +- ❌ `git add .` (selezionare file specifici) +- ❌ Messaggi vaghi: "fix stuff", "update", "WIP" +- ❌ Commit multi-funzionalità +- ❌ Push force su main +- ❌ Commit senza scope quando applicabile +- ❌ Mancata documentazione in `githistory.md` diff --git a/.opencode/agents/security-reviewer.md b/.opencode/agents/security-reviewer.md new file mode 100644 index 0000000..f0296f5 --- /dev/null +++ b/.opencode/agents/security-reviewer.md @@ -0,0 +1,88 @@ +# Agente: Security Reviewer + +## Ruolo +Responsabile della revisione della sicurezza e della conformità alle best practices di sicurezza. + +## Responsabilità + +1. **Code Security Review** + - Revisionare codice per vulnerabilità comuni + - Verificare gestione segreti (API key, password, token) + - Controllare validazione input + - Verificare protezione contro SQL injection, XSS, CSRF + +2. **Crittografia** + - Verificare cifratura API key (AES-256) + - Controllare hashing password (bcrypt/Argon2) + - Validare gestione chiavi di cifratura + - Verificare trasmissione sicura (HTTPS) + +3. **Autenticazione e Autorizzazione** + - Validare implementazione JWT + - Verificare scadenza token + - Controllare refresh token flow + - Validare permessi e RBAC + +4. **Compliance** + - Verificare conformità GDPR (dati personali) + - Controllare logging sicuro (no leak dati sensibili) + - Validare rate limiting + +## Checklist Sicurezza + +### Per Ogni Feature + +- [ ] **Input Validation**: Tutti gli input sono validati +- [ ] **Output Encoding**: Prevenzione XSS +- [ ] **Authentication**: Solo utenti autenticati accedono a risorse protette +- [ ] **Authorization**: Verifica permessi per ogni operazione +- [ ] **Secrets Management**: Nessun segreto in codice o log +- [ ] **Error Handling**: Errori non leakano informazioni sensibili +- [ ] **Logging**: Log di sicurezza per operazioni critiche + +### Critico per Questo Progetto + +- [ ] **API Key Encryption**: Chiavi OpenRouter cifrate con AES-256 +- [ ] **Password Hashing**: bcrypt con salt appropriato +- [ ] **JWT Security**: Secret key forte, scadenza breve +- [ ] **Rate Limiting**: Protezione brute force e DoS +- [ ] **SQL Injection**: Query sempre parameterizzate +- [ ] **CSRF Protection**: Token CSRF per form web + +## Output + +Quando trovi problemi di sicurezza, crea: + +```markdown +## Security Review: [Feature/Componente] + +**Data:** YYYY-MM-DD +**Revisore:** @security-reviewer + +### Vulnerabilità Trovate + +#### [ID-001] SQL Injection in endpoint X +- **Livello:** 🔴 Critico / 🟡 Medio / 🟢 Basso +- **File:** `src/path/to/file.py:line` +- **Problema:** Descrizione +- **Fix:** Soluzione proposta + +### Raccomandazioni + +1. [Raccomandazione specifica] + +### Checklist Completata + +- [x] Input validation +- [x] Output encoding +- ... +``` + +Salva in: `/home/google/Sources/LucaSacchiNet/openrouter-watcher/docs/security_reviews/[feature].md` + +## Comportamento Vietato + +- ❌ Approvare codice con vulnerabilità critiche +- ❌ Ignorare best practices di cifratura +- ❌ Permettere logging di dati sensibili +- ❌ Saltare review per "piccole modifiche" diff --git a/.opencode/agents/spec-architect.md b/.opencode/agents/spec-architect.md new file mode 100644 index 0000000..8445979 --- /dev/null +++ b/.opencode/agents/spec-architect.md @@ -0,0 +1,73 @@ +# Agente: Spec-Driven Lead + +## Ruolo +Responsabile della definizione delle specifiche e dell'architettura prima dell'implementazione. + +## Responsabilità + +1. **Analisi dei Requisiti** + - Leggere e comprendere il PRD (`/home/google/Sources/LucaSacchiNet/openrouter-watcher/prd.md`) + - Fare domande mirate per chiarire ambiguità + - Non procedere se i requisiti sono vaghi + +2. **Definizione Specifiche** + - Creare/aggiornare `/home/google/Sources/LucaSacchiNet/openrouter-watcher/export/prd.md` con: + - Obiettivi chiari e misurabili + - User stories (formato: "Come [ruolo], voglio [obiettivo], per [beneficio]") + - Requisiti tecnici specifici + - Criteri di accettazione + +3. **Architettura** + - Creare/aggiornare `/home/google/Sources/LucaSacchiNet/openrouter-watcher/export/architecture.md` con: + - Scelte architetturali + - Stack tecnologico + - Diagrammi di flusso + - Interfacce e contratti API + +4. **Pianificazione** + - Creare/aggiornare `/home/google/Sources/LucaSacchiNet/openrouter-watcher/export/kanban.md` con: + - Scomposizione in task minimi + - Dipendenze tra task + - Stima complessità + - Regola "little often": task verificabili in <2 ore + +## Principi Guida + +- **Rigore**: Essere diretti, concisi, tecnici +- **Nessuna Supposizione**: Se qualcosa è vago, chiedere +- **Little Often**: Task piccoli, progresso incrementale +- **Output Definiti**: Solo i 3 file in /export/ sono l'output valido + +## Domande da Fare (Checklist) + +Prima di iniziare: +- [ ] Qual è il problema che stiamo risolvendo? +- [ ] Chi sono gli utenti finali? +- [ ] Quali sono i vincoli tecnici? +- [ ] Ci sono dipendenze da altri componenti? +- [ ] Qual è il criterio di successo? +- [ ] Quali sono i casi limite/errori da gestire? + +## Output Attesi + +``` +/home/google/Sources/LucaSacchiNet/openrouter-watcher/export/ +├── prd.md # Requisiti prodotto +├── architecture.md # Architettura sistema +├── kanban.md # Task breakdown +└── progress.md # Tracciamento progresso +``` + +## Progress Tracking + +Quando crei una nuova feature/specifica: +1. Inizializza `progress.md` con la feature corrente +2. Imposta stato a "🔴 Pianificazione" +3. Aggiorna metriche e task pianificate + +## Comportamento Vietato + +- ❌ Inventare requisiti non espliciti +- ❌ Procedere senza specifiche chiare +- ❌ Creare task troppo grandi +- ❌ Ignorare vincoli tecnici diff --git a/.opencode/agents/tdd-developer.md b/.opencode/agents/tdd-developer.md new file mode 100644 index 0000000..c9bc9d9 --- /dev/null +++ b/.opencode/agents/tdd-developer.md @@ -0,0 +1,163 @@ +# Agente: TDD Developer + +## Ruolo +Responsabile dell'implementazione seguendo rigorosamente il Test-Driven Development. + +## Responsabilità + +1. **Sviluppo TDD** + - Seguire il ciclo RED → GREEN → REFACTOR + - Implementare una singola funzionalità alla volta + - Non saltare mai la fase di test + +2. **Qualità del Codice** + - Scrivere codice minimo per passare il test + - Refactoring continuo + - Coverage ≥90% + +3. **Documentazione** +- Aggiornare `/home/google/Sources/LucaSacchiNet/openrouter-watcher/docs/bug_ledger.md` per bug complessi +- Aggiornare `/home/google/Sources/LucaSacchiNet/openrouter-watcher/docs/architecture.md` per cambi di design +- Aggiornare `/home/google/Sources/LucaSacchiNet/openrouter-watcher/export/progress.md` all'inizio e fine di ogni task + +4. **Git** + - Commit atomici alla fine di ogni task verde + - Conventional commits obbligatori + +## Progress Tracking + +All'inizio di ogni task: +1. Apri `progress.md` +2. Aggiorna "Task Corrente" con ID e descrizione +3. Imposta stato a "🟡 In progress" +4. Aggiorna timestamp inizio + +Al completamento: +1. Sposta task in "Task Completate" +2. Aggiungi commit reference +3. Aggiorna percentuale completamento +4. Aggiorna timestamp fine +5. Documenta commit in `githistory.md` con contesto e motivazione + +## Ciclo di Lavoro TDD + +### Fase 1: RED (Scrivere il test) +```python +# tests/unit/test_notebook_service.py +async def test_create_notebook_empty_title_raises_validation_error(): + """Test that empty title raises ValidationError.""" + # Arrange + service = NotebookService() + + # Act & Assert + with pytest.raises(ValidationError, match="Title cannot be empty"): + await service.create_notebook(title="") +``` +**Verifica:** Il test DEVE fallire + +### Fase 2: GREEN (Implementare minimo) +```python +# src/notebooklm_agent/services/notebook_service.py +async def create_notebook(self, title: str) -> Notebook: + if not title or not title.strip(): + raise ValidationError("Title cannot be empty") + # ... implementazione minima +``` +**Verifica:** Il test DEVE passare + +### Fase 3: REFACTOR (Migliorare) +```python +# Pulire codice, rimuovere duplicazione, migliorare nomi +# I test devono rimanere verdi +``` + +## Pattern di Test (AAA) + +```python +async def test_create_notebook_valid_title_returns_created(): + # Arrange - Setup + title = "Test Notebook" + service = NotebookService() + + # Act - Execute + result = await service.create_notebook(title) + + # Assert - Verify + assert result.title == title + assert result.id is not None + assert result.created_at is not None +``` + +## Regole di Test + +1. **Un test = Un comportamento** +2. **Testare prima i casi d'errore** +3. **Nomi descrittivi**: `test___` +4. **No logic in tests**: No if/else, no loop +5. **Isolamento**: Mock per dipendenze esterne + +## Struttura Test + +``` +tests/ +├── unit/ # Logica pura, no I/O +│ ├── test_services/ +│ └── test_core/ +├── integration/ # Con dipendenze mockate +│ └── test_api/ +└── e2e/ # Flussi completi + └── test_workflows/ +``` + +## Convenzioni + +### Nomenclatura +- File: `test_.py` +- Funzioni: `test___` +- Classi: `Test` + +### Marker pytest +```python +@pytest.mark.unit +def test_pure_function(): + pass + +@pytest.mark.integration +def test_with_http(): + pass + +@pytest.mark.e2e +def test_full_workflow(): + pass + +@pytest.mark.asyncio +async def test_async(): + pass +``` + +## Documentazione Bug + +Quando risolvi un bug complesso, aggiungi a `/home/google/Sources/LucaSacchiNet/openrouter-watcher/docs/bug_ledger.md`: + +```markdown +## 2026-04-05: Race condition in webhook dispatch + +**Sintomo:** Webhook duplicati inviati sotto carico + +**Causa:** Manca lock su dispatcher, richieste concorrenti causano doppia delivery + +**Soluzione:** Aggiunto asyncio.Lock() nel dispatcher, sequentializza invio + +**Prevenzione:** +- Test di carico obbligatori per componenti async +- Review focus su race condition +- Documentare comportamento thread-safe nei docstring +``` + +## Comportamento Vietato + +- ❌ Scrivere codice senza test prima +- ❌ Implementare più funzionalità insieme +- ❌ Ignorare test che falliscono +- ❌ Commit con test rossi +- ❌ Copertura <90% diff --git a/.opencode/opencode.json b/.opencode/opencode.json new file mode 100644 index 0000000..5e3ed0e --- /dev/null +++ b/.opencode/opencode.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://opencode.ai/config.json", + "mcp": { + "sequential-thinking": { + "type": "local", + "command": [ + "npx", + "-y", + "@modelcontextprotocol/server-sequential-thinking" + ] + }, + "context7": { + "type": "local", + "command": [ + "npx", + "-y", + "@context7/mcp-server" + ] + }, + "universal-skills": { + "type": "local", + "command": [ + "npx", + "-y", + "github:jacob-bd/universal-skills-manager" + ] + } + } +} diff --git a/.opencode/skills/project-guidelines/SKILL.md b/.opencode/skills/project-guidelines/SKILL.md new file mode 100644 index 0000000..61ac4b0 --- /dev/null +++ b/.opencode/skills/project-guidelines/SKILL.md @@ -0,0 +1,221 @@ +--- +name: project-guidelines +description: Linee guida per lo sviluppo del progetto. Usa questa skill per comprendere l'architettura, le convenzioni di codice e il workflow di sviluppo. +--- + +# Project Guidelines - [NOME PROGETTO] + +> ⚠️ **NOTA**: Personalizza questo file con il nome e la descrizione del tuo progetto! + +## Panoramica del Progetto + +**[NOME PROGETTO]** è [breve descrizione del progetto - da personalizzare]. + +## Quick Start + +### Leggere Prima +1. **Workflow**: `.opencode/WORKFLOW.md` - Flusso di lavoro obbligatorio +2. **PRD**: `prd.md` - Requisiti prodotto +3. **AGENTS.md**: Linee guida generali del progetto (se esiste) + +### Agenti Disponibili (in `.opencode/agents/`) + +| Agente | Ruolo | Quando Usare | +|--------|-------|--------------| +| `@spec-architect` | Definisce specifiche e architettura | Prima di nuove feature | +| `@tdd-developer` | Implementazione TDD | Durante sviluppo | +| `@git-manager` | Gestione commit Git | A fine task | + +## Flusso di Lavoro (OBBLIGATORIO) + +### Per Nuove Feature + +``` +1. @spec-architect → Legge PRD, definisce specifiche + ↓ + Crea/aggiorna: + - /export/prd.md + - /export/architecture.md + - /export/kanban.md + ↓ +2. @tdd-developer → Implementa seguendo TDD + ↓ + RED → GREEN → REFACTOR + ↓ +3. @git-manager → Commit atomico + ↓ + Conventional Commit +``` + +### Per Bug Fix + +``` +1. Leggi bug_ledger.md per pattern simili +2. Scrivi test che riproduce il bug +3. Implementa fix +4. Aggiorna bug_ledger.md +5. Commit con tipo "fix:" +``` + +## Regole Fondamentali + +### 1. TDD (Test-Driven Development) +- **RED**: Scrivi test fallimentare PRIMA +- **GREEN**: Scrivi codice minimo per passare +- **REFACTOR**: Migliora mantenendo test verdi + +### 2. Spec-Driven +- Leggi sempre `prd.md` prima di implementare +- Non implementare funzionalità non richieste +- Output specifiche in `/export/` + +### 3. Little Often +- Task piccoli e verificabili +- Progresso incrementale +- Commit atomici + +### 4. Memoria +- Bug complessi → `docs/bug_ledger.md` +- Decisioni design → `docs/architecture.md` +- Progresso task → `export/progress.md` (aggiorna inizio/fine task) + +### 5. Git +- Conventional commits obbligatori +- Commit atomici +- Test verdi prima del commit +- Documenta contesto in `export/githistory.md` + +## Struttura Progetto (Personalizza) + +``` +[nome-progetto]/ +├── src/ # Codice sorgente +│ └── [nome_package]/ +│ ├── [moduli]/ # Moduli applicativi +│ └── ... +├── tests/ # Test suite +│ ├── unit/ +│ ├── integration/ +│ └── e2e/ +├── docs/ # Documentazione +│ ├── bug_ledger.md # Log bug risolti +│ └── architecture.md # Decisioni architetturali +├── export/ # Output spec-driven +│ ├── prd.md # Product Requirements +│ ├── architecture.md # Architettura +│ ├── kanban.md # Task breakdown +│ ├── progress.md # Tracciamento progresso +│ └── githistory.md # Storico commit +├── .opencode/ # Configurazione OpenCode +│ ├── WORKFLOW.md # Flusso di lavoro +│ ├── agents/ # Configurazioni agenti +│ └── skills/ # Skill condivise +├── scripts/ # Script utilità +├── prd.md # Product Requirements (root) +├── AGENTS.md # Linee guida generali (opzionale) +└── SKILL.md # Questo file +``` + +## Convenzioni di Codice (Personalizza) + +### [Linguaggio - es. Python/JavaScript/Go] +- Versione: [es. 3.10+] +- Stile: [es. PEP 8 / StandardJS / gofmt] +- Type hints: [obbligatorio/consigliato] +- Line length: [es. 100 caratteri] + +### Testing +- Framework: [pytest/jest/go test] +- Coverage target: ≥90% +- Pattern: AAA (Arrange-Act-Assert) +- Mock per dipendenze esterne + +### Commit +``` +(): + +[body] + +[footer] +``` + +**Tipi:** feat, fix, docs, test, refactor, chore, ci, style +**Scope:** [personalizza in base al progetto - es. api, db, ui, core] + +## Risorse + +| File | Scopo | +|------|-------| +| `prd.md` | Requisiti prodotto | +| `AGENTS.md` | Linee guida progetto (se esiste) | +| `.opencode/WORKFLOW.md` | Flusso di lavoro dettagliato | +| `.opencode/agents/` | Configurazioni agenti | +| `docs/bug_ledger.md` | Log bug risolti | +| `docs/architecture.md` | Decisioni architetturali | +| `export/progress.md` | Tracciamento progresso task | +| `export/githistory.md` | Storico commit con contesto | +| `CHANGELOG.md` | Changelog | +| `CONTRIBUTING.md` | Guida contribuzione | + +## Comandi Utili (Personalizza) + +```bash +# Test +[comando test] # Tutti i test +[comando test --coverage] # Con coverage + +# Qualità +[comando lint] # Linting +[comando format] # Formattazione +[comando type-check] # Type checking + +# Pre-commit +[comando pre-commit] + +# Server/Run +[comando run] +``` + +## Checklist + +### Setup Iniziale (da fare una volta) +- [ ] Personalizzato `SKILL.md` con nome progetto +- [ ] Creata struttura cartelle `src/` +- [ ] Configurato ambiente di sviluppo +- [ ] Inizializzato `prd.md` con requisiti +- [ ] Inizializzato `export/kanban.md` con task + +### Pre-Implementazione +- [ ] Ho letto `prd.md` +- [ ] Ho compreso lo scope +- [ ] Ho letto `.opencode/WORKFLOW.md` + +### Durante Implementazione +- [ ] Test scritto prima (RED) +- [ ] Codice minimo (GREEN) +- [ ] Refactoring (REFACTOR) + +### Post-Implementazione +- [ ] Tutti i test passano +- [ ] Coverage ≥90% +- [ ] `bug_ledger.md` aggiornato (se bug) +- [ ] `architecture.md` aggiornato (se design) +- [ ] `progress.md` aggiornato (inizio/fine task) +- [ ] `githistory.md` aggiornato (contesto commit) +- [ ] Commit con conventional commits + +--- + +*Per dettagli su flusso di lavoro, vedere `.opencode/WORKFLOW.md`* + +--- + +## 📝 Note per l'Utente + +Questo è un template. Per usarlo: + +1. **Sostituisci** `[NOME PROGETTO]` con il nome reale +2. **Descrivi** il progetto nella sezione Panoramica +3. **Personalizza** la struttura cartelle in base al tuo stack +4. **Aggiungi** comandi specifici del tuo linguaggio/framework +5. **Definisci** gli scope dei commit pertinenti al tuo progetto diff --git a/export/architecture.md b/export/architecture.md new file mode 100644 index 0000000..946e7d1 --- /dev/null +++ b/export/architecture.md @@ -0,0 +1,1094 @@ +# Architecture Document + +## OpenRouter API Key Monitor - Fase 1 (MVP) + +--- + +## 1. Stack Tecnologico + +| Componente | Tecnologia | Versione | Note | +|------------|------------|----------|------| +| **Runtime** | Python | 3.11+ | Type hints, async/await | +| **Web Framework** | FastAPI | 0.104+ | Async, automatic OpenAPI docs | +| **Database** | SQLite | 3.39+ | Embedded, zero-config | +| **ORM** | SQLAlchemy | 2.0+ | Modern declarative syntax | +| **Migrations** | Alembic | 1.12+ | Database versioning | +| **Auth** | python-jose | 3.3+ | JWT tokens | +| **Password Hash** | bcrypt | 4.1+ | Industry standard | +| **Encryption** | cryptography | 41.0+ | AES-256-GCM | +| **Frontend** | HTMX | 1.9+ | Server-side rendering | +| **CSS** | Tailwind CSS | 3.4+ | Utility-first | +| **Background Tasks** | APScheduler | 3.10+ | Cron jobs | +| **Testing** | pytest | 7.4+ | Async support | +| **HTTP Client** | httpx | 0.25+ | Async requests | +| **Validation** | Pydantic | 2.5+ | Data validation | + +--- + +## 2. Struttura Cartelle Progetto + +``` +/home/google/Sources/LucaSacchiNet/openrouter-watcher/ +├── app/ +│ ├── __init__.py +│ ├── main.py # Entry point FastAPI +│ ├── config.py # Configuration management +│ ├── database.py # DB connection & session +│ ├── dependencies.py # FastAPI dependencies +│ ├── models/ # SQLAlchemy models +│ │ ├── __init__.py +│ │ ├── user.py +│ │ ├── api_key.py +│ │ ├── usage_stats.py +│ │ └── api_token.py +│ ├── schemas/ # Pydantic schemas +│ │ ├── __init__.py +│ │ ├── auth.py +│ │ ├── user.py +│ │ ├── api_key.py +│ │ └── stats.py +│ ├── routers/ # API endpoints +│ │ ├── __init__.py +│ │ ├── auth.py # Login/register/logout +│ │ ├── web.py # HTML pages (HTMX) +│ │ ├── api_keys.py # CRUD API keys +│ │ └── public_api.py # Public API v1 +│ ├── services/ # Business logic +│ │ ├── __init__.py +│ │ ├── auth_service.py +│ │ ├── encryption.py +│ │ ├── openrouter.py +│ │ └── stats_service.py +│ ├── templates/ # Jinja2 templates +│ │ ├── base.html +│ │ ├── login.html +│ │ ├── register.html +│ │ ├── dashboard.html +│ │ ├── keys.html +│ │ └── partials/ # HTMX fragments +│ ├── static/ # CSS, JS, images +│ │ ├── css/ +│ │ └── js/ +│ └── tasks/ # Background jobs +│ ├── __init__.py +│ └── sync.py +├── alembic/ # Database migrations +│ ├── versions/ +│ └── env.py +├── tests/ +│ ├── __init__.py +│ ├── conftest.py +│ ├── test_auth.py +│ ├── test_api_keys.py +│ └── test_public_api.py +├── docs/ +├── export/ # Project specifications +│ ├── prd.md +│ ├── architecture.md +│ ├── kanban.md +│ └── progress.md +├── .env.example # Environment template +├── requirements.txt +├── requirements-dev.txt +├── alembic.ini +├── pytest.ini +└── README.md +``` + +--- + +## 3. Schema Database + +### 3.1 Diagramma ER (ASCII) + +``` +┌─────────────┐ ┌─────────────┐ ┌─────────────┐ +│ users │ │ api_keys │ │ usage_stats │ +├─────────────┤ ├─────────────┤ ├─────────────┤ +│ PK id │◄──────┤ FK user_id │◄──────┤ FK key_id │ +│ email │ 1:N │ name │ 1:N │ date │ +│ password │ │ key_enc │ │ model │ +│ created │ │ is_active│ │ requests │ +│ updated │ │ created │ │ tokens_in│ +│ active │ │ last_used│ │ tokens_out│ +└─────────────┘ └─────────────┘ │ cost │ + ▲ └─────────────┘ + │ ▲ + │ │ + │ ┌──────┴──────┐ + │ │ api_tokens │ + │ ├─────────────┤ + └────────────────────────────────────┤ FK user_id │ + 1:N │ token_hash + │ name │ + │ created │ + │ last_used│ + │ active │ + └─────────────┘ +``` + +### 3.2 DDL SQL (SQLite) + +```sql +-- ============================================= +-- Table: 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 '%_@__%.__%') +); + +CREATE INDEX idx_users_email ON users(email); + +-- ============================================= +-- Table: 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 +); + +CREATE INDEX idx_api_keys_user ON api_keys(user_id); +CREATE INDEX idx_api_keys_active ON api_keys(is_active); + +-- ============================================= +-- Table: 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) +); + +CREATE INDEX idx_usage_key ON usage_stats(api_key_id); +CREATE INDEX idx_usage_date ON usage_stats(date); +CREATE INDEX idx_usage_model ON usage_stats(model); + +-- ============================================= +-- Table: api_tokens +-- ============================================= +CREATE TABLE api_tokens ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + token_hash VARCHAR(255) NOT NULL, -- SHA-256 hash of token + 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 +); + +CREATE INDEX idx_api_tokens_user ON api_tokens(user_id); +CREATE INDEX idx_api_tokens_hash ON api_tokens(token_hash); +CREATE INDEX idx_api_tokens_active ON api_tokens(is_active); + +-- ============================================= +-- Triggers per updated_at +-- ============================================= +CREATE TRIGGER update_users_timestamp +AFTER UPDATE ON users +BEGIN + UPDATE users SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id; +END; +``` + +### 3.3 SQLAlchemy Models + +```python +# app/models/user.py +from datetime import datetime +from sqlalchemy import Column, Integer, String, DateTime, Boolean +from sqlalchemy.orm import relationship +from app.database import Base + +class User(Base): + __tablename__ = "users" + + id = Column(Integer, primary_key=True, index=True) + email = Column(String(255), unique=True, index=True, nullable=False) + password_hash = Column(String(255), nullable=False) + created_at = Column(DateTime, default=datetime.utcnow) + updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + is_active = Column(Boolean, default=True) + + api_keys = relationship("ApiKey", back_populates="user", cascade="all, delete-orphan") + api_tokens = relationship("ApiToken", back_populates="user", cascade="all, delete-orphan") + +# app/models/api_key.py +from datetime import datetime +from sqlalchemy import Column, Integer, String, DateTime, Boolean, ForeignKey +from sqlalchemy.orm import relationship +from app.database import Base + +class ApiKey(Base): + __tablename__ = "api_keys" + + id = Column(Integer, primary_key=True, index=True) + user_id = Column(Integer, ForeignKey("users.id"), nullable=False) + name = Column(String(100), nullable=False) + key_encrypted = Column(String, nullable=False) + is_active = Column(Boolean, default=True) + created_at = Column(DateTime, default=datetime.utcnow) + last_used_at = Column(DateTime, nullable=True) + + user = relationship("User", back_populates="api_keys") + usage_stats = relationship("UsageStats", back_populates="api_key", cascade="all, delete-orphan") + +# app/models/usage_stats.py +from datetime import date, datetime +from sqlalchemy import Column, Integer, String, Date, DateTime, Numeric, ForeignKey +from sqlalchemy.orm import relationship +from app.database import Base + +class UsageStats(Base): + __tablename__ = "usage_stats" + + id = Column(Integer, primary_key=True, index=True) + api_key_id = Column(Integer, ForeignKey("api_keys.id"), nullable=False) + date = Column(Date, nullable=False) + model = Column(String(100), nullable=False) + requests_count = Column(Integer, default=0) + tokens_input = Column(Integer, default=0) + tokens_output = Column(Integer, default=0) + cost = Column(Numeric(10, 6), default=0.0) + created_at = Column(DateTime, default=datetime.utcnow) + + api_key = relationship("ApiKey", back_populates="usage_stats") + +# app/models/api_token.py +from datetime import datetime +from sqlalchemy import Column, Integer, String, DateTime, Boolean, ForeignKey +from sqlalchemy.orm import relationship +from app.database import Base + +class ApiToken(Base): + __tablename__ = "api_tokens" + + id = Column(Integer, primary_key=True, index=True) + user_id = Column(Integer, ForeignKey("users.id"), nullable=False) + token_hash = Column(String(255), nullable=False, index=True) + name = Column(String(100), nullable=False) + created_at = Column(DateTime, default=datetime.utcnow) + last_used_at = Column(DateTime, nullable=True) + is_active = Column(Boolean, default=True) + + user = relationship("User", back_populates="api_tokens") +``` + +--- + +## 4. Diagramma Flusso Autenticazione + +### 4.1 Web Authentication Flow (Session/JWT) + +``` +┌──────────┐ POST /auth/login ┌──────────┐ +│ Client │ ─────────────────────────► │ Server │ +│ (Web) │ {email, password} │ │ +└──────────┘ └────┬─────┘ + │ + ▼ + ┌────────────────┐ + │ Validate │ + │ credentials │ + │ (bcrypt) │ + └────┬───────────┘ + │ + ▼ + ┌────────────────┐ + │ Generate JWT │ + │ (HS256) │ + └────┬───────────┘ + │ + ▼ +┌──────────┐ 200 + JWT Cookie ┌──────────┐ +│ Client │ ◄──────────────────────── │ Server │ +│ (Web) │ {access_token} │ │ +└────┬─────┘ └──────────┘ + │ + │ Include JWT in Cookie/Header + ▼ +┌──────────┐ GET /dashboard ┌──────────┐ +│ Client │ ──────────────────────► │ Server │ +│ (Web) │ Cookie: jwt=xxx │ │ +└──────────┘ └────┬─────┘ + │ + ▼ + ┌────────────────┐ + │ Verify JWT │ + │ Signature │ + └────┬───────────┘ + │ + Valid? ────┼────┐ + │ │ No + ▼ ▼ + ┌────────┐ ┌──────────┐ + │ Return │ │ 401 │ + │ Data │ │ Unauthorized + └────┬───┘ └────┬─────┘ + │ │ + ▼ ▼ + ┌──────────┐ ┌──────────┐ + │ Client │ │ Client │ + │ (Web) │ │ (Web) │ + └──────────┘ └──────────┘ +``` + +### 4.2 API Token Authentication Flow (Public API) + +``` +┌──────────┐ POST /api/v1/tokens ┌──────────┐ +│ Client │ ───────────────────────► │ Server │ +│ (Web) │ {name: "My Token"} │ │ +│ (Auth) │ Bearer │ │ +└──────────┘ └────┬─────┘ + │ + ▼ + ┌────────────────┐ + │ Generate │ + │ random token │ + │ (64 chars) │ + └────┬───────────┘ + │ + ▼ + ┌────────────────┐ + │ Hash token │ + │ (SHA-256) │ + │ Store in DB │ + └────┬───────────┘ + │ + ▼ +┌──────────┐ 201 + Plain Token ┌──────────┐ +│ Client │ ◄──────────────────────── │ Server │ +│ (Web) │ {token: "abc123..."} │ │ +│ (Save) │ ⚠️ Only shown once │ │ +└──────────┘ └──────────┘ + +──────────────────────────────────────────────────────────── + +┌──────────┐ GET /api/v1/stats ┌──────────┐ +│ Client │ ──────────────────────► │ Server │ +│ (Ext) │ Authorization: │ │ +│ │ Bearer abc123... │ │ +└──────────┘ └────┬─────┘ + │ + ▼ + ┌────────────────┐ + │ Extract token │ + │ from header │ + └────┬───────────┘ + │ + ▼ + ┌────────────────┐ + │ Hash token │ + │ (SHA-256) │ + └────┬───────────┘ + │ + ▼ + ┌────────────────┐ + │ Lookup in DB │ + │ (api_tokens) │ + └────┬───────────┘ + │ + Found? ────┼────┐ + │ │ No + ▼ ▼ + ┌────────┐ ┌──────────┐ + │ Update │ │ 401 │ + │last_used│ │ Invalid │ + └───┬────┘ │ Token │ + │ └────┬─────┘ + ▼ │ + ┌──────────┐ │ + │ Return │ │ + │ Stats │ │ + └────┬─────┘ │ + │ │ + ▼ ▼ + ┌──────────┐ ┌──────────┐ + │ Client │ │ Client │ + │ (Ext) │ │ (Ext) │ + └──────────┘ └──────────┘ +``` + +--- + +## 5. Interfacce API + +### 5.1 Web Routes (HTML + HTMX) + +| Method | Path | Auth | Description | Response | +|--------|------|------|-------------|----------| +| `GET` | `/` | No | Redirect to dashboard or login | 302 Redirect | +| `GET` | `/login` | No | Login form page | HTML form | +| `POST` | `/login` | No | Submit login credentials | Redirect or error | +| `GET` | `/register` | No | Registration form page | HTML form | +| `POST` | `/register` | No | Submit registration | Redirect or error | +| `GET` | `/logout` | Yes | Logout user | Redirect to login | +| `GET` | `/dashboard` | Yes | Main dashboard view | HTML dashboard | +| `GET` | `/keys` | Yes | API keys list page | HTML table | +| `POST` | `/keys` | Yes | Create new API key | HTMX fragment | +| `PUT` | `/keys/{id}` | Yes | Update API key | HTMX fragment | +| `DELETE` | `/keys/{id}` | Yes | Delete API key | HTMX fragment (empty) | +| `GET` | `/profile` | Yes | User profile page | HTML form | +| `PUT` | `/profile` | Yes | Update profile/password | Redirect or error | + +### 5.2 REST API (JSON) + +#### Auth Endpoints + +**POST /api/auth/login** +```http +Request: +POST /api/auth/login +Content-Type: application/json + +{ + "email": "user@example.com", + "password": "securepassword" +} + +Response 200: +{ + "access_token": "eyJhbGciOiJIUzI1NiIs...", + "token_type": "bearer", + "expires_in": 3600 +} + +Response 401: +{ + "detail": "Invalid credentials" +} +``` + +**POST /api/auth/register** +```http +Request: +POST /api/auth/register +Content-Type: application/json + +{ + "email": "user@example.com", + "password": "securepassword", + "password_confirm": "securepassword" +} + +Response 201: +{ + "id": 1, + "email": "user@example.com", + "created_at": "2024-01-01T00:00:00Z" +} + +Response 400: +{ + "detail": "Email already registered" +} +``` + +#### API Keys Management + +**GET /api/keys** +```http +Request: +GET /api/keys +Authorization: Bearer + +Response 200: +{ + "items": [ + { + "id": 1, + "name": "Production Key", + "is_active": true, + "created_at": "2024-01-01T00:00:00Z", + "last_used_at": "2024-01-15T10:30:00Z" + } + ], + "total": 1 +} +``` + +**POST /api/keys** +```http +Request: +POST /api/keys +Authorization: Bearer +Content-Type: application/json + +{ + "name": "My New Key", + "key": "sk-or-v1-abc123..." // OpenRouter API key +} + +Response 201: +{ + "id": 2, + "name": "My New Key", + "is_active": true, + "created_at": "2024-01-15T12:00:00Z" +} + +Response 400: +{ + "detail": "Invalid API key format" +} +``` + +**PUT /api/keys/{id}** +```http +Request: +PUT /api/keys/1 +Authorization: Bearer +Content-Type: application/json + +{ + "name": "Updated Name", + "is_active": false +} + +Response 200: +{ + "id": 1, + "name": "Updated Name", + "is_active": false, + "created_at": "2024-01-01T00:00:00Z" +} +``` + +**DELETE /api/keys/{id}** +```http +Request: +DELETE /api/keys/1 +Authorization: Bearer + +Response 204: (No content) +``` + +#### Public API v1 (External Access) + +**GET /api/v1/stats** +```http +Request: +GET /api/v1/stats +Authorization: Bearer + +Response 200: +{ + "summary": { + "total_requests": 15234, + "total_cost": 125.50, + "total_tokens_input": 450000, + "total_tokens_output": 180000 + }, + "by_model": [ + { + "model": "anthropic/claude-3-opus", + "requests": 5234, + "cost": 89.30 + }, + { + "model": "openai/gpt-4", + "requests": 10000, + "cost": 36.20 + } + ] +} + +Response 401: +{ + "detail": "Invalid or expired token" +} +``` + +**GET /api/v1/usage** +```http +Request: +GET /api/v1/usage?start_date=2024-01-01&end_date=2024-01-31&page=1&limit=100 +Authorization: Bearer + +Response 200: +{ + "items": [ + { + "date": "2024-01-15", + "model": "anthropic/claude-3-opus", + "requests": 234, + "tokens_input": 45000, + "tokens_output": 12000, + "cost": 8.92 + } + ], + "pagination": { + "page": 1, + "limit": 100, + "total": 45, + "pages": 1 + } +} +``` + +**GET /api/v1/keys** +```http +Request: +GET /api/v1/keys +Authorization: Bearer + +Response 200: +{ + "items": [ + { + "id": 1, + "name": "Production Key", + "is_active": true, + "stats": { + "total_requests": 15234, + "total_cost": 125.50 + } + } + ] +} +``` + +#### API Token Management + +**POST /api/tokens** +```http +Request: +POST /api/tokens +Authorization: Bearer +Content-Type: application/json + +{ + "name": "Integration Token" +} + +Response 201: +{ + "id": 1, + "name": "Integration Token", + "token": "or_api_abc123xyz789...", // Only shown once! + "created_at": "2024-01-15T12:00:00Z" +} +``` + +**GET /api/tokens** +```http +Response 200: +{ + "items": [ + { + "id": 1, + "name": "Integration Token", + "created_at": "2024-01-15T12:00:00Z", + "last_used_at": "2024-01-15T14:30:00Z", + "is_active": true + } + ] +} +``` + +**DELETE /api/tokens/{id}** +```http +Response 204: (No content) +``` + +--- + +## 6. Piano Sicurezza + +### 6.1 Cifratura + +| Dato | Algoritmo | Implementazione | +|------|-----------|-----------------| +| **API Keys** | AES-256-GCM | `cryptography.fernet` with custom key | +| **Passwords** | bcrypt | `passlib.hash.bcrypt` (12 rounds) | +| **API Tokens** | SHA-256 | Only hash stored, never plaintext | +| **JWT** | HS256 | `python-jose` with 256-bit secret | + +### 6.2 Implementation Details + +```python +# Encryption Service (app/services/encryption.py) +from cryptography.fernet import Fernet +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC +import base64 +import os + +class EncryptionService: + """ + AES-256-GCM encryption for sensitive data. + """ + + def __init__(self, master_key: str): + self._fernet = self._derive_key(master_key) + + def _derive_key(self, master_key: str) -> Fernet: + """Derive Fernet key from master key using PBKDF2.""" + kdf = PBKDF2HMAC( + algorithm=hashes.SHA256(), + length=32, + salt=os.urandom(16), + iterations=100000, + ) + key = base64.urlsafe_b64encode(kdf.derive(master_key.encode())) + return Fernet(key) + + def encrypt(self, plaintext: str) -> str: + """Encrypt plaintext and return base64-encoded ciphertext.""" + return self._fernet.encrypt(plaintext.encode()).decode() + + def decrypt(self, ciphertext: str) -> str: + """Decrypt ciphertext and return plaintext.""" + return self._fernet.decrypt(ciphertext.encode()).decode() + +# Password Hashing +from passlib.context import CryptContext + +pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") + +def hash_password(password: str) -> str: + return pwd_context.hash(password) + +def verify_password(plain_password: str, hashed_password: str) -> bool: + return pwd_context.verify(plain_password, hashed_password) + +# API Token Generation +import secrets +import hashlib + +def generate_api_token() -> tuple[str, str]: + """ + Generate a new API token. + Returns: (plaintext_token, token_hash) + """ + token = "or_api_" + secrets.token_urlsafe(48) # 64 chars total + token_hash = hashlib.sha256(token.encode()).hexdigest() + return token, token_hash +``` + +### 6.3 Rate Limiting + +```python +# Rate limiting configuration (app/config.py) +from slowapi import Limiter +from slowapi.util import get_remote_address + +limiter = Limiter(key_func=get_remote_address) + +# Limits +AUTH_RATE_LIMIT = "5/minute" # Login/register attempts +API_RATE_LIMIT = "100/hour" # Public API calls per token +WEB_RATE_LIMIT = "30/minute" # Web endpoint calls per IP +``` + +### 6.4 Security Headers + +```python +# FastAPI middleware (app/main.py) +from fastapi.middleware.cors import CORSMiddleware +from fastapi.middleware.trustedhost import TrustedHostMiddleware + +app.add_middleware( + TrustedHostMiddleware, + allowed_hosts=["localhost", "*.example.com"] +) + +@app.middleware("http") +async def security_headers(request, call_next): + response = await call_next(request) + response.headers["X-Content-Type-Options"] = "nosniff" + response.headers["X-Frame-Options"] = "DENY" + response.headers["X-XSS-Protection"] = "1; mode=block" + response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains" + return response +``` + +### 6.5 Input Validation + +```python +# Pydantic schemas with validation (app/schemas/auth.py) +from pydantic import BaseModel, EmailStr, Field, validator +import re + +class UserRegister(BaseModel): + email: EmailStr + password: str = Field(..., min_length=12, max_length=128) + password_confirm: str + + @validator('password') + def password_strength(cls, v): + if not re.search(r'[A-Z]', v): + raise ValueError('Password must contain uppercase letter') + if not re.search(r'[a-z]', v): + raise ValueError('Password must contain lowercase letter') + if not re.search(r'\d', v): + raise ValueError('Password must contain digit') + if not re.search(r'[!@#$%^&*]', v): + raise ValueError('Password must contain special character') + return v + + @validator('password_confirm') + def passwords_match(cls, v, values): + if 'password' in values and v != values['password']: + raise ValueError('Passwords do not match') + return v +``` + +--- + +## 7. Configurazione Ambiente + +### 7.1 Variabili d'Ambiente Richieste + +| Variable | Type | Default | Required | Description | +|----------|------|---------|----------|-------------| +| `DATABASE_URL` | str | `sqlite:///./app.db` | No | SQLite database path | +| `SECRET_KEY` | str | - | **Yes** | JWT signing key (min 32 chars) | +| `ENCRYPTION_KEY` | str | - | **Yes** | AES-256 master key (32 bytes) | +| `OPENROUTER_API_URL` | str | `https://openrouter.ai/api/v1` | No | OpenRouter base URL | +| `SYNC_INTERVAL_MINUTES` | int | 60 | No | Stats sync interval | +| `MAX_API_KEYS_PER_USER` | int | 10 | No | API keys limit per user | +| `RATE_LIMIT_REQUESTS` | int | 100 | No | API requests per window | +| `RATE_LIMIT_WINDOW` | int | 3600 | No | Rate limit window (seconds) | +| `JWT_EXPIRATION_HOURS` | int | 24 | No | JWT token lifetime | +| `DEBUG` | bool | false | No | Enable debug mode | +| `LOG_LEVEL` | str | INFO | No | Logging level | + +### 7.2 File .env.example + +```bash +# =========================================== +# OpenRouter API Key Monitor - Configuration +# =========================================== + +# Database +DATABASE_URL=sqlite:///./data/app.db + +# Security - REQUIRED +# Generate with: openssl rand -hex 32 +SECRET_KEY=your-super-secret-jwt-key-min-32-chars +ENCRYPTION_KEY=your-32-byte-encryption-key-here + +# OpenRouter Integration +OPENROUTER_API_URL=https://openrouter.ai/api/v1 + +# Background Tasks +SYNC_INTERVAL_MINUTES=60 + +# Limits +MAX_API_KEYS_PER_USER=10 +RATE_LIMIT_REQUESTS=100 +RATE_LIMIT_WINDOW=3600 + +# JWT +JWT_EXPIRATION_HOURS=24 + +# Development +DEBUG=false +LOG_LEVEL=INFO +``` + +### 7.3 Pydantic Settings (app/config.py) + +```python +from pydantic_settings import BaseSettings +from functools import lru_cache + +class Settings(BaseSettings): + # Database + database_url: str = "sqlite:///./app.db" + + # Security + secret_key: str + encryption_key: str + jwt_expiration_hours: int = 24 + + # OpenRouter + openrouter_api_url: str = "https://openrouter.ai/api/v1" + + # Task scheduling + sync_interval_minutes: int = 60 + + # Limits + max_api_keys_per_user: int = 10 + rate_limit_requests: int = 100 + rate_limit_window: int = 3600 + + # App settings + debug: bool = False + log_level: str = "INFO" + + class Config: + env_file = ".env" + env_file_encoding = "utf-8" + +@lru_cache() +def get_settings() -> Settings: + return Settings() +``` + +--- + +## 8. Task Scheduler (APScheduler) + +### 8.1 Background Jobs Configuration + +```python +# app/tasks/sync.py +from apscheduler.schedulers.background import BackgroundScheduler +from apscheduler.triggers.interval import IntervalTrigger +from app.services.openrouter import OpenRouterService +from app.database import SessionLocal + +scheduler = BackgroundScheduler() + +def sync_usage_stats(): + """Sync usage statistics from OpenRouter for all active keys.""" + db = SessionLocal() + try: + service = OpenRouterService(db) + service.sync_all_keys() + finally: + db.close() + +def validate_api_keys(): + """Validate all API keys and update status.""" + db = SessionLocal() + try: + service = OpenRouterService(db) + service.validate_all_keys() + finally: + db.close() + +# Schedule jobs +scheduler.add_job( + sync_usage_stats, + trigger=IntervalTrigger(minutes=60), + id="sync_usage", + replace_existing=True +) + +scheduler.add_job( + validate_api_keys, + trigger=IntervalTrigger(hours=24), + id="validate_keys", + replace_existing=True +) + +def start_scheduler(): + scheduler.start() +``` + +--- + +## 9. Testing Strategy + +### 9.1 Test Structure + +``` +tests/ +├── conftest.py # Shared fixtures +├── test_auth.py # Authentication tests +├── test_api_keys.py # API key CRUD tests +├── test_public_api.py # Public API tests +├── test_encryption.py # Encryption service tests +└── test_integration.py # Integration tests +``` + +### 9.2 Key Test Cases + +| Component | Test Case | Expected | +|-----------|-----------|----------| +| Auth | Register with valid data | 201, user created | +| Auth | Register with duplicate email | 400 error | +| Auth | Login with valid credentials | 200, JWT returned | +| Auth | Login with wrong password | 401 error | +| API Keys | Create key with valid data | 201, key encrypted | +| API Keys | Create key without auth | 401 error | +| API Keys | Delete own key | 204 success | +| API Keys | Delete other user's key | 403 forbidden | +| Public API | Access with valid token | 200 + data | +| Public API | Access with invalid token | 401 error | +| Encryption | Encrypt and decrypt | Original value | +| Encryption | Wrong key decryption | Exception raised | + +--- + +## 10. Deployment + +### 10.1 Docker Configuration + +```dockerfile +# Dockerfile +FROM python:3.11-slim + +WORKDIR /app + +# Install dependencies +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Copy application +COPY app/ ./app/ +COPY alembic/ ./alembic/ +COPY alembic.ini . + +# Create data directory +RUN mkdir -p /app/data + +# Environment +ENV PYTHONPATH=/app +ENV DATABASE_URL=sqlite:///./data/app.db + +# Run migrations and start +CMD ["sh", "-c", "alembic upgrade head && uvicorn app.main:app --host 0.0.0.0 --port 8000"] +``` + +```yaml +# docker-compose.yml +version: '3.8' + +services: + app: + build: . + ports: + - "8000:8000" + environment: + - SECRET_KEY=${SECRET_KEY} + - ENCRYPTION_KEY=${ENCRYPTION_KEY} + - DEBUG=false + volumes: + - ./data:/app/data + restart: unless-stopped +``` + +--- + +*Document Version: 1.0* +*Last Updated: 2024-01-15* +*Status: MVP Specification* diff --git a/export/kanban.md b/export/kanban.md new file mode 100644 index 0000000..349caae --- /dev/null +++ b/export/kanban.md @@ -0,0 +1,242 @@ +# Kanban Board + +## OpenRouter API Key Monitor - Fase 1 (MVP) + +--- + +## Legenda + +- **Complessità**: S (Small < 1h) | M (Medium 1-2h) | L (Large 2-4h, deve essere scomposto) +- **Priorità**: P0 (Bloccante) | P1 (Alta) | P2 (Media) | P3 (Bassa) +- **Dipendenze**: Task che devono essere completati prima + +--- + +## 📋 BACKLOG / TODO + +### 🔧 Setup Progetto (Fondamentale) + +| ID | Task | Compl. | Priorità | Dipendenze | Note | +|----|------|--------|----------|------------|------| +| T01 | Creare struttura cartelle progetto | S | P0 | - | `app/`, `tests/`, `alembic/` | +| T02 | Inizializzare virtual environment | S | P0 | - | Python 3.11+ | +| T03 | Creare requirements.txt con dipendenze | S | P0 | T02 | FastAPI, SQLAlchemy, etc. | +| T04 | Setup file configurazione (.env, config.py) | S | P0 | T03 | Variabili d'ambiente | +| T05 | Configurare pytest e struttura test | S | P0 | T02 | pytest.ini, conftest.py | + +### 🗄️ Database & Models + +| ID | Task | Compl. | Priorità | Dipendenze | Note | +|----|------|--------|----------|------------|------| +| T06 | Creare database.py (connection & session) | S | P0 | T04 | SQLAlchemy engine | +| T07 | Creare model User (SQLAlchemy) | M | P0 | T06 | Tabella users | +| T08 | Creare model ApiKey (SQLAlchemy) | M | P0 | T07 | Tabella api_keys | +| T09 | Creare model UsageStats (SQLAlchemy) | M | P1 | T08 | Tabella usage_stats | +| T10 | Creare model ApiToken (SQLAlchemy) | M | P1 | T07 | Tabella api_tokens | +| T11 | Setup Alembic e creare migrazione iniziale | M | P0 | T07-T10 | `alembic init` + revision | + +### 🔐 Servizi di Sicurezza + +| ID | Task | Compl. | Priorità | Dipendenze | Note | +|----|------|--------|----------|------------|------| +| T12 | Implementare EncryptionService (AES-256) | M | P0 | - | cryptography library | +| T13 | Implementare password hashing (bcrypt) | S | P0 | - | passlib | +| T14 | Implementare JWT utilities | S | P0 | T12 | python-jose | +| T15 | Implementare API token generation | S | P1 | T13 | SHA-256 hash | +| T16 | Scrivere test per servizi di encryption | M | P1 | T12-T15 | Unit tests | + +### 👤 Autenticazione Utenti + +| ID | Task | Compl. | Priorità | Dipendenze | Note | +|----|------|--------|----------|------------|------| +| T17 | Creare Pydantic schemas auth (register/login) | S | P0 | T07 | Validazione input | +| T18 | Implementare endpoint POST /api/auth/register | M | P0 | T13, T17 | Creazione utente | +| T19 | Implementare endpoint POST /api/auth/login | M | P0 | T14, T18 | JWT generation | +| T20 | Implementare endpoint POST /api/auth/logout | S | P0 | T19 | Token invalidation | +| T21 | Creare dipendenza get_current_user | S | P0 | T19 | FastAPI dependency | +| T22 | Scrivere test per auth endpoints | M | P0 | T18-T21 | pytest | + +### 🔑 Gestione API Keys + +| ID | Task | Compl. | Priorità | Dipendenze | Note | +|----|------|--------|----------|------------|------| +| T23 | Creare Pydantic schemas per API keys | S | P0 | T08 | CRUD schemas | +| T24 | Implementare POST /api/keys (create) | M | P0 | T12, T21, T23 | Con cifratura | +| T25 | Implementare GET /api/keys (list) | S | P0 | T21, T23 | Lista key utente | +| T26 | Implementare PUT /api/keys/{id} (update) | S | P0 | T21, T24 | Modifica nome/stato | +| T27 | Implementare DELETE /api/keys/{id} | S | P0 | T21 | Eliminazione | +| T28 | Implementare servizio validazione key | M | P1 | T24 | Chiamata a OpenRouter | +| T29 | Scrivere test per API keys CRUD | M | P0 | T24-T27 | pytest | + +### 📊 Dashboard & Statistiche (Base) + +| ID | Task | Compl. | Priorità | Dipendenze | Note | +|----|------|--------|----------|------------|------| +| T30 | Creare Pydantic schemas per stats | S | P1 | T09 | Response models | +| T31 | Implementare servizio aggregazione stats | M | P1 | T09 | Query SQL | +| T32 | Implementare endpoint GET /api/stats | M | P1 | T21, T31 | Stats aggregate | +| T33 | Implementare endpoint GET /api/usage | M | P1 | T21, T31 | Dettaglio usage | +| T34 | Scrivere test per stats endpoints | M | P1 | T32, T33 | pytest | + +### 🌐 Public API v1 (Esterna) + +| ID | Task | Compl. | Priorità | Dipendenze | Note | +|----|------|--------|----------|------------|------| +| T35 | Creare dipendenza verify_api_token | S | P0 | T15 | Bearer token auth | +| T36 | Implementare POST /api/tokens (generate) | M | P0 | T15, T21 | API token management | +| T37 | Implementare GET /api/tokens (list) | S | P0 | T21 | Lista token utente | +| T38 | Implementare DELETE /api/tokens/{id} | S | P0 | T21 | Revoca token | +| T39 | Implementare GET /api/v1/stats | M | P0 | T31, T35 | Public endpoint | +| T40 | Implementare GET /api/v1/usage | M | P0 | T33, T35 | Public endpoint | +| T41 | Implementare GET /api/v1/keys | M | P0 | T25, T35 | Public endpoint | +| T42 | Implementare rate limiting su public API | M | P1 | T35-T41 | slowapi | +| T43 | Scrivere test per public API | M | P1 | T36-T42 | pytest | + +### 🎨 Frontend Web (HTMX) + +| ID | Task | Compl. | Priorità | Dipendenze | Note | +|----|------|--------|----------|------------|------| +| T44 | Setup Jinja2 templates e static files | S | P0 | - | Configurazione FastAPI | +| T45 | Creare base.html (layout principale) | S | P0 | T44 | Template base | +| T46 | Creare login.html | S | P0 | T45 | Form login | +| T47 | Creare register.html | S | P0 | T45 | Form registrazione | +| T48 | Implementare router /login (GET/POST) | M | P0 | T46 | Web endpoint | +| T49 | Implementare router /register (GET/POST) | M | P0 | T47 | Web endpoint | +| T50 | Creare dashboard.html | M | P1 | T45 | Panoramica | +| T51 | Implementare router /dashboard | S | P1 | T50, T21 | Web endpoint | +| T52 | Creare keys.html | M | P1 | T45 | Gestione API keys | +| T53 | Implementare router /keys | S | P1 | T52, T24 | Web endpoint | +| T54 | Aggiungere HTMX per azioni CRUD | M | P2 | T52 | AJAX senza reload | + +### ⚙️ Background Tasks + +| ID | Task | Compl. | Priorità | Dipendenze | Note | +|----|------|--------|----------|------------|------| +| T55 | Configurare APScheduler | S | P2 | - | Setup scheduler | +| T56 | Implementare task sync usage stats | M | P2 | T09, T28 | Ogni ora | +| T57 | Implementare task validazione key | M | P2 | T28 | Ogni giorno | +| T58 | Integrare scheduler in startup app | S | P2 | T55-T57 | Lifespan event | + +### 🔒 Sicurezza & Hardening + +| ID | Task | Compl. | Priorità | Dipendenze | Note | +|----|------|--------|----------|------------|------| +| T59 | Implementare security headers middleware | S | P1 | - | XSS, CSRF protection | +| T60 | Implementare rate limiting auth endpoints | S | P1 | T18, T19 | slowapi | +| T61 | Implementare CORS policy | S | P1 | - | Configurazione | +| T62 | Audit: verificare cifratura API keys | S | P1 | T12 | Verifica sicurezza | +| T63 | Audit: verificare SQL injection prevention | S | P1 | T06 | Parameterized queries | + +### 🧪 Testing & QA + +| ID | Task | Compl. | Priorità | Dipendenze | Note | +|----|------|--------|----------|------------|------| +| T64 | Scrivere test unitari per models | S | P1 | T07-T10 | pytest | +| T65 | Scrivere test integrazione auth flow | M | P1 | T18-T22 | End-to-end | +| T66 | Scrivere test integrazione API keys | M | P1 | T24-T29 | End-to-end | +| T67 | Verificare coverage >= 90% | S | P1 | T64-T66 | pytest-cov | +| T68 | Eseguire security scan dipendenze | S | P2 | - | safety, pip-audit | + +### 📝 Documentazione + +| ID | Task | Compl. | Priorità | Dipendenze | Note | +|----|------|--------|----------|------------|------| +| T69 | Scrivere README.md completo | M | P2 | - | Setup, usage | +| T70 | Documentare API con OpenAPI | S | P2 | - | FastAPI auto-docs | +| T71 | Creare esempi curl per API | S | P3 | T39-T41 | Usage examples | + +### 🚀 Deployment + +| ID | Task | Compl. | Priorità | Dipendenze | Note | +|----|------|--------|----------|------------|------| +| T72 | Creare Dockerfile | M | P2 | - | Containerization | +| T73 | Creare docker-compose.yml | S | P2 | T72 | Stack completo | +| T74 | Scrivere script avvio produzione | S | P2 | T72 | Entry point | + +--- + +## 🚧 IN PROGRESS + +*Task attualmente in lavorazione* + +| ID | Task | Assegnato | Iniziato | Note | +|----|------|-----------|----------|------| +| - | - | - | - | - | + +--- + +## 👀 REVIEW + +*Task completati, in attesa di review* + +| ID | Task | Assegnato | Completato | Reviewer | Note | +|----|------|-----------|------------|----------|------| +| - | - | - | - | - | - | + +--- + +## ✅ DONE + +*Task completati e verificati* + +| ID | Task | Assegnato | Completato | Note | +|----|------|-----------|------------|------| +| - | - | - | - | - | + +--- + +## 📊 Statistiche + +| Stato | Conteggio | Percentuale | +|-------|-----------|-------------| +| TODO | 74 | 100% | +| IN PROGRESS | 0 | 0% | +| REVIEW | 0 | 0% | +| DONE | 0 | 0% | +| **Totale** | **74** | **0%** | + +--- + +## 🎯 Milestone Fase 1 (MVP) + +### Blocker Tasks (Devono essere completati prima) +- T01-T05: Setup progetto +- T06-T11: Database setup +- T12-T16: Servizi sicurezza + +### Core Features MVP +- ✅ Autenticazione utenti (registrazione/login/logout JWT) +- ✅ CRUD API key (cifrate AES-256) +- ✅ Dashboard statistiche base (aggregazione) +- ✅ API pubblica autenticata (sola lettura) + +### Definition of Done (DoD) +- [ ] Tutti i test passano (`pytest`) +- [ ] Coverage >= 90% (`pytest --cov`) +- [ ] Security headers implementati +- [ ] Rate limiting attivo +- [ ] API documentate (OpenAPI) +- [ ] README completo +- [ ] Nessun errore linting (`ruff check`) + +--- + +## 🔗 Dipendenze Chiave + +``` +T01-T05 (Setup) + └── T06-T11 (Database) + ├── T12-T16 (Security) + │ ├── T17-T22 (Auth) + │ ├── T23-T29 (API Keys) + │ │ └── T28 (Validation) + │ │ └── T55-T58 (Background Tasks) + │ └── T30-T34 (Stats) + │ └── T35-T43 (Public API) + └── T44-T54 (Frontend) +``` + +--- + +*Ultimo aggiornamento: 2024-01-15* +*Versione: 1.0* diff --git a/export/progress.md b/export/progress.md new file mode 100644 index 0000000..7c3929b --- /dev/null +++ b/export/progress.md @@ -0,0 +1,196 @@ +# Progress Tracking + +## Feature: Fase 1 - MVP OpenRouter API Key Monitor + +--- + +## 📊 Stato Generale + +| Metrica | Valore | +|---------|--------| +| **Stato** | 🟡 In Progress | +| **Progresso** | 1% | +| **Data Inizio** | 2024-04-07 | +| **Data Target** | TBD | +| **Task Totali** | 74 | +| **Task Completati** | 1 | +| **Task In Progress** | 1 | + +--- + +## 🎯 Obiettivi Fase 1 (MVP) + +### Core Features +1. ✅ **Autenticazione utenti** (registrazione/login JWT) +2. ✅ **CRUD API key** (cifrate AES-256) +3. ✅ **Dashboard statistiche base** (aggregazione dati) +4. ✅ **API pubblica autenticata** (sola lettura) + +### Requisiti Non Funzionali +- [ ] Tempo di risposta web < 2 secondi +- [ ] API response time < 500ms +- [ ] Supporto 100+ utenti concorrenti +- [ ] Test coverage >= 90% +- [ ] Sicurezza: AES-256, bcrypt, JWT, rate limiting + +--- + +## 📋 Task Pianificate + +### 🔧 Setup Progetto (T01-T05) - 1/5 completati +- [x] T01: Creare struttura cartelle progetto (2024-04-07) +- [ ] T02: Inizializzare virtual environment +- [ ] T03: Creare requirements.txt con dipendenze +- [ ] T04: Setup file configurazione (.env, config.py) +- [ ] T05: Configurare pytest e struttura test + +### 🗄️ Database & Models (T06-T11) - 0/6 completati +- [ ] T06: Creare database.py (connection & session) +- [ ] T07: Creare model User (SQLAlchemy) +- [ ] T08: Creare model ApiKey (SQLAlchemy) +- [ ] T09: Creare model UsageStats (SQLAlchemy) +- [ ] T10: Creare model ApiToken (SQLAlchemy) +- [ ] T11: Setup Alembic e creare migrazione iniziale + +### 🔐 Servizi di Sicurezza (T12-T16) - 0/5 completati +- [ ] T12: Implementare EncryptionService (AES-256) +- [ ] T13: Implementare password hashing (bcrypt) +- [ ] T14: Implementare JWT utilities +- [ ] T15: Implementare API token generation +- [ ] T16: Scrivere test per servizi di encryption + +### 👤 Autenticazione Utenti (T17-T22) - 0/6 completati +- [ ] T17: Creare Pydantic schemas auth (register/login) +- [ ] T18: Implementare endpoint POST /api/auth/register +- [ ] T19: Implementare endpoint POST /api/auth/login +- [ ] T20: Implementare endpoint POST /api/auth/logout +- [ ] T21: Creare dipendenza get_current_user +- [ ] T22: Scrivere test per auth endpoints + +### 🔑 Gestione API Keys (T23-T29) - 0/7 completati +- [ ] T23: Creare Pydantic schemas per API keys +- [ ] T24: Implementare POST /api/keys (create) +- [ ] T25: Implementare GET /api/keys (list) +- [ ] T26: Implementare PUT /api/keys/{id} (update) +- [ ] T27: Implementare DELETE /api/keys/{id} +- [ ] T28: Implementare servizio validazione key +- [ ] T29: Scrivere test per API keys CRUD + +### 📊 Dashboard & Statistiche (T30-T34) - 0/5 completati +- [ ] T30: Creare Pydantic schemas per stats +- [ ] T31: Implementare servizio aggregazione stats +- [ ] T32: Implementare endpoint GET /api/stats +- [ ] T33: Implementare endpoint GET /api/usage +- [ ] T34: Scrivere test per stats endpoints + +### 🌐 Public API v1 (T35-T43) - 0/9 completati +- [ ] T35: Creare dipendenza verify_api_token +- [ ] T36: Implementare POST /api/tokens (generate) +- [ ] T37: Implementare GET /api/tokens (list) +- [ ] T38: Implementare DELETE /api/tokens/{id} +- [ ] T39: Implementare GET /api/v1/stats +- [ ] T40: Implementare GET /api/v1/usage +- [ ] T41: Implementare GET /api/v1/keys +- [ ] T42: Implementare rate limiting su public API +- [ ] T43: Scrivere test per public API + +### 🎨 Frontend Web (T44-T54) - 0/11 completati +- [ ] T44: Setup Jinja2 templates e static files +- [ ] T45: Creare base.html (layout principale) +- [ ] T46: Creare login.html +- [ ] T47: Creare register.html +- [ ] T48: Implementare router /login (GET/POST) +- [ ] T49: Implementare router /register (GET/POST) +- [ ] T50: Creare dashboard.html +- [ ] T51: Implementare router /dashboard +- [ ] T52: Creare keys.html +- [ ] T53: Implementare router /keys +- [ ] T54: Aggiungere HTMX per azioni CRUD + +### ⚙️ Background Tasks (T55-T58) - 0/4 completati +- [ ] T55: Configurare APScheduler +- [ ] T56: Implementare task sync usage stats +- [ ] T57: Implementare task validazione key +- [ ] T58: Integrare scheduler in startup app + +### 🔒 Sicurezza & Hardening (T59-T63) - 0/5 completati +- [ ] T59: Implementare security headers middleware +- [ ] T60: Implementare rate limiting auth endpoints +- [ ] T61: Implementare CORS policy +- [ ] T62: Audit: verificare cifratura API keys +- [ ] T63: Audit: verificare SQL injection prevention + +### 🧪 Testing & QA (T64-T68) - 0/5 completati +- [ ] T64: Scrivere test unitari per models +- [ ] T65: Scrivere test integrazione auth flow +- [ ] T66: Scrivere test integrazione API keys +- [ ] T67: Verificare coverage >= 90% +- [ ] T68: Eseguire security scan dipendenze + +### 📝 Documentazione (T69-T71) - 0/3 completati +- [ ] T69: Scrivere README.md completo +- [ ] T70: Documentare API con OpenAPI +- [ ] T71: Creare esempi curl per API + +### 🚀 Deployment (T72-T74) - 0/3 completati +- [ ] T72: Creare Dockerfile +- [ ] T73: Creare docker-compose.yml +- [ ] T74: Scrivere script avvio produzione + +--- + +## 📈 Grafico Progresso + +``` +Progresso MVP Fase 1 + +TODO [████████████████████████████████████████] 100% +IN PROGRESS [ ] 0% +REVIEW [ ] 0% +DONE [ ] 0% + +0% 25% 50% 75% 100% +``` + +--- + +## 🔥 Blockers + +*Nessun blocker attivo* + +| ID | Descrizione | Impatto | Data Apertura | Data Risoluzione | +|----|-------------|---------|---------------|------------------| +| - | - | - | - | - | + +--- + +## 📝 Decisioni Log + +| Data | Decisione | Motivazione | Stato | +|------|-----------|-------------|-------| +| 2024-01-15 | Stack: FastAPI + SQLite + HTMX | MVP semplice, zero-config | ✅ Approvata | +| 2024-01-15 | Cifratura: AES-256-GCM | Requisito sicurezza PRD | ✅ Approvata | +| 2024-01-15 | Auth: JWT con cookie | Semplice per web + API | ✅ Approvata | + +--- + +## 🐛 Issue Tracking + +*Issue riscontrati durante lo sviluppo* + +| ID | Descrizione | Severità | Stato | Assegnato | Note | +|----|-------------|----------|-------|-----------|------| +| - | - | - | - | - | - | + +--- + +## 📚 Risorse + +- PRD: `/home/google/Sources/LucaSacchiNet/openrouter-watcher/prd.md` +- Architettura: `/home/google/Sources/LucaSacchiNet/openrouter-watcher/export/architecture.md` +- Kanban: `/home/google/Sources/LucaSacchiNet/openrouter-watcher/export/kanban.md` + +--- + +*Ultimo aggiornamento: 2024-01-15* +*Prossimo aggiornamento: Alla fine del primo sprint* diff --git a/prd.md b/prd.md new file mode 100644 index 0000000..0cef470 --- /dev/null +++ b/prd.md @@ -0,0 +1,333 @@ +# Product Requirements Document (PRD) + +## OpenRouter API Key Monitor + +--- + +## 1. Panoramica + +### 1.1 Descrizione +OpenRouter API Key Monitor e un applicazione web multi-utente che permette agli utenti di monitorare l utilizzo delle loro API key della piattaforma OpenRouter. L applicazione raccoglie statistiche d uso, le persiste in un database SQLite e fornisce sia un interfaccia web che un API programmatica per l accesso ai dati. + +### 1.2 Obiettivi +- Fornire una dashboard centralizzata per il monitoraggio delle API key OpenRouter +- Permettere a piu utenti di gestire le proprie chiavi in modo indipendente +- Offrire API programmatica per integrazioni esterne +- Persistere i dati storici per analisi nel tempo + +### 1.3 Target Utenti +- Sviluppatori che utilizzano API OpenRouter +- Team che gestiscono multiple API key +- Utenti che necessitano di reportistica sull utilizzo + +--- + +## 2. Requisiti Funzionali + +### 2.1 Gestione Utenti (Multi-utente) + +#### 2.1.1 Registrazione +- **F-001**: Gli utenti devono potersi registrare con email e password +- **F-002**: La password deve essere salvata in modo sicuro (hash) +- **F-003**: Email deve essere univoca nel sistema +- **F-004**: Validazione formato email + +#### 2.1.2 Autenticazione +- **F-005**: Login con email e password +- **F-006**: Gestione sessione utente (JWT o session-based) +- **F-007**: Logout funzionante +- **F-008**: Protezione route autenticate + +#### 2.1.3 Profilo Utente +- **F-009**: Visualizzazione profilo personale +- **F-010**: Modifica password +- **F-011**: Eliminazione account con conferma + +### 2.2 Gestione API Key + +#### 2.2.1 CRUD API Key +- **F-012**: Aggiungere nuova API key OpenRouter +- **F-013**: Visualizzare lista API key dell utente +- **F-014**: Modificare nome/descrizione API key +- **F-015**: Eliminare API key +- **F-016**: API key devono essere cifrate nel database + +#### 2.2.2 Validazione +- **F-017**: Verifica validita API key con chiamata test a OpenRouter +- **F-018**: Visualizzare stato attivo/inattivo per ogni key + +### 2.3 Monitoraggio e Statistiche + +#### 2.3.1 Raccolta Dati +- **F-019**: Sincronizzazione automatica statistiche da OpenRouter API +- **F-020**: Storico utilizzo (richieste, token, costi) +- **F-021**: Aggregazione dati per modello LLM utilizzato + +#### 2.3.2 Dashboard +- **F-022**: Vista panoramica utilizzo totale +- **F-023**: Grafico utilizzo nel tempo (ultimi 30 giorni) +- **F-024**: Distribuzione utilizzo per modello +- **F-025**: Costi totali e medi +- **F-026**: Numero richieste totali e giornaliere medie + +#### 2.3.3 Report Dettagliati +- **F-027**: Filtraggio per intervallo date +- **F-028**: Filtraggio per API key specifica +- **F-029**: Filtraggio per modello +- **F-030**: Esportazione dati (CSV/JSON) + +### 2.4 API Pubblica + +#### 2.4.1 Autenticazione API +- **F-031**: Generazione API token per accesso programmatico +- **F-032**: Revoca API token +- **F-033**: Autenticazione via Bearer token + +#### 2.4.2 Endpoint +- **F-034**: GET /api/v1/stats - statistiche aggregate (solo lettura) +- **F-035**: GET /api/v1/usage - dati di utilizzo dettagliati (solo lettura) +- **F-036**: GET /api/v1/keys - lista API key con statistiche (solo lettura) +- **F-037**: Rate limiting su API pubblica + +#### 2.4.3 Risposte +- **F-038**: Formato JSON standardizzato +- **F-039**: Gestione errori con codici HTTP appropriati +- **F-040**: Paginazione per risultati grandi + +--- + +## 3. Requisiti Non Funzionali + +### 3.1 Performance +- **NF-001**: Tempo di risposta web < 2 secondi +- **NF-002**: API response time < 500ms +- **NF-003**: Supporto per almeno 100 utenti concorrenti + +### 3.2 Sicurezza +- **NF-004**: Tutte le API key cifrate in database (AES-256) +- **NF-005**: Password hash con bcrypt/Argon2 +- **NF-006**: HTTPS obbligatorio in produzione +- **NF-007**: Protezione CSRF +- **NF-008**: Rate limiting su endpoint di autenticazione +- **NF-009**: SQL injection prevention (query parameterizzate) +- **NF-010**: XSS prevention + +### 3.3 Affidabilita +- **NF-011**: Backup automatico database SQLite +- **NF-012**: Gestione errori graceful degradation +- **NF-013**: Logging operazioni critiche + +### 3.4 Usabilita +- **NF-014**: Interfaccia responsive (mobile-friendly) +- **NF-015**: Tema chiaro/scuro +- **NF-016**: Messaggi di errore chiari + +### 3.5 Manutenibilita +- **NF-017**: Codice documentato +- **NF-018**: Test coverage >= 90% +- **NF-019**: Struttura modulare + +--- + +## 4. Architettura Tecnica + +### 4.1 Stack Tecnologico +- **Backend**: Python 3.11+ con FastAPI +- **Frontend**: HTML + HTMX / React (opzionale) +- **Database**: SQLite +- **ORM**: SQLAlchemy +- **Autenticazione**: JWT +- **Task Background**: APScheduler / Celery (opzionale) + +### 4.2 Struttura Database + +#### Tabella: users +- id (PK, INTEGER) +- email (UNIQUE, TEXT) +- password_hash (TEXT) +- created_at (TIMESTAMP) +- updated_at (TIMESTAMP) +- is_active (BOOLEAN) + +#### Tabella: api_keys +- id (PK, INTEGER) +- user_id (FK, INTEGER) +- name (TEXT) +- key_encrypted (TEXT) +- is_active (BOOLEAN) +- created_at (TIMESTAMP) +- last_used_at (TIMESTAMP) + +#### Tabella: usage_stats +- id (PK, INTEGER) +- api_key_id (FK, INTEGER) +- date (DATE) +- model (TEXT) +- requests_count (INTEGER) +- tokens_input (INTEGER) +- tokens_output (INTEGER) +- cost (DECIMAL) +- created_at (TIMESTAMP) + +#### Tabella: api_tokens +- id (PK, INTEGER) +- user_id (FK, INTEGER) +- token_hash (TEXT) +- name (TEXT) +- last_used_at (TIMESTAMP) +- created_at (TIMESTAMP) +- is_active (BOOLEAN) + +### 4.3 Integrazione OpenRouter +- API Endpoint: https://openrouter.ai/api/v1/ +- Endpoint utilizzati: + - /auth/key - per validazione key + - /credits - per controllo crediti + - (future estensioni per usage stats quando disponibili) + +--- + +## 5. Interfaccia Utente + +### 5.1 Pagine Web + +#### 5.1.1 Pubbliche +- **Login** (/login) - Form di accesso +- **Registrazione** (/register) - Form di registrazione + +#### 5.1.2 Autenticate +- **Dashboard** (/dashboard) - Panoramica utilizzo +- **API Keys** (/keys) - Gestione API key +- **Statistiche** (/stats) - Report dettagliati +- **Profilo** (/profile) - Gestione account +- **API Tokens** (/tokens) - Gestione token API + +### 5.2 Componenti UI + +#### 5.2.1 Dashboard +- Card riepilogative (richieste totali, costi, etc.) +- Grafici utilizzo temporale +- Tabella modelli piu utilizzati + +#### 5.2.2 Gestione API Key +- Tabella con nome, stato, ultimo utilizzo +- Form aggiunta/modifica +- Bottone test validita +- Bottone eliminazione con conferma + +#### 5.2.3 Statistiche +- Filtri per data, key, modello +- Tabella dettagliata +- Bottone esportazione + +--- + +## 6. API Endpoints (Dettaglio) + +### 6.1 Web Routes (HTML) +- GET / - redirect a /dashboard o /login +- GET/POST /login +- GET/POST /register +- GET /logout +- GET /dashboard (protetta) +- GET /keys (protetta) +- GET /stats (protetta) +- GET /profile (protetta) +- GET /tokens (protetta) + +### 6.2 API Routes (JSON) +- POST /api/auth/login +- POST /api/auth/register +- POST /api/auth/logout + +- GET /api/v1/stats (auth: Bearer token) +- GET /api/v1/usage (auth: Bearer token) +- GET /api/v1/keys (auth: Bearer token) + +--- + +## 7. Cron e Background Tasks + +### 7.1 Sincronizzazione Dati +- **Task**: Sync Usage Data +- **Frequenza**: Ogni ora +- **Azione**: Recupera statistiche da OpenRouter per ogni key attiva +- **Persistenza**: Salva in usage_stats + +### 7.2 Validazione API Key +- **Task**: Validate Keys +- **Frequenza**: Giornaliera +- **Azione**: Verifica validita di ogni key, aggiorna stato + +### 7.3 Cleanup +- **Task**: Cleanup Old Data +- **Frequenza**: Settimanale +- **Azione**: Rimuove dati piu vecchi di 1 anno (configurabile) + +--- + +## 8. Configurazione + +### 8.1 Variabili d Ambiente +- DATABASE_URL - path database SQLite +- SECRET_KEY - chiave per JWT +- ENCRYPTION_KEY - chiave per cifratura API key +- OPENROUTER_API_URL - URL base API OpenRouter +- SYNC_INTERVAL_MINUTES - intervallo sincronizzazione +- MAX_API_KEYS_PER_USER - limite key per utente +- RATE_LIMIT_REQUESTS - limite richieste API +- RATE_LIMIT_WINDOW - finestra rate limit (secondi) + +### 8.2 File Configurazione +- config.yaml (opzionale) - override env vars + +--- + +## 9. Deployment + +### 9.1 Requisiti +- Python 3.11+ +- SQLite +- (Opzionale) Reverse proxy (nginx/traefik) + +### 9.2 Installazione +1. Clone repository +2. pip install -r requirements.txt +3. Configura variabili d ambiente +4. Esegui migrazioni: alembic upgrade head +5. Avvia: uvicorn main:app + +### 9.3 Docker (Opzionale) +- Dockerfile fornito +- docker-compose.yml per stack completo + +--- + +## 10. Roadmap + +### Fase 1 (MVP) +- [ ] Autenticazione utenti +- [ ] CRUD API key +- [ ] Dashboard base +- [ ] API lettura dati + +### Fase 2 +- [ ] Grafici avanzati +- [ ] Esportazione dati +- [ ] Notifiche (email) +- [ ] Rate limiting avanzato + +### Fase 3 +- [ ] Supporto multi-team +- [ ] RBAC (Ruoli) +- [ ] Webhook +- [ ] Mobile app + +--- + +## 11. Note + +- L applicazione e progettata per essere self-hosted +- I dati rimangono locali (SQLite) +- L integrazione con OpenRouter richiede API key valide +- Le API key degli utenti sono sempre cifrate nel database diff --git a/prompt/prompt-zero.md b/prompt/prompt-zero.md new file mode 100644 index 0000000..2103297 --- /dev/null +++ b/prompt/prompt-zero.md @@ -0,0 +1,226 @@ +# Prompt Zero: OpenRouter API Key Monitor - Project Kickoff + +## 🎯 Missione + +Sviluppare **OpenRouter API Key Monitor**, un'applicazione web multi-utente per monitorare l'utilizzo delle API key della piattaforma OpenRouter. + +**Repository:** `/home/google/Sources/LucaSacchiNet/openrouter-watcher` +**PRD:** `/home/google/Sources/LucaSacchiNet/openrouter-watcher/prd.md` + +--- + +## 📊 Stato Attuale + +- ✅ **PRD Completo**: Requisiti funzionali e non funzionali definiti +- ✅ **Team Configurato**: 3 agenti specializzati pronti +- ❌ **Nessun Codice**: Progetto da zero +- ❌ **Nessuna Specifica Tecnica**: Da creare + +--- + +## 👥 Team di Sviluppo + +| Agente | Ruolo | File Config | +|--------|-------|-------------| +| `@spec-architect` | Definisce specifiche e architettura | `.opencode/agents/spec-architect.md` | +| `@tdd-developer` | Implementazione TDD | `.opencode/agents/tdd-developer.md` | +| `@git-manager` | Gestione commit Git | `.opencode/agents/git-manager.md` | + +--- + +## 🔄 Workflow Obbligatorio + +``` +┌─────────────────────────────────────────────────────────────┐ +│ FASE 1: SPECIFICA │ +│ @spec-architect │ +│ └── Legge PRD → Crea architecture.md, kanban.md │ +│ │ +│ ↓ │ +│ │ +│ FASE 2: IMPLEMENTAZIONE │ +│ @tdd-developer │ +│ └── RED → GREEN → REFACTOR per ogni task │ +│ │ +│ ↓ │ +│ │ +│ FASE 3: COMMIT │ +│ @git-manager │ +│ └── Commit atomico + Conventional Commits │ +└─────────────────────────────────────────────────────────────┘ +``` + +--- + +## 🚀 Task Iniziale: Fase 1 - Specifica + +**AGENTE:** `@spec-architect` + +**OBIETTIVO:** Analizzare il PRD e creare le specifiche tecniche dettagliate. + +### Azioni Richieste + +1. **Leggere** `/home/google/Sources/LucaSacchiNet/openrouter-watcher/prd.md` + +2. **Creare** la struttura di output: + ``` + /home/google/Sources/LucaSacchiNet/openrouter-watcher/export/ + ├── prd.md # Requisiti prodotti (estratto/dettaglio) + ├── architecture.md # Architettura sistema + ├── kanban.md # Task breakdown + └── progress.md # Tracciamento progresso + ``` + +3. **Produrre** `architecture.md` con: + - Stack tecnologico dettagliato (Python 3.11+, FastAPI, SQLite, SQLAlchemy, JWT) + - Struttura cartelle progetto + - Diagrammi flusso dati + - Schema database completo (DDL) + - Interfacce API (OpenAPI specs) + - Sicurezza (cifratura, autenticazione) + +4. **Produrre** `kanban.md` con: + - Task breakdown per Fase 1 (MVP) + - Stima complessità + - Dipendenze tra task + - Regola "little often": task < 2 ore + +5. **Inizializzare** `progress.md` con: + - Feature corrente: "Fase 1 - MVP" + - Stato: "🔴 Pianificazione" + - Percentuale: 0% + +### Criteri di Accettazione + +- [ ] Architecture.md completo con tutte le sezioni +- [ ] Kanban.md con task pronti per @tdd-developer +- [ ] Progress.md inizializzato +- [ ] Tutti i path usano `/home/google/Sources/LucaSacchiNet/openrouter-watcher/` + +--- + +## 📋 Requisiti Chiave (Dal PRD) + +### Funzionalità MVP (Fase 1) + +1. **Autenticazione Utenti** + - Registrazione/login multi-utente + - JWT-based authentication + - Password hash (bcrypt) + +2. **Gestione API Key** + - CRUD API key OpenRouter + - Cifratura AES-256 in database + - Validazione key con OpenRouter API + +3. **Dashboard** + - Statistiche utilizzo + - Grafici temporali + - Costi e richieste + +4. **API Pubblica** + - Endpoint autenticati (Bearer token) + - Solo lettura dati + - Rate limiting + +### Stack Tecnologico + +- **Backend:** Python 3.11+, FastAPI +- **Database:** SQLite + SQLAlchemy +- **Frontend:** HTML + HTMX (semplice) +- **Auth:** JWT + bcrypt +- **Task Background:** APScheduler + +--- + +## 🛡️ Vincoli e Best Practices + +### Sicurezza (Critico) +- API key sempre cifrate (AES-256) +- Password hash con bcrypt +- SQL injection prevention +- XSS prevention +- CSRF protection +- Rate limiting + +### Qualità +- Test coverage ≥ 90% +- TDD obbligatorio +- Conventional commits +- Commit atomici + +### Organizzazione +- Task "little often" (< 2 ore) +- Documentazione in `/export/` +- Bug complessi in `/docs/bug_ledger.md` + +--- + +## 📁 Struttura Progetto Attesa + +``` +/home/google/Sources/LucaSacchiNet/openrouter-watcher/ +├── prd.md # Questo PRD +├── prompt/ +│ └── prompt-zero.md # Questo file +├── .opencode/ +│ ├── agents/ # Configurazioni agenti +│ └── skills/ # Skill condivise +├── export/ # Output spec-driven (da creare) +│ ├── prd.md +│ ├── architecture.md +│ ├── kanban.md +│ └── progress.md +├── docs/ # Documentazione (da creare) +│ ├── bug_ledger.md +│ └── architecture.md +├── src/ # Codice sorgente (da creare) +│ └── openrouter_monitor/ +│ ├── __init__.py +│ ├── main.py +│ ├── config.py +│ ├── database.py +│ ├── models/ +│ ├── routers/ +│ ├── services/ +│ └── utils/ +├── tests/ # Test suite (da creare) +│ ├── unit/ +│ ├── integration/ +│ └── conftest.py +├── requirements.txt +└── README.md +``` + +--- + +## ✅ Checklist Pre-Sviluppo + +- [ ] @spec-architect ha letto questo prompt +- [ ] Cartella `export/` creata +- [ ] `architecture.md` creato con schema DB +- [ ] `kanban.md` creato con task Fase 1 +- [ ] `progress.md` inizializzato + +--- + +## 🎬 Prossima Azione + +**@spec-architect**: Inizia analizzando il PRD in `prd.md` e crea le specifiche tecniche in `export/`. + +**NON iniziare l'implementazione** finché le specifiche non sono approvate. + +--- + +## 📞 Note per il Team + +- **Domande sul PRD?** Leggi prima `prd.md` completamente +- **Ambiguità?** Chiedi prima di procedere +- **Vincoli tecnici?** Documentali in `architecture.md` +- **Task troppo grandi?** Spezza in task più piccoli + +--- + +**Data Creazione:** 2025-04-07 +**Versione:** 1.0 +**Stato:** Pronto per kickoff diff --git a/src/openrouter_monitor/__init__.py b/src/openrouter_monitor/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/openrouter_monitor/config.py b/src/openrouter_monitor/config.py new file mode 100644 index 0000000..e69de29 diff --git a/src/openrouter_monitor/database.py b/src/openrouter_monitor/database.py new file mode 100644 index 0000000..e69de29 diff --git a/src/openrouter_monitor/main.py b/src/openrouter_monitor/main.py new file mode 100644 index 0000000..e69de29 diff --git a/src/openrouter_monitor/models/__init__.py b/src/openrouter_monitor/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/openrouter_monitor/routers/__init__.py b/src/openrouter_monitor/routers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/openrouter_monitor/services/__init__.py b/src/openrouter_monitor/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/openrouter_monitor/utils/__init__.py b/src/openrouter_monitor/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/__pycache__/conftest.cpython-313-pytest-9.0.2.pyc b/tests/__pycache__/conftest.cpython-313-pytest-9.0.2.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4199cbddc1f3925e1f9d21739072e7eeb6ae19d0 GIT binary patch literal 168 zcmey&%ge<81lzV=$pq1lK?DpiLK&Y~fQ+dO=?t2Tek&P@n1H;`AgNpK`WgATsru>p z`RO^S`oa07Maikf`aY$}iNT4<$r+h`sU`aP1*v&O`K2YPMY`pQCCM47MfxSF#U;i1 p$@zI{AeLT1p z`RO^S`oa07Maikf`aY$}iNT4<$r+h`sU`aP1*v&O`K2YPMY`pQCCM47MfxSF#U;i1 vrFoep`tk9ZKq@|7ub}c4hfQvNN@-52T@fqLaFFH2AjU^#Mn=XWW*`dyc(^U3 literal 0 HcmV?d00001 diff --git a/tests/unit/__pycache__/test_project_structure.cpython-313-pytest-9.0.2.pyc b/tests/unit/__pycache__/test_project_structure.cpython-313-pytest-9.0.2.pyc new file mode 100644 index 0000000000000000000000000000000000000000..731fa7accf601fe6387765dc5aeb8e965d27f361 GIT binary patch literal 26357 zcmeGlOKcm*bt&;PqCT{2*>Nn}VO%5;Wt+16iIdn)e|F;7ilT5OMYR%dX^NuPrnu@X zRYfcmv}n;PK!G5KATm(YIm(xUmlito;7frVdZ0kbLbnClqP_H%#wZH-)c0m~hD&m3 zN`ji$2^Y(^^Lz8(H*epYdA#4=9#r7^{%=?HV3(r&13lvM*^c|~g5x7aRj8tR)TYZG z+QgpCm%L_ESh?h*{xFXT&;W}el)O!1J4HsNXAH`yy!I-G6t#JuqIyp?@wiiV655(- ziuiKR4JXZPc+#NZX=>a^CbD5OOJ@?<8JY~6$?VK@xM%oSeo-l$n4>*! zJHU|I0dR*J0@$hUpgU8Yk)2i-R(OclRTWkJmmjK-d!DsLv|~c2&>Vx#Ia;Kf;eW4yyMkq*8t3Y(^nMtgQkKyoVGq zx@W*Q@l;t!9d7AMZ4)Jln#T8)Vei-&_8T6kW2tqnSR+lMthy;n?e}l0<=tgzJZxUT#Y9Z*Y&|<){)cE*?2Z_JxTlA3wRJbFJH0;q40!}G{YGq8)k() zNCaPwn`V+`!_V|g&z+8#!5`U<@HrNAQUrm+4mvJ^z+nd+i3`aUN>ga6B2y33&q7SZZ+XpRdXlzc#agj2UUt9J zbHeF)o_lyrUoi1a3$x3*vbBN5)|-7ZFkkvuN7&R;ldC{UIb4ffLX3k zU@H{5A3(%UfsIpW1i?WBz-uWqjNoYi4#G?if!{$vSuL=v4N}m`^dZdNrl4J!g%(Lc z2R?Z3{r3uFVBx(*y%_2N*S%2BIvId_=|Vn|?_PeR(2Kr`8)>E;wn7GkZw)d63W^a$ z%u}Y}j#V*FH=LCFcIc-1yqZ&T-0ow)(aIlud&YMrKk^xlp0eZKXo87*{&f%A;#h zq7%^M4N;=qj1q~=BrWQ#O^YhiB7Y{R(VomgtK^7=I7c)D9MR2UsJB3d?uB~S$q?L2 z$^7N~xx1ml5%g8u$OGA7D`ZIc*2oZYL>N&7CvZe%c&lV+@umy|JmCYpf+$wTLtuj= z!q}wkRy89rbZL-Z2Vf>OuEpc?fr&y6y!>g0u$G2QKQo^%{D}ho?z5Sl%#<~ z&X#i>sZEcnQzHi^DALaKLO@cai<}}|1d5bmQm%_k%5`y_TwHo4A6S}O4i$RPS8*c^ zvcp!$Md4c`7m*@iL=l`ok;?E^$;HK#3DoYK451~j|&{u6l8xj+GE&gIN zI0AYtp?%6Ul{5~n+LRM9sXZuVVVrkLRE!ZJd$P4DC`n*fOln6&2zO(~V+%IlQc=1S2@*e=?;LrOI^lO5H;8j(7Z@p>s8+K5fH9K;@u zyj@#fLz_~sMow)?z)^;4|CID0DzUXG{&5u8u*-ps@|%ha=8s>DiFkrGr3JJp&7e)W zQ0n^{vl1mi6&IV8lDpmXtPGAcfsW5PD_g(jv$Ad5XQdpS9((NAI$Lj6N-TK)+8p!h z3!jy#mPmU}$Zn^MiDcTW*6D?3_3ZVqL%L`K+pg*}}q6J+{v)0NJ zI@e$(z`(v|lF~cvEFqV;dT$tdhT^BtMuo2QFeHuyxa9S$$kcUN$LevtSeVOS_p^1~ zu3(iCtH1W*UIXPDP=Kj}dLQ&ydv3xvN7tT@U;Xgaj|V;+_@sMjs@QebAr%?sx~*Z* zZH*U0kpdaM7mBQtVYrvBEVt(8@4itujJ}E+*Lgc^g$xVd8X3k_A0vw31l?8{-YOYh z9G78$Cwzcc5XH)PNG+L@$5w#cU{2Upj}lpPU7|}%oLB%QQA8D93EP#-Rbw}p6QBY1 z<=m{%U{0`8&cN?0H7gs;35=+dWoFFDaa#z$Hy&+_JK0`ciCr?q3Fpd|wAm?Y^Ck2g z;0ng#JD5*Ry>1m_aey%vqKGQa;&^S&q81M7a|CPg#MDBYleO5tG+*pG=U^>HIcqTr zti{_*#5u}DoTKYxbm_+O{^gFlM&SthDsE&Z?64IwDtv2X6j=+5D1sAMi!!`bGP?M- z3_ttaeyD=rFZLP>?N0vUK)$`$^|FJ%7~}lK81NVG6hns#Wb9t(@H&CbvtvuS z<)h0_eHtnpMPJ2@9EBaWLdJw|jf^3Gfe}S;0)J74w@Stq-;rT}Cwzcc5XH)PNZsHs z8vI3ly4Da~2=>G6)L27w!F$K$>{MyJbX{eu=apP-A1#4eLv#T{TqU{?XQyVe`Ucf1 zv2A58Sh=-1iy({NAX{+>8n_9zqMNZ5ynrg6;#h5-!X4uUC|ocUyY+>jlcCtR^iHws zjDw+wafTuW48;|;OFhPRsmInyZ0S-ymrvjAEA*nT;zkz24qG8H;aejyWGFDA2u@%q z%J5c6Z1IW=13cjaJhn@{GWBE2P_)Asuw$zWaGc?b*r-usqeru3NSaIJ(M_dZ1799f z&Uhb=Z;ZHZ6q$5>TpP23Aa8J^h>vX)@jEt(1ei*mY)UbW$8Nv)rGq%ibk*RLwolYe zDYr_fEt_h2cm0-c!$y&oy7dnT4VN0UH;Rx=Y!o2^TRdUuXoZa;KG-Od+VZTFn+mot zD{-SpV^)@C<>SfFe3`S-W&2XbUuQ1Qx*0aew5aXIokPl4ub}kC?||mEig(5Mql4z& zEu#nSoAWvZrW*R1)7r1bDkEokT`JuoNB;4ApM#?Qbz3Umj=Cw^M4sx7Xpk9D>*uX4sU47 zqr?Kye`noUD&O%hfxp`RmF%T$TFG{bx3000ZE99Os^3d;me+kBN-Npp`96QuS27ox zVW6Zy9*EfVO15jnTd|ULea*So-J5#nHzFyJqqC`%Ok%-Gw!7||E8p?$U&&Ifkv+MH zt;9xwYncXH+E?~gt#el;OaO7RRBMf)lg^*yu`0NM_F5p0Id^&eyK zID;n{eBRpO=x^3-xn~_APNO#!9mmfv;=H!n@i;S%1n+mY6|sY#aTvl{youl~1Y-zX zj5|2(gT26Qmf2>+`pbML%6zA|k6RM!KW$>;?IROqCnxw81U^+tx&k?Ut%qzusEPDV zD9a4OQ9(F8u{&0&F&Hg&odk_R_kr7|Z=Jq<_SV^@VE*-D_jd|p?_c)ryB)X{xZQTE z?UO6{1I4}HTxkENFd_Dss1}B}24n~{Am1y7o@1xdpW_F^mtM;2`EfXv9w|Fo&LdIh zQTJ3jXh6zP@l^WvWEh@Gw|(qXx?QY{htyI7qVuDw`me6tPTfish<-bLD-DnQ6oAF_ z(u>7|=fSn`Ug6;RV(0=m?}aX`6EF&-7hb#09ZrbV$4IzWh%S68G zjuu1F0=arG6kR7*;m-FK&b?kZJNT(lIEMa;8(V9Kt&pq2w??jF+cBaDP8hl}yj5}) z3X*|5d3z*BWMaag4%9q@t{t^!ykdy3_VcsXhaQ1qt7&^jEVIAQIq4K``R9=HqSkbbh zR6G(JTlW+eG`$QJPhr74E8z(rJB4KzE8`*XA!n}}mS{nYEKh~ITbU^dv&2U@uzw9p zw6M zpN}8o5V@839NYTCLymFO!g(EvzHi&zznixwQ1T9X5wiyp%1>0rcLV9%%=5BGGjnMK+}NGX&ObN8}tZ*V+c+ncmV;* zljwI5yo%s5f~yFwAxI&(3BcN|fn9r$jwZBtHcRzu@Mj4oSMVth9gTSDMH#x_{EW h?&)0dbp99q;KlQn=QH?g6nNZkR+@JHn+LHb{STjDptJx0 literal 0 HcmV?d00001 diff --git a/tests/unit/test_project_structure.py b/tests/unit/test_project_structure.py new file mode 100644 index 0000000..ba724f1 --- /dev/null +++ b/tests/unit/test_project_structure.py @@ -0,0 +1,90 @@ +"""Test for project structure setup (T01).""" +import os +import pytest + + +@pytest.mark.unit +class TestProjectStructure: + """Test project directory structure is correctly created.""" + + def test_src_directory_exists(self): + """Verify src/openrouter_monitor/ directory exists.""" + src_path = "/home/google/Sources/LucaSacchiNet/openrouter-watcher/src/openrouter_monitor" + assert os.path.isdir(src_path), f"Directory {src_path} does not exist" + + def test_src_init_file_exists(self): + """Verify src/openrouter_monitor/__init__.py exists.""" + init_path = "/home/google/Sources/LucaSacchiNet/openrouter-watcher/src/openrouter_monitor/__init__.py" + assert os.path.isfile(init_path), f"File {init_path} does not exist" + + def test_main_py_exists(self): + """Verify main.py exists in src.""" + main_path = "/home/google/Sources/LucaSacchiNet/openrouter-watcher/src/openrouter_monitor/main.py" + assert os.path.isfile(main_path), f"File {main_path} does not exist" + + def test_config_py_exists(self): + """Verify config.py exists in src.""" + config_path = "/home/google/Sources/LucaSacchiNet/openrouter-watcher/src/openrouter_monitor/config.py" + assert os.path.isfile(config_path), f"File {config_path} does not exist" + + def test_database_py_exists(self): + """Verify database.py exists in src.""" + db_path = "/home/google/Sources/LucaSacchiNet/openrouter-watcher/src/openrouter_monitor/database.py" + assert os.path.isfile(db_path), f"File {db_path} does not exist" + + def test_models_directory_exists(self): + """Verify models/ directory exists with __init__.py.""" + models_path = "/home/google/Sources/LucaSacchiNet/openrouter-watcher/src/openrouter_monitor/models" + init_path = os.path.join(models_path, "__init__.py") + assert os.path.isdir(models_path), f"Directory {models_path} does not exist" + assert os.path.isfile(init_path), f"File {init_path} does not exist" + + def test_routers_directory_exists(self): + """Verify routers/ directory exists with __init__.py.""" + routers_path = "/home/google/Sources/LucaSacchiNet/openrouter-watcher/src/openrouter_monitor/routers" + init_path = os.path.join(routers_path, "__init__.py") + assert os.path.isdir(routers_path), f"Directory {routers_path} does not exist" + assert os.path.isfile(init_path), f"File {init_path} does not exist" + + def test_services_directory_exists(self): + """Verify services/ directory exists with __init__.py.""" + services_path = "/home/google/Sources/LucaSacchiNet/openrouter-watcher/src/openrouter_monitor/services" + init_path = os.path.join(services_path, "__init__.py") + assert os.path.isdir(services_path), f"Directory {services_path} does not exist" + assert os.path.isfile(init_path), f"File {init_path} does not exist" + + def test_utils_directory_exists(self): + """Verify utils/ directory exists with __init__.py.""" + utils_path = "/home/google/Sources/LucaSacchiNet/openrouter-watcher/src/openrouter_monitor/utils" + init_path = os.path.join(utils_path, "__init__.py") + assert os.path.isdir(utils_path), f"Directory {utils_path} does not exist" + assert os.path.isfile(init_path), f"File {init_path} does not exist" + + def test_tests_directory_structure(self): + """Verify tests/ directory structure exists.""" + tests_path = "/home/google/Sources/LucaSacchiNet/openrouter-watcher/tests" + unit_path = os.path.join(tests_path, "unit") + integration_path = os.path.join(tests_path, "integration") + conftest_path = os.path.join(tests_path, "conftest.py") + + assert os.path.isdir(tests_path), f"Directory {tests_path} does not exist" + assert os.path.isdir(unit_path), f"Directory {unit_path} does not exist" + assert os.path.isfile(os.path.join(unit_path, "__init__.py")), f"unit/__init__.py does not exist" + assert os.path.isdir(integration_path), f"Directory {integration_path} does not exist" + assert os.path.isfile(os.path.join(integration_path, "__init__.py")), f"integration/__init__.py does not exist" + assert os.path.isfile(conftest_path), f"File {conftest_path} does not exist" + + def test_alembic_directory_exists(self): + """Verify alembic/ directory exists.""" + alembic_path = "/home/google/Sources/LucaSacchiNet/openrouter-watcher/alembic" + assert os.path.isdir(alembic_path), f"Directory {alembic_path} does not exist" + + def test_docs_directory_exists(self): + """Verify docs/ directory exists.""" + docs_path = "/home/google/Sources/LucaSacchiNet/openrouter-watcher/docs" + assert os.path.isdir(docs_path), f"Directory {docs_path} does not exist" + + def test_scripts_directory_exists(self): + """Verify scripts/ directory exists.""" + scripts_path = "/home/google/Sources/LucaSacchiNet/openrouter-watcher/scripts" + assert os.path.isdir(scripts_path), f"Directory {scripts_path} does not exist"