cleanup
This commit is contained in:
parent
700f491ef9
commit
32dc893434
@ -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"
|
||||
}
|
||||
@ -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"
|
||||
}
|
||||
@ -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"
|
||||
}
|
||||
@ -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"
|
||||
}
|
||||
@ -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"
|
||||
}
|
||||
@ -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
|
||||
@ -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()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user