diff --git a/data/config.json b/data/config.json index e922d5c..f37aea1 100644 --- a/data/config.json +++ b/data/config.json @@ -16,9 +16,6 @@ "path": "data/backups", "keep_days": 30 }, - "other": { - "master_password_hash": "$pbkdf2-sha256$29000$tRZCyFnr/d87x/i/19p7Lw$BoD8EF67N97SRs7kIX8SREbotRwvFntS.WCH9ZwTxHY", - "anime_directory": "/home/lukas/Volume/serien/" - }, + "other": {}, "version": "1.0.0" } \ No newline at end of file diff --git a/data/config_backups/config_backup_20251202_173540.json b/data/config_backups/config_backup_20251202_173540.json deleted file mode 100644 index 15e8092..0000000 --- a/data/config_backups/config_backup_20251202_173540.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "Aniworld", - "data_dir": "data", - "scheduler": { - "enabled": true, - "interval_minutes": 60 - }, - "logging": { - "level": "INFO", - "file": null, - "max_bytes": null, - "backup_count": 3 - }, - "backup": { - "enabled": false, - "path": "data/backups", - "keep_days": 30 - }, - "other": { - "master_password_hash": "$pbkdf2-sha256$29000$JWTsXWstZYyxNiYEQAihFA$K9QPNr2J9biZEX/7SFKU94dnynvyCICrGjKtZcEu6t8" - }, - "version": "1.0.0" -} \ No newline at end of file diff --git a/data/config_backups/config_backup_20251202_175313.json b/data/config_backups/config_backup_20251202_175313.json deleted file mode 100644 index 9285118..0000000 --- a/data/config_backups/config_backup_20251202_175313.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "Aniworld", - "data_dir": "data", - "scheduler": { - "enabled": true, - "interval_minutes": 60 - }, - "logging": { - "level": "INFO", - "file": null, - "max_bytes": null, - "backup_count": 3 - }, - "backup": { - "enabled": false, - "path": "data/backups", - "keep_days": 30 - }, - "other": { - "master_password_hash": "$pbkdf2-sha256$29000$1fo/x1gLYax1bs15L.X8/w$T2GKqjDG7LT9tTZIwX/P2T/uKKuM9IhOD9jmhFUw4A0" - }, - "version": "1.0.0" -} \ No newline at end of file diff --git a/data/config_backups/config_backup_20251213_085947.json b/data/config_backups/config_backup_20251213_085947.json deleted file mode 100644 index dca913d..0000000 --- a/data/config_backups/config_backup_20251213_085947.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "Aniworld", - "data_dir": "data", - "scheduler": { - "enabled": true, - "interval_minutes": 60 - }, - "logging": { - "level": "INFO", - "file": null, - "max_bytes": null, - "backup_count": 3 - }, - "backup": { - "enabled": false, - "path": "data/backups", - "keep_days": 30 - }, - "other": { - "master_password_hash": "$pbkdf2-sha256$29000$nbNWSkkJIeTce48xxrh3bg$QXT6A63JqmSLimtTeI04HzC4eKfQS26xFW7UL9Ry5co", - "anime_directory": "/home/lukas/Volume/serien/" - }, - "version": "1.0.0" -} \ No newline at end of file diff --git a/data/config_backups/config_backup_20251213_090130.json b/data/config_backups/config_backup_20251213_090130.json deleted file mode 100644 index 2157c7d..0000000 --- a/data/config_backups/config_backup_20251213_090130.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "Aniworld", - "data_dir": "data", - "scheduler": { - "enabled": true, - "interval_minutes": 60 - }, - "logging": { - "level": "INFO", - "file": null, - "max_bytes": null, - "backup_count": 3 - }, - "backup": { - "enabled": false, - "path": "data/backups", - "keep_days": 30 - }, - "other": { - "master_password_hash": "$pbkdf2-sha256$29000$j5HSWuu9V.rdm9Pa2zunNA$gjQqL753WLBMZtHVOhziVn.vW3Bkq8mGtCzSkbBjSHo", - "anime_directory": "/home/lukas/Volume/serien/" - }, - "version": "1.0.0" -} \ No newline at end of file diff --git a/docs/tasks/refactor-seriesapp-db-access.md b/docs/tasks/refactor-seriesapp-db-access.md deleted file mode 100644 index 168c576..0000000 --- a/docs/tasks/refactor-seriesapp-db-access.md +++ /dev/null @@ -1,379 +0,0 @@ -# Task: Refactor Database Access Out of SeriesApp - -## Overview - -**Issue**: `SeriesApp` (in `src/core/`) directly contains database access code, violating the clean architecture principle that core domain logic should be independent of infrastructure concerns. - -**Goal**: Move all database operations from `SeriesApp` to the service layer (`src/server/services/`), maintaining clean separation between core domain logic and persistence. - -## Current Architecture (Problematic) - -``` -┌─────────────────────────────────────────────────────────┐ -│ src/core/ (Domain Layer) │ -│ ┌─────────────────────────────────────────────────┐ │ -│ │ SeriesApp │ │ -│ │ - db_session parameter ❌ │ │ -│ │ - Imports from src.server.database ❌ │ │ -│ │ - Calls AnimeSeriesService directly ❌ │ │ -│ └─────────────────────────────────────────────────┘ │ -│ ┌─────────────────────────────────────────────────┐ │ -│ │ SerieList │ │ -│ │ - db_session parameter ❌ │ │ -│ │ - Uses EpisodeService directly ❌ │ │ -│ └─────────────────────────────────────────────────┘ │ -│ ┌─────────────────────────────────────────────────┐ │ -│ │ SerieScanner │ │ -│ │ - db_session parameter ❌ │ │ -│ │ - Uses AnimeSeriesService directly ❌ │ │ -│ └─────────────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────────┘ -``` - -## Target Architecture (Clean) - -``` -┌─────────────────────────────────────────────────────────┐ -│ src/server/services/ (Application Layer) │ -│ ┌─────────────────────────────────────────────────┐ │ -│ │ AnimeService │ │ -│ │ - Owns database session │ │ -│ │ - Orchestrates SeriesApp + persistence │ │ -│ │ - Subscribes to SeriesApp events │ │ -│ │ - Persists changes to database │ │ -│ └─────────────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────────┘ - │ - ▼ calls -┌─────────────────────────────────────────────────────────┐ -│ src/core/ (Domain Layer) │ -│ ┌─────────────────────────────────────────────────┐ │ -│ │ SeriesApp │ │ -│ │ - Pure domain logic only ✅ │ │ -│ │ - No database imports ✅ │ │ -│ │ - Emits events for state changes ✅ │ │ -│ │ - Works with in-memory entities ✅ │ │ -│ └─────────────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────────┘ -``` - -## Benefits of Refactoring - -| Benefit | Description | -| -------------------------- | ------------------------------------------------------- | -| **Clean Layer Separation** | Core layer has no dependencies on server layer | -| **Testability** | `SeriesApp` can be unit tested without database mocking | -| **CLI Compatibility** | CLI can use `SeriesApp` without database setup | -| **Single Responsibility** | Each class has one reason to change | -| **Flexibility** | Easy to swap persistence layer (SQLite → PostgreSQL) | - ---- - -## Task List - -### Phase 1: Analysis & Preparation ✅ - -- [x] **1.1** Document all database operations currently in `SeriesApp` - - File: `src/core/SeriesApp.py` - - Operations: `init_from_db_async()`, `set_db_session()`, db_session propagation -- [x] **1.2** Document all database operations in `SerieList` - - File: `src/core/entities/SerieList.py` - - Operations: `EpisodeService` calls for episode persistence -- [x] **1.3** Document all database operations in `SerieScanner` - - - File: `src/core/SerieScanner.py` - - Operations: `AnimeSeriesService` calls for series persistence - -- [x] **1.4** Identify all events already emitted by `SeriesApp` - - - Review `src/core/events.py` for existing event types - - Determine which events need to be added for persistence triggers - -- [x] **1.5** Create backup/branch before refactoring - ```bash - git checkout -b refactor/remove-db-from-core - ``` - -### Phase 2: Extend Event System ✅ - -- [x] **2.1** Add new events for persistence triggers in `src/core/events.py` - - ```python - # Events that AnimeService should listen to for persistence - class SeriesLoadedEvent: # When series data is loaded/updated - class EpisodeStatusChangedEvent: # When episode download status changes - class ScanCompletedEvent: # When rescan completes with new data - ``` - -- [x] **2.2** Ensure `SeriesApp` emits events at appropriate points - - After loading series from files - - After episode status changes - - After scan completes - -### Phase 3: Refactor SeriesApp ✅ - -- [x] **3.1** Remove `db_session` parameter from `SeriesApp.__init__()` - - - File: `src/core/SeriesApp.py` - - Remove lines ~147-149 (db_session parameter and storage) - -- [x] **3.2** Remove `set_db_session()` method from `SeriesApp` - - - File: `src/core/SeriesApp.py` - - Remove entire method (~lines 191-204) - -- [x] **3.3** Remove `init_from_db_async()` method from `SeriesApp` - - - File: `src/core/SeriesApp.py` - - Remove entire method (~lines 206-238) - - This functionality moves to `AnimeService` - -- [x] **3.4** Remove database imports from `SeriesApp` - - - Remove: `from src.server.database.services.anime_series_service import AnimeSeriesService` - -- [x] **3.5** Update `rescan()` to emit events instead of saving to DB - - File: `src/core/SeriesApp.py` - - Remove direct `AnimeSeriesService` calls - - Emit `ScanCompletedEvent` with scan results - -### Phase 4: Refactor SerieList ✅ - -- [x] **4.1** Remove `db_session` parameter from `SerieList.__init__()` - - - File: `src/core/entities/SerieList.py` - -- [x] **4.2** Remove `set_db_session()` method from `SerieList` - - - File: `src/core/entities/SerieList.py` - -- [x] **4.3** Remove database imports from `SerieList` - - - Remove: `from src.server.database.services.episode_service import EpisodeService` - -- [x] **4.4** Update episode status methods to emit events - - When download status changes, emit `EpisodeStatusChangedEvent` - -### Phase 5: Refactor SerieScanner ✅ - -- [x] **5.1** Remove `db_session` parameter from `SerieScanner.__init__()` - - - File: `src/core/SerieScanner.py` - -- [x] **5.2** Remove database imports from `SerieScanner` - - - Remove: `from src.server.database.services.anime_series_service import AnimeSeriesService` - -- [x] **5.3** Update scanner to return results instead of persisting - - Return scan results as domain objects - - Let `AnimeService` handle persistence - -### Phase 6: Update AnimeService ✅ - -- [x] **6.1** Add event subscription in `AnimeService.__init__()` - - - File: `src/server/services/anime_service.py` - - Subscribe to `SeriesLoadedEvent`, `EpisodeStatusChangedEvent`, `ScanCompletedEvent` - -- [x] **6.2** Implement `_on_series_loaded()` handler - - - Persist series data to database via `AnimeSeriesService` - -- [x] **6.3** Implement `_on_episode_status_changed()` handler - - - Update episode status in database via `EpisodeService` - -- [x] **6.4** Implement `_on_scan_completed()` handler - - - Persist new/updated series to database - -- [x] **6.5** Move `init_from_db_async()` logic to `AnimeService` - - - New method: `load_series_from_database()` - - Loads from DB and populates `SeriesApp` in-memory - -- [x] **6.6** Update `sync_series_from_data_files()` to use new pattern - - Call `SeriesApp` for domain logic - - Handle persistence in service layer - -### Phase 7: Update Dependent Code ✅ - -- [x] **7.1** Update `src/server/dependencies.py` - - - Remove `db_session` from `SeriesApp` initialization - - Ensure `AnimeService` handles DB session lifecycle - -- [x] **7.2** Update API routes if they directly access `SeriesApp` with DB - - - File: `src/server/routes/*.py` - - Routes should call `AnimeService`, not `SeriesApp` directly - -- [x] **7.3** Update CLI if it uses `SeriesApp` - - Ensure CLI works without database (pure file-based mode) - -### Phase 8: Testing ✅ - -- [x] **8.1** Create unit tests for `SeriesApp` without database - - - File: `tests/core/test_series_app.py` - - Test pure domain logic in isolation - -- [x] **8.2** Create unit tests for `AnimeService` with mocked DB - - - File: `tests/server/services/test_anime_service.py` - - Test persistence logic - -- [x] **8.3** Create integration tests for full flow - - - Test `AnimeService` → `SeriesApp` → Events → Persistence - -- [x] **8.4** Run existing tests and fix failures - ```bash - pytest tests/ -v - ``` - - **Result**: 146 unit tests pass for refactored components - -### Phase 9: Documentation ✅ - -- [x] **9.1** Update `docs/instructions.md` architecture section - - - Document new clean separation - -- [x] **9.2** Update inline code documentation - - - Add docstrings explaining the architecture - -- [x] **9.3** Create architecture diagram - - Add to `docs/architecture.md` - ---- - -## Files to Modify - -| File | Changes | -| --------------------------------------------- | ------------------------------------------------ | -| `src/core/SeriesApp.py` | Remove db_session, remove DB methods, add events | -| `src/core/entities/SerieList.py` | Remove db_session, add events | -| `src/core/SerieScanner.py` | Remove db_session, return results only | -| `src/core/events.py` | Add new event types | -| `src/server/services/anime_service.py` | Add event handlers, DB operations | -| `src/server/dependencies.py` | Update initialization | -| `tests/core/test_series_app.py` | New tests | -| `tests/server/services/test_anime_service.py` | New tests | - -## Code Examples - -### Before (Problematic) - -```python -# src/core/SeriesApp.py -class SeriesApp: - def __init__(self, ..., db_session=None): - self._db_session = db_session - # ... passes db_session to children - - async def init_from_db_async(self): - # Direct DB access in core layer ❌ - service = AnimeSeriesService(self._db_session) - series = await service.get_all() -``` - -### After (Clean) - -```python -# src/core/SeriesApp.py -class SeriesApp: - def __init__(self, ...): - # No db_session parameter ✅ - self._event_bus = EventBus() - - def load_series(self, series_list: List[Serie]) -> None: - """Load series into memory (called by service layer).""" - self._series.extend(series_list) - self._event_bus.emit(SeriesLoadedEvent(series_list)) - -# src/server/services/anime_service.py -class AnimeService: - def __init__(self, series_app: SeriesApp, db_session: AsyncSession): - self._series_app = series_app - self._db_session = db_session - # Subscribe to events - series_app.event_bus.subscribe(SeriesLoadedEvent, self._persist_series) - - async def initialize(self): - """Load series from DB into SeriesApp.""" - db_service = AnimeSeriesService(self._db_session) - series = await db_service.get_all() - self._series_app.load_series(series) # Domain logic - - async def _persist_series(self, event: SeriesLoadedEvent): - """Persist series to database.""" - db_service = AnimeSeriesService(self._db_session) - await db_service.save_many(event.series) -``` - -## Acceptance Criteria - -- [x] `src/core/` has **zero imports** from `src/server/database/` -- [x] `SeriesApp` can be instantiated **without any database session** -- [x] All unit tests pass (146/146) -- [x] CLI works without database (file-based mode) -- [x] API endpoints continue to work with full persistence -- [x] No regression in functionality - -## Completion Summary - -**Status**: ✅ COMPLETED - -**Completed Date**: 2024 - -**Summary of Changes**: - -### Core Layer (src/core/) - Now DB-Free: - -- **SeriesApp**: Removed `db_session`, `set_db_session()`, `init_from_db_async()`. Added `load_series_from_list()` method. `rescan()` now returns list of Serie objects. -- **SerieList**: Removed `db_session` from `__init__()`, removed `add_to_db()`, `load_series_from_db()`, `contains_in_db()`, `_convert_from_db()`, `_convert_to_db_dict()` methods. Now pure file-based storage only. -- **SerieScanner**: Removed `db_session`, `scan_async()`, `_save_serie_to_db()`, `_update_serie_in_db()`. Returns scan results without persisting. - -### Service Layer (src/server/services/) - Owns DB Operations: - -- **AnimeService**: Added `_save_scan_results_to_db()`, `_load_series_from_db()`, `_create_series_in_db()`, `_update_series_in_db()`, `add_series_to_db()`, `contains_in_db()` methods. -- **sync_series_from_data_files()**: Updated to use inline DB operations instead of `serie_list.add_to_db()`. - -### Dependencies (src/server/utils/): - -- Removed `get_series_app_with_db()` from dependencies.py. - -### Tests: - -- Updated `tests/unit/test_serie_list.py`: Removed database-related test classes. -- Updated `tests/unit/test_serie_scanner.py`: Removed obsolete async/DB test classes. -- Updated `tests/unit/test_anime_service.py`: Updated TestRescan to mock new DB methods. -- Updated `tests/integration/test_data_file_db_sync.py`: Removed SerieList.add_to_db tests. - -### Verification: - -- **124 unit tests pass** for core layer components (SeriesApp, SerieList, SerieScanner, AnimeService) -- **Zero database imports** in `src/core/` - verified via grep search -- Core layer is now pure domain logic, service layer handles all persistence - -## Estimated Effort - -| Phase | Effort | -| ---------------------- | ------------- | -| Phase 1: Analysis | 2 hours | -| Phase 2: Events | 2 hours | -| Phase 3: SeriesApp | 3 hours | -| Phase 4: SerieList | 2 hours | -| Phase 5: SerieScanner | 2 hours | -| Phase 6: AnimeService | 4 hours | -| Phase 7: Dependencies | 2 hours | -| Phase 8: Testing | 4 hours | -| Phase 9: Documentation | 2 hours | -| **Total** | **~23 hours** | - -## References - -- [docs/instructions.md](../instructions.md) - Project architecture guidelines -- [PEP 8](https://peps.python.org/pep-0008/) - Python style guide -- [Clean Architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html) - Architecture principles diff --git a/src/core/SeriesApp.py b/src/core/SeriesApp.py index f3a427c..37c3ce6 100644 --- a/src/core/SeriesApp.py +++ b/src/core/SeriesApp.py @@ -159,6 +159,7 @@ class SeriesApp: directory_to_search, self.loader ) self.list = SerieList(self.directory_to_search) + self.series_list: List[Any] = [] # Synchronous init used during constructor to avoid awaiting # in __init__ self._init_list_sync()