13 KiB
Database Documentation
Document Purpose
This document describes the database schema, models, and data layer of the Aniworld application.
1. Database Overview
Technology
- Database Engine: SQLite 3 (default), PostgreSQL supported
- ORM: SQLAlchemy 2.0 with async support (aiosqlite)
- Location:
data/aniworld.db(configurable viaDATABASE_URL)
Source: src/config/settings.py
Connection Configuration
# Default connection string
DATABASE_URL = "sqlite+aiosqlite:///./data/aniworld.db"
# PostgreSQL alternative
DATABASE_URL = "postgresql+asyncpg://user:pass@localhost/aniworld"
Source: src/server/database/connection.py
2. Entity Relationship Diagram
+-------------------+ +-------------------+ +------------------------+
| anime_series | | episodes | | download_queue_item |
+-------------------+ +-------------------+ +------------------------+
| id (PK) |<--+ | id (PK) | +-->| id (PK, VARCHAR) |
| key (UNIQUE) | | | series_id (FK)----+---+ | series_id (FK)---------+
| name | +---| | | status |
| site | | season | | priority |
| folder | | episode_number | | season |
| created_at | | title | | episode |
| updated_at | | file_path | | progress_percent |
+-------------------+ | is_downloaded | | error_message |
| created_at | | retry_count |
| updated_at | | added_at |
+-------------------+ | started_at |
| completed_at |
| created_at |
| updated_at |
+------------------------+
3. Table Schemas
3.1 anime_series
Stores anime series metadata.
| Column | Type | Constraints | Description |
|---|---|---|---|
id |
INTEGER | PRIMARY KEY, AUTOINCREMENT | Internal database ID |
key |
VARCHAR(255) | UNIQUE, NOT NULL, INDEX | Primary identifier - provider-assigned URL-safe key |
name |
VARCHAR(500) | NOT NULL, INDEX | Display name of the series |
site |
VARCHAR(500) | NOT NULL | Provider site URL |
folder |
VARCHAR(1000) | NOT NULL | Filesystem folder name (metadata only) |
created_at |
DATETIME | NOT NULL, DEFAULT NOW | Record creation timestamp |
updated_at |
DATETIME | NOT NULL, ON UPDATE NOW | Last update timestamp |
Identifier Convention:
keyis the primary identifier for all operations (e.g.,"attack-on-titan")folderis metadata only for filesystem operations (e.g.,"Attack on Titan (2013)")idis used only for database relationships
Source: src/server/database/models.py
3.2 episodes
Stores individual episode information.
| Column | Type | Constraints | Description |
|---|---|---|---|
id |
INTEGER | PRIMARY KEY, AUTOINCREMENT | Internal database ID |
series_id |
INTEGER | FOREIGN KEY, NOT NULL, INDEX | Reference to anime_series.id |
season |
INTEGER | NOT NULL | Season number (1-based) |
episode_number |
INTEGER | NOT NULL | Episode number within season |
title |
VARCHAR(500) | NULLABLE | Episode title if known |
file_path |
VARCHAR(1000) | NULLABLE | Local file path if downloaded |
is_downloaded |
BOOLEAN | NOT NULL, DEFAULT FALSE | Download status flag |
created_at |
DATETIME | NOT NULL, DEFAULT NOW | Record creation timestamp |
updated_at |
DATETIME | NOT NULL, ON UPDATE NOW | Last update timestamp |
Foreign Key:
series_id->anime_series.id(ON DELETE CASCADE)
Source: src/server/database/models.py
3.3 download_queue_item
Stores download queue items with status tracking.
| Column | Type | Constraints | Description |
|---|---|---|---|
id |
VARCHAR(36) | PRIMARY KEY | UUID identifier |
series_id |
INTEGER | FOREIGN KEY, NOT NULL | Reference to anime_series.id |
season |
INTEGER | NOT NULL | Season number |
episode |
INTEGER | NOT NULL | Episode number |
status |
VARCHAR(20) | NOT NULL, DEFAULT 'pending' | Download status |
priority |
VARCHAR(10) | NOT NULL, DEFAULT 'NORMAL' | Queue priority |
progress_percent |
FLOAT | NULLABLE | Download progress (0-100) |
error_message |
TEXT | NULLABLE | Error description if failed |
retry_count |
INTEGER | NOT NULL, DEFAULT 0 | Number of retry attempts |
source_url |
VARCHAR(2000) | NULLABLE | Download source URL |
added_at |
DATETIME | NOT NULL, DEFAULT NOW | When added to queue |
started_at |
DATETIME | NULLABLE | When download started |
completed_at |
DATETIME | NULLABLE | When download completed/failed |
created_at |
DATETIME | NOT NULL, DEFAULT NOW | Record creation timestamp |
updated_at |
DATETIME | NOT NULL, ON UPDATE NOW | Last update timestamp |
Status Values: pending, downloading, paused, completed, failed, cancelled
Priority Values: LOW, NORMAL, HIGH
Foreign Key:
series_id->anime_series.id(ON DELETE CASCADE)
Source: src/server/database/models.py
4. Indexes
| Table | Index Name | Columns | Purpose |
|---|---|---|---|
anime_series |
ix_anime_series_key |
key |
Fast lookup by primary identifier |
anime_series |
ix_anime_series_name |
name |
Search by name |
episodes |
ix_episodes_series_id |
series_id |
Join with series |
download_queue_item |
ix_download_series_id |
series_id |
Filter by series |
download_queue_item |
ix_download_status |
status |
Filter by status |
5. Model Layer
5.1 SQLAlchemy ORM Models
# src/server/database/models.py
class AnimeSeries(Base, TimestampMixin):
__tablename__ = "anime_series"
id: Mapped[int] = mapped_column(Integer, primary_key=True)
key: Mapped[str] = mapped_column(String(255), unique=True, index=True)
name: Mapped[str] = mapped_column(String(500), index=True)
site: Mapped[str] = mapped_column(String(500))
folder: Mapped[str] = mapped_column(String(1000))
episodes: Mapped[List["Episode"]] = relationship(
"Episode", back_populates="series", cascade="all, delete-orphan"
)
Source: src/server/database/models.py
5.2 Pydantic API Models
# src/server/models/download.py
class DownloadItem(BaseModel):
id: str
serie_id: str # Maps to anime_series.key
serie_folder: str # Metadata only
serie_name: str
episode: EpisodeIdentifier
status: DownloadStatus
priority: DownloadPriority
Source: src/server/models/download.py
5.3 Model Mapping
| API Field | Database Column | Notes |
|---|---|---|
serie_id |
anime_series.key |
Primary identifier |
serie_folder |
anime_series.folder |
Metadata only |
serie_name |
anime_series.name |
Display name |
6. Repository Pattern
The QueueRepository class provides data access abstraction.
class QueueRepository:
async def save_item(self, item: DownloadItem) -> None:
"""Save or update a download item."""
async def get_all_items(self) -> List[DownloadItem]:
"""Get all items from database."""
async def delete_item(self, item_id: str) -> bool:
"""Delete item by ID."""
async def get_items_by_status(
self, status: DownloadStatus
) -> List[DownloadItem]:
"""Get items filtered by status."""
Source: src/server/services/queue_repository.py
7. Database Service
The AnimeSeriesService provides async CRUD operations.
class AnimeSeriesService:
@staticmethod
async def create(
db: AsyncSession,
key: str,
name: str,
site: str,
folder: str
) -> AnimeSeries:
"""Create a new anime series."""
@staticmethod
async def get_by_key(
db: AsyncSession,
key: str
) -> Optional[AnimeSeries]:
"""Get series by primary key identifier."""
Source: src/server/database/service.py
8. Data Integrity Rules
Validation Constraints
| Field | Rule | Error Message |
|---|---|---|
anime_series.key |
Non-empty, max 255 chars | "Series key cannot be empty" |
anime_series.name |
Non-empty, max 500 chars | "Series name cannot be empty" |
episodes.season |
0-1000 | "Season number must be non-negative" |
episodes.episode_number |
0-10000 | "Episode number must be non-negative" |
Source: src/server/database/models.py
Cascade Rules
- Deleting
anime_seriesdeletes all relatedepisodesanddownload_queue_item
9. Migration Strategy
Currently, SQLAlchemy's create_all() is used for schema creation.
# src/server/database/connection.py
async def init_db():
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
For production migrations, Alembic is recommended but not yet implemented.
Source: src/server/database/connection.py
10. Common Query Patterns
Get all series with missing episodes
series = await db.execute(
select(AnimeSeries).options(selectinload(AnimeSeries.episodes))
)
for serie in series.scalars():
downloaded = [e for e in serie.episodes if e.is_downloaded]
Get pending downloads ordered by priority
items = await db.execute(
select(DownloadQueueItem)
.where(DownloadQueueItem.status == "pending")
.order_by(
case(
(DownloadQueueItem.priority == "HIGH", 1),
(DownloadQueueItem.priority == "NORMAL", 2),
(DownloadQueueItem.priority == "LOW", 3),
),
DownloadQueueItem.added_at
)
)
11. Database Location
| Environment | Default Location |
|---|---|
| Development | ./data/aniworld.db |
| Production | Via DATABASE_URL environment variable |
| Testing | In-memory SQLite (sqlite+aiosqlite:///:memory:) |