Lukas 30de86e77a feat(database): Add comprehensive database initialization module
- Add src/server/database/init.py with complete initialization framework
  * Schema creation with idempotent table generation
  * Schema validation with detailed reporting
  * Schema versioning (v1.0.0) and migration support
  * Health checks with connectivity monitoring
  * Backup functionality for SQLite databases
  * Initial data seeding framework
  * Utility functions for database info and migration guides

- Add comprehensive test suite (tests/unit/test_database_init.py)
  * 28 tests covering all functionality
  * 100% test pass rate
  * Integration tests and error handling

- Update src/server/database/__init__.py
  * Export new initialization functions
  * Add schema version and expected tables constants

- Fix syntax error in src/server/models/anime.py
  * Remove duplicate import statement

- Update instructions.md
  * Mark database initialization task as complete

Features:
- Automatic schema creation and validation
- Database health monitoring
- Backup creation with timestamps
- Production-ready with Alembic migration guidance
- Async/await support throughout
- Comprehensive error handling and logging

Test Results: 69/69 database tests passing (100%)
2025-10-19 17:21:31 +02:00

62 lines
2.5 KiB
Python

from __future__ import annotations
from datetime import datetime
from typing import List, Optional
from pydantic import BaseModel, Field, HttpUrl
class EpisodeInfo(BaseModel):
"""Information about a single episode."""
episode_number: int = Field(..., ge=1, description="Episode index (1-based)")
title: Optional[str] = Field(None, description="Optional episode title")
aired_at: Optional[datetime] = Field(None, description="Air date/time if known")
duration_seconds: Optional[int] = Field(None, ge=0, description="Duration in seconds")
available: bool = Field(True, description="Whether the episode is available for download")
sources: List[HttpUrl] = Field(default_factory=list, description="List of known streaming/download source URLs")
class MissingEpisodeInfo(BaseModel):
"""Represents a gap in the episode list for a series."""
from_episode: int = Field(..., ge=1, description="Starting missing episode number")
to_episode: int = Field(..., ge=1, description="Ending missing episode number (inclusive)")
reason: Optional[str] = Field(None, description="Optional explanation why episodes are missing")
@property
def count(self) -> int:
"""Number of missing episodes in the range."""
return max(0, self.to_episode - self.from_episode + 1)
class AnimeSeriesResponse(BaseModel):
"""Response model for a series with metadata and episodes."""
id: str = Field(..., description="Unique series identifier")
title: str = Field(..., description="Series title")
alt_titles: List[str] = Field(default_factory=list, description="Alternative titles")
description: Optional[str] = Field(None, description="Short series description")
total_episodes: Optional[int] = Field(None, ge=0, description="Declared total episode count if known")
episodes: List[EpisodeInfo] = Field(default_factory=list, description="Known episodes information")
missing_episodes: List[MissingEpisodeInfo] = Field(default_factory=list, description="Detected missing episode ranges")
thumbnail: Optional[HttpUrl] = Field(None, description="Optional thumbnail image URL")
class SearchRequest(BaseModel):
"""Request payload for searching series."""
query: str = Field(..., min_length=1)
limit: int = Field(10, ge=1, le=100)
include_adult: bool = Field(False)
class SearchResult(BaseModel):
"""Search result item for a series discovery endpoint."""
id: str
title: str
snippet: Optional[str] = None
thumbnail: Optional[HttpUrl] = None
score: Optional[float] = None