feat(database): Implement comprehensive database service layer
Implemented database service layer with CRUD operations for all models: - AnimeSeriesService: Create, read, update, delete, search anime series - EpisodeService: Episode management and download tracking - DownloadQueueService: Priority-based queue with status tracking - UserSessionService: Session management with JWT support Features: - Repository pattern for clean separation of concerns - Full async/await support for non-blocking operations - Comprehensive type hints and docstrings - Transaction management via FastAPI dependency injection - Priority queue ordering (HIGH > NORMAL > LOW) - Automatic timestamp management - Cascade delete support Testing: - 22 comprehensive unit tests with 100% pass rate - In-memory SQLite for isolated testing - All CRUD operations tested Documentation: - Enhanced database README with service examples - Integration examples in examples.py - Updated infrastructure.md with service details - Migration utilities for schema management Files: - src/server/database/service.py (968 lines) - src/server/database/examples.py (467 lines) - tests/unit/test_database_service.py (22 tests) - src/server/database/migrations.py (enhanced) - src/server/database/__init__.py (exports added) Closes #9 - Database Layer: Create database service
This commit is contained in:
@@ -4,7 +4,7 @@ SQLAlchemy-based database layer for the Aniworld web application.
|
||||
|
||||
## Overview
|
||||
|
||||
This package provides persistent storage for anime series, episodes, download queue, and user sessions using SQLAlchemy ORM.
|
||||
This package provides persistent storage for anime series, episodes, download queue, and user sessions using SQLAlchemy ORM with comprehensive service layer for CRUD operations.
|
||||
|
||||
## Quick Start
|
||||
|
||||
@@ -198,6 +198,149 @@ The test suite uses an in-memory SQLite database for isolation and speed.
|
||||
- **connection.py**: Engine, session factory, dependency injection
|
||||
- **migrations.py**: Alembic migration placeholder
|
||||
- ****init**.py**: Package exports
|
||||
- **service.py**: Service layer with CRUD operations
|
||||
|
||||
## Service Layer
|
||||
|
||||
The service layer provides high-level CRUD operations for all models:
|
||||
|
||||
### AnimeSeriesService
|
||||
|
||||
```python
|
||||
from src.server.database import AnimeSeriesService
|
||||
|
||||
# Create series
|
||||
series = await AnimeSeriesService.create(
|
||||
db,
|
||||
key="my-anime",
|
||||
name="My Anime",
|
||||
site="https://example.com",
|
||||
folder="/path/to/anime"
|
||||
)
|
||||
|
||||
# Get by ID or key
|
||||
series = await AnimeSeriesService.get_by_id(db, series_id)
|
||||
series = await AnimeSeriesService.get_by_key(db, "my-anime")
|
||||
|
||||
# Get all with pagination
|
||||
all_series = await AnimeSeriesService.get_all(db, limit=50, offset=0)
|
||||
|
||||
# Update
|
||||
updated = await AnimeSeriesService.update(db, series_id, name="Updated Name")
|
||||
|
||||
# Delete (cascades to episodes and downloads)
|
||||
deleted = await AnimeSeriesService.delete(db, series_id)
|
||||
|
||||
# Search
|
||||
results = await AnimeSeriesService.search(db, "naruto", limit=10)
|
||||
```
|
||||
|
||||
### EpisodeService
|
||||
|
||||
```python
|
||||
from src.server.database import EpisodeService
|
||||
|
||||
# Create episode
|
||||
episode = await EpisodeService.create(
|
||||
db,
|
||||
series_id=1,
|
||||
season=1,
|
||||
episode_number=5,
|
||||
title="Episode 5"
|
||||
)
|
||||
|
||||
# Get episodes for series
|
||||
episodes = await EpisodeService.get_by_series(db, series_id, season=1)
|
||||
|
||||
# Get specific episode
|
||||
episode = await EpisodeService.get_by_episode(db, series_id, season=1, episode_number=5)
|
||||
|
||||
# Mark as downloaded
|
||||
updated = await EpisodeService.mark_downloaded(
|
||||
db,
|
||||
episode_id,
|
||||
file_path="/path/to/file.mp4",
|
||||
file_size=1024000
|
||||
)
|
||||
```
|
||||
|
||||
### DownloadQueueService
|
||||
|
||||
```python
|
||||
from src.server.database import DownloadQueueService
|
||||
from src.server.database.models import DownloadPriority, DownloadStatus
|
||||
|
||||
# Add to queue
|
||||
item = await DownloadQueueService.create(
|
||||
db,
|
||||
series_id=1,
|
||||
season=1,
|
||||
episode_number=5,
|
||||
priority=DownloadPriority.HIGH
|
||||
)
|
||||
|
||||
# Get pending downloads (ordered by priority)
|
||||
pending = await DownloadQueueService.get_pending(db, limit=10)
|
||||
|
||||
# Get active downloads
|
||||
active = await DownloadQueueService.get_active(db)
|
||||
|
||||
# Update status
|
||||
updated = await DownloadQueueService.update_status(
|
||||
db,
|
||||
item_id,
|
||||
DownloadStatus.DOWNLOADING
|
||||
)
|
||||
|
||||
# Update progress
|
||||
updated = await DownloadQueueService.update_progress(
|
||||
db,
|
||||
item_id,
|
||||
progress_percent=50.0,
|
||||
downloaded_bytes=500000,
|
||||
total_bytes=1000000,
|
||||
download_speed=50000.0
|
||||
)
|
||||
|
||||
# Clear completed
|
||||
count = await DownloadQueueService.clear_completed(db)
|
||||
|
||||
# Retry failed downloads
|
||||
retried = await DownloadQueueService.retry_failed(db, max_retries=3)
|
||||
```
|
||||
|
||||
### UserSessionService
|
||||
|
||||
```python
|
||||
from src.server.database import UserSessionService
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
# Create session
|
||||
expires_at = datetime.utcnow() + timedelta(hours=24)
|
||||
session = await UserSessionService.create(
|
||||
db,
|
||||
session_id="unique-session-id",
|
||||
token_hash="hashed-jwt-token",
|
||||
expires_at=expires_at,
|
||||
user_id="user123",
|
||||
ip_address="127.0.0.1"
|
||||
)
|
||||
|
||||
# Get session
|
||||
session = await UserSessionService.get_by_session_id(db, "session-id")
|
||||
|
||||
# Get active sessions
|
||||
active = await UserSessionService.get_active_sessions(db, user_id="user123")
|
||||
|
||||
# Update activity
|
||||
updated = await UserSessionService.update_activity(db, "session-id")
|
||||
|
||||
# Revoke session
|
||||
revoked = await UserSessionService.revoke(db, "session-id")
|
||||
|
||||
# Cleanup expired sessions
|
||||
count = await UserSessionService.cleanup_expired(db)
|
||||
```
|
||||
|
||||
## Database Schema
|
||||
|
||||
|
||||
Reference in New Issue
Block a user