Commit initial

This commit is contained in:
2026-04-15 17:57:12 +02:00
parent 005d8e797e
commit 55516ee10f
269 changed files with 26854 additions and 0 deletions
@@ -0,0 +1,187 @@
---
phase: 01-foundation
plan: 02
type: execute
wave: 2
depends_on: ["01-01"]
files_modified:
- imptune/db/__init__.py
- imptune/db/database.py
- imptune/db/models.py
- imptune/storage/__init__.py
- imptune/storage/driver_store.py
- imptune/main.py
- tests/test_db.py
autonomous: true
requirements:
- INFRA-01
- INFRA-02
must_haves:
truths:
- "SQLite database initializes automatically on first run with all tables (Client, Driver, Printer, Icon)"
- "Database uses WAL journal mode and has foreign keys enabled"
- "Database file is created inside the DATA_DIR volume path, not inside the container filesystem"
- "Schema creation is idempotent — repeated startups do not fail or duplicate tables"
artifacts:
- path: "imptune/db/database.py"
provides: "Peewee SqliteDatabase instance with WAL mode and init_db function"
exports: ["db", "init_db"]
- path: "imptune/db/models.py"
provides: "All ORM models for phases 1-5 (BaseModel, Client, Driver, Printer, Icon)"
exports: ["BaseModel", "Client", "Driver", "Printer", "Icon"]
- path: "imptune/storage/driver_store.py"
provides: "SHA256 content-addressed file storage abstraction for driver packages"
exports: ["DriverStore"]
- path: "tests/test_db.py"
provides: "Database initialization and schema validation tests"
key_links:
- from: "imptune/main.py"
to: "imptune/db/database.py"
via: "startup event calling init_db()"
pattern: "init_db"
- from: "imptune/db/models.py"
to: "imptune/db/database.py"
via: "BaseModel.Meta.database = db"
pattern: "database = db"
- from: "imptune/db/database.py"
to: "imptune/config.py"
via: "DB_PATH from config"
pattern: "DB_PATH|DATA_DIR"
---
<objective>
Create the full SQLite schema using Peewee ORM (all tables for phases 1-5) and the content-addressed driver storage abstraction. Wire database initialization into the FastAPI startup event.
Purpose: Establish the data layer that all subsequent phases depend on. The full schema is created upfront per the locked user decision, so later phases only add routes and logic — not schema changes. Satisfies INFRA-01 (SQLite auto-init) and INFRA-02 (no external DB).
Output: Working database module with all models, driver storage helper, and startup wiring.
</objective>
<execution_context>
@C:/Users/SebastienQUEROL/.claude/get-shit-done/workflows/execute-plan.md
@C:/Users/SebastienQUEROL/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/01-foundation/01-CONTEXT.md
@.planning/phases/01-foundation/01-RESEARCH.md
<interfaces>
<!-- From plan 01-01: key exports the executor needs -->
From imptune/config.py:
```python
DATA_DIR: str # env var, default "/data"
DB_PATH: str # DATA_DIR + "/imptune.db"
DRIVERS_DIR: str # DATA_DIR + "/drivers"
```
From imptune/main.py:
```python
app = FastAPI(title="ImpTune")
# startup event already creates DATA_DIR/DRIVERS_DIR directories
# Executor must ADD init_db() call to the existing startup event
```
</interfaces>
</context>
<tasks>
<task type="auto" tdd="true">
<name>Task 1: Create Peewee models, database init, and driver storage</name>
<files>
imptune/db/__init__.py,
imptune/db/database.py,
imptune/db/models.py,
imptune/storage/__init__.py,
imptune/storage/driver_store.py
</files>
<behavior>
- test_create_tables: calling init_db() creates all 4 tables (client, driver, printer, icon) in a fresh SQLite file
- test_wal_mode: after init_db(), PRAGMA journal_mode returns "wal"
- test_foreign_keys: after init_db(), PRAGMA foreign_keys returns 1
- test_idempotent: calling init_db() twice does not raise an error
- test_driver_store_save: saving bytes returns their SHA256 hex digest and creates a file at DRIVERS_DIR/{sha256}
- test_driver_store_dedup: saving the same bytes twice results in one file on disk (not two)
- test_driver_store_get_path: get_path(sha256) returns the correct file path
</behavior>
<action>
**imptune/db/database.py**:
- Import SqliteDatabase from peewee, import DB_PATH from imptune.config
- Create db = SqliteDatabase(None) (deferred init — path set at runtime so tests can override)
- init_db() function: call db.init(DB_PATH, pragmas={"journal_mode": "wal", "foreign_keys": 1}), then db.connect(reuse_if_open=True), then import all models and call db.create_tables([Client, Driver, Printer, Icon], safe=True)
- Use deferred database pattern so tests can point at a temp file
**imptune/db/models.py** (full schema for all phases per locked decision):
- BaseModel with Meta.database = db
- Client: name (CharField unique), created_at (DateTimeField default utcnow)
- Driver: sha256 (CharField unique, indexed), original_filename (CharField), size_bytes (IntegerField), uploaded_at (DateTimeField default utcnow), driver_desc (CharField null=True), inf_filename (CharField null=True), architecture (CharField null=True), has_cat_file (BooleanField default=False)
- Printer: name (CharField), ip_address (CharField), port_name (CharField), client (ForeignKeyField Client null=True backref="printers"), driver (ForeignKeyField Driver null=True backref="printers"), duplex_mode (CharField default="OneSided"), color_mode (BooleanField default=True), paper_size (CharField default="A4"), collate (BooleanField default=True), created_at, updated_at (both DateTimeField default utcnow)
- Icon: printer (ForeignKeyField Printer unique backref="icons"), sha256 (CharField), original_filename (CharField), size_bytes (IntegerField), uploaded_at (DateTimeField default utcnow)
**imptune/storage/driver_store.py**:
- Class DriverStore with __init__(self, base_dir: str)
- save(self, data: bytes) -> str: compute SHA256, write to base_dir/{sha256} if not exists, return hex digest
- get_path(self, sha256: str) -> Path: return Path(base_dir) / sha256
- exists(self, sha256: str) -> bool: check if file exists
</action>
<verify>
<automated>cd C:/Users/SebastienQUEROL/Documents/projets/ImpTune && python -m pytest tests/test_db.py -x -v</automated>
</verify>
<done>
- All 7 tests pass
- init_db() creates Client, Driver, Printer, Icon tables
- WAL mode and foreign keys enabled
- Idempotent — second call is no-op
- DriverStore deduplicates by SHA256
</done>
</task>
<task type="auto">
<name>Task 2: Wire database init into FastAPI startup</name>
<files>
imptune/main.py
</files>
<action>
Modify the existing imptune/main.py (created by plan 01-01) to add database initialization on startup:
- Import init_db from imptune.db.database
- In the existing startup event handler, add a call to init_db() AFTER the directory creation logic
- This ensures the SQLite database is created inside DATA_DIR (which was just created/verified)
- Keep all existing code (StaticFiles mount, router includes, directory creation) — only ADD the init_db() call
Do NOT use async def for the startup handler — Peewee is sync-only. Use regular def with FastAPI's @app.on_event("startup") which already exists from plan 01-01.
</action>
<verify>
<automated>cd C:/Users/SebastienQUEROL/Documents/projets/ImpTune && python -m pytest tests/test_health.py tests/test_db.py -x -v</automated>
</verify>
<done>
- main.py imports and calls init_db() on startup
- Existing health and static tests still pass (no regression)
- Database tests pass with init triggered via app startup
</done>
</task>
</tasks>
<verification>
- `python -m pytest tests/ -x -v` — all tests pass (health + static + db)
- `python -c "from imptune.db.models import Client, Driver, Printer, Icon; print('Models OK')"` — imports without error
- `python -c "from imptune.storage.driver_store import DriverStore; print('DriverStore OK')"` — imports without error
</verification>
<success_criteria>
- SQLite database auto-creates on app startup with 4 tables
- WAL journal mode and foreign keys enabled via pragmas
- Database file lives at DATA_DIR/imptune.db (volume-mounted path)
- DriverStore saves files by SHA256 with deduplication
- All existing tests continue to pass (no regression)
- 7+ new tests pass for db and storage
</success_criteria>
<output>
After completion, create `.planning/phases/01-foundation/01-02-SUMMARY.md`
</output>