feat(domain): SQLite repo for Package aggregate (task 26)

Merged
MP
6mo ago
+1482-24

Summary

Persists the Package aggregate to SQLite with full CRUD operations and proper foreign key handling. Implements the repository pattern per PRD-v2 §P1.7, unblocking downstream package management tasks (27–31).

Why

Packages (containers, playlists, manual groups, split archives) were in-memory only. Persistence enables user workflows: install container → convert to package → redownload members, or group related downloads manually. Foreign key ON DELETE SET NULL preserves download records even if their package is deleted.

Changes

  • Domain : Refactored Package aggregate with typed PackageId (String, not u64) and new PackageSourceType enum (Container, Playlist, Manual, SplitArchive). Added setters for folder_path, password, auto_extract, priority. Kept in-memory download_ids vec separate from persisted FK inverse.
  • Port : New PackageRepository trait with methods find_by_id, save, list, delete, list_downloads. Contracts match hexagonal architecture (domain never knows about ORM).
  • Migration : m20260429_000007_create_packages creates table with TEXT primary key, source_type enum, optional folder_path/password, defaults for auto_extract (1) and priority (5). Adds package_id FK to downloads with SET NULL cascade. Creates idx_downloads_package.
  • Adapter : SqlitePackageRepo implements trait via sea-orm upsert (insert…on_conflict). Preserves created_at timestamp on update. Uses raw SQL for list_downloads since download entity FK not yet exposed (future task).
  • Entity : sea-orm model with into_domain() and from_domain() converters. Handles type conversions: source_type string ↔ enum, i64 timestamp → u64, validation on unknown enum variants.

Testing

  • 23 unit tests in package.rs covering creation, setters, source-type round-trip, reconstruction.
  • 19 unit tests in package_repo.rs covering save/find/list/delete, upsert with created_at preservation, FK SET NULL validation, list_downloads ordering.
  • 5 unit tests in ports/driven/tests.rs in-memory mock implementation.
  • In-database migration test verifies schema on fresh and migrated DBs (from pre-packages state).
  • Coverage: 99.73% (domain), 100% (entity), 98.82% (adapter). Acceptance criteria: [x] migration applies on fresh + existing, [x] FK SET NULL works, [x] all fields round-trip, [x] ≥85% coverage.

```bash
cargo test --workspace -- --nocapture
cargo llvm-cov --workspace --fail-under-lines 85
```

All 1204 tests pass (1201 existing + 3 new error-path).

Related Issues

  • Closes #Task-26
  • Refs PRD-v2 §P1.7, PRD §6.3
  • Unblocks Task-27 (Commands), Task-28 (Queries), Task-29 (Vue), Task-30 (Auto-grouping), Task-31 (Split archives)

Notes for Reviewer

  • package_id type changed from u64 to String (TEXT primary key in SQL). Required touching DomainEvent::PackageCreated to use PackageId wrapper instead of raw u64.
  • list_downloads() uses raw SQL parameterized query because download entity hasn't yet exposed the new package_id FK field (that wiring happens in future tasks). This keeps the repo self-contained without blocking on downstream changes.
  • save() explicitly excludes created_at from the upsert update columns via sea-orm's on_conflict().do_update().col() chain. This prevents timestamp mutation on subsequent saves.

Checklist

  • Tests added and passing (1204 total)
  • CHANGELOG.md updated
  • No .unwrap() outside tests
  • No new external dependencies
  • No warnings or clippy errors
  • Git workflow followed (feat/task-26 branch, conventional commit, clean tree)
  • Acceptance criteria met (85% coverage, FK validation, round-trip preservation, fresh + existing DB)
  • Self-reviewed diff
  • CI will be green

Summary by cubic

Adds SQLite persistence for packages with a new PackageRepository + SqlitePackageRepo, full CRUD, and strict validation. Implements Task-26 (PRD-v2 §P1.7) and updates events to use string PackageId.

  • New Features

    • Repository: find_by_id, save (upsert, preserves created_at), list (ordered by (created_at, id)), delete, list_downloads (ordered by queue_position; raw SQL now uses ? placeholder for SQLite).
    • Domain: PackageId(String), PackageSourceType (serializes as split_archive), setters; priority 1..=10 enforced; auto_extract is 0/1; created_at must be non-negative; DomainEvent::PackageCreated.id is PackageId.
  • Migration

    • Create real packages table; add downloads.package_id TEXT REFERENCES packages(id) ON DELETE SET NULL and idx_downloads_package.
    • Down migration restores the legacy packages table (BIGINT id, name, created_at).

Summary by CodeRabbit

  • New Features

    • Persistent package storage with richer metadata, deterministic ordering, safe upserts, and preserved downloads when packages are removed.
    • Full Accounts management UI with quota-driven rotation, cooldown/expiry rules, extended selection strategies (including exclusions), credential import/export, and keyring-backed credential storage.
    • Improved package events and repository wiring for UI interactions via the app bridge.
  • Tests

    • Expanded migration, persistence, and round-trip tests covering ordering, upsert stability, detach behavior, and error cases.