soem fixes

This commit is contained in:
2025-12-02 14:04:37 +01:00
parent e0a7c6baa9
commit 4347057c06
10 changed files with 188 additions and 498 deletions

View File

@@ -26,11 +26,12 @@ optional_bearer = HTTPBearer(auto_error=False)
@router.post("/setup", status_code=http_status.HTTP_201_CREATED)
def setup_auth(req: SetupRequest):
async def setup_auth(req: SetupRequest):
"""Initial setup endpoint to configure the master password.
This endpoint also initializes the configuration with default values
and saves the anime directory and master password hash.
If anime_directory is provided, runs migration for existing data files.
"""
if auth_service.is_configured():
raise HTTPException(
@@ -57,17 +58,37 @@ def setup_auth(req: SetupRequest):
config.other['master_password_hash'] = password_hash
# Store anime directory in config's other field if provided
anime_directory = None
if hasattr(req, 'anime_directory') and req.anime_directory:
config.other['anime_directory'] = req.anime_directory
anime_directory = req.anime_directory.strip()
if anime_directory:
config.other['anime_directory'] = anime_directory
# Save the config with the password hash and anime directory
config_service.save_config(config, create_backup=False)
# Run migration if anime directory was provided
response = {"status": "ok"}
if anime_directory:
from src.server.services.startup_migration import (
run_migration_for_directory,
)
migration_result = await run_migration_for_directory(
anime_directory
)
if migration_result:
response["migration"] = {
"total_found": migration_result.total_found,
"migrated": migration_result.migrated,
"skipped": migration_result.skipped,
"failed": migration_result.failed,
}
return response
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e)) from e
return {"status": "ok"}
@router.post("/login", response_model=LoginResponse)
def login(req: LoginRequest):

View File

@@ -1,4 +1,4 @@
from typing import Dict, List, Optional
from typing import Any, Dict, List, Optional
from fastapi import APIRouter, Depends, HTTPException, status
@@ -210,18 +210,18 @@ def update_advanced_config(
) from e
@router.post("/directory", response_model=Dict[str, str])
def update_directory(
@router.post("/directory", response_model=Dict[str, Any])
async def update_directory(
directory_config: Dict[str, str], auth: dict = Depends(require_auth)
) -> Dict[str, str]:
"""Update anime directory configuration.
) -> Dict[str, Any]:
"""Update anime directory configuration and run migration.
Args:
directory_config: Dictionary with 'directory' key
auth: Authentication token (required)
Returns:
Success message
Success message with optional migration results
"""
try:
directory = directory_config.get("directory")
@@ -235,13 +235,27 @@ def update_directory(
app_config = config_service.load_config()
# Store directory in other section
if "anime_directory" not in app_config.other:
app_config.other["anime_directory"] = directory
else:
app_config.other["anime_directory"] = directory
app_config.other["anime_directory"] = directory
config_service.save_config(app_config)
return {"message": "Anime directory updated successfully"}
# Run migration for the new directory
from src.server.services.startup_migration import run_migration_for_directory
migration_result = await run_migration_for_directory(directory)
response: Dict[str, Any] = {
"message": "Anime directory updated successfully"
}
if migration_result:
response["migration"] = {
"total_found": migration_result.total_found,
"migrated": migration_result.migrated,
"skipped": migration_result.skipped,
"failed": migration_result.failed,
}
return response
except ConfigServiceError as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,

View File

@@ -86,19 +86,24 @@ async def init_db() -> None:
db_url = _get_database_url()
logger.info(f"Initializing database: {db_url}")
# Build engine kwargs based on database type
is_sqlite = "sqlite" in db_url
engine_kwargs = {
"echo": settings.log_level == "DEBUG",
"poolclass": pool.StaticPool if is_sqlite else pool.QueuePool,
"pool_pre_ping": True,
}
# Only add pool_size and max_overflow for non-SQLite databases
if not is_sqlite:
engine_kwargs["pool_size"] = 5
engine_kwargs["max_overflow"] = 10
# Create async engine
_engine = create_async_engine(
db_url,
echo=settings.log_level == "DEBUG",
poolclass=pool.StaticPool if "sqlite" in db_url else pool.QueuePool,
pool_size=5 if "sqlite" not in db_url else None,
max_overflow=10 if "sqlite" not in db_url else None,
pool_pre_ping=True,
future=True,
)
_engine = create_async_engine(db_url, **engine_kwargs)
# Configure SQLite if needed
if "sqlite" in db_url:
if is_sqlite:
_configure_sqlite_engine(_engine)
# Create async session factory
@@ -112,12 +117,13 @@ async def init_db() -> None:
# Create sync engine for initial setup
sync_url = settings.database_url
_sync_engine = create_engine(
sync_url,
echo=settings.log_level == "DEBUG",
poolclass=pool.StaticPool if "sqlite" in sync_url else pool.QueuePool,
pool_pre_ping=True,
)
is_sqlite_sync = "sqlite" in sync_url
sync_engine_kwargs = {
"echo": settings.log_level == "DEBUG",
"poolclass": pool.StaticPool if is_sqlite_sync else pool.QueuePool,
"pool_pre_ping": True,
}
_sync_engine = create_engine(sync_url, **sync_engine_kwargs)
# Create sync session factory
_sync_session_factory = sessionmaker(

View File

@@ -264,10 +264,20 @@ class DataMigrationService:
str(k): v for k, v in (serie.episodeDict or {}).items()
}
# Use folder as fallback name if name is empty
series_name = serie.name
if not series_name or not series_name.strip():
series_name = serie.folder
logger.debug(
"Using folder '%s' as name for series '%s'",
series_name,
serie.key
)
await AnimeSeriesService.create(
db,
key=serie.key,
name=serie.name,
name=series_name,
site=serie.site,
folder=serie.folder,
episode_dict=episode_dict_for_db,

View File

@@ -22,6 +22,7 @@ from pathlib import Path
from typing import Optional
from src.server.database.connection import get_db_session
from src.server.services.auth_service import auth_service
from src.server.services.config_service import ConfigService
from src.server.services.data_migration_service import (
MigrationResult,
@@ -116,6 +117,37 @@ def _get_anime_directory_from_config() -> Optional[str]:
return None
def _is_setup_complete() -> bool:
"""Check if the application setup is complete.
Setup is complete when:
1. Master password is configured
2. Configuration file exists and is valid
Returns:
True if setup is complete, False otherwise
"""
# Check if master password is configured
if not auth_service.is_configured():
return False
# Check if config exists and is valid
try:
config_service = ConfigService()
config = config_service.load_config()
# Validate the loaded config
validation = config.validate()
if not validation.valid:
return False
except Exception:
# If we can't load or validate config, setup is not complete
return False
return True
async def ensure_migration_on_startup() -> Optional[MigrationResult]:
"""Ensure data file migration runs during application startup.
@@ -123,6 +155,9 @@ async def ensure_migration_on_startup() -> Optional[MigrationResult]:
It loads the anime directory from configuration and runs the
migration if the directory is configured and contains data files.
Migration will only run if setup is complete (master password
configured and valid configuration exists).
Returns:
MigrationResult if migration was run, None if skipped
(e.g., when no anime directory is configured)
@@ -157,6 +192,13 @@ async def ensure_migration_on_startup() -> Optional[MigrationResult]:
yield
await close_db()
"""
# Check if setup is complete before running migration
if not _is_setup_complete():
logger.debug(
"Setup not complete, skipping startup migration"
)
return None
# Get anime directory from config
anime_directory = _get_anime_directory_from_config()
@@ -203,3 +245,65 @@ async def ensure_migration_on_startup() -> Optional[MigrationResult]:
failed=1,
errors=[f"Migration failed: {str(e)}"]
)
async def run_migration_for_directory(
anime_directory: str
) -> Optional[MigrationResult]:
"""Run data file migration for a specific directory.
This function can be called after setup is complete to migrate
data files from the specified anime directory to the database.
Unlike ensure_migration_on_startup, this does not check setup
status as it's intended to be called after setup is complete.
Args:
anime_directory: Path to the anime directory containing
series folders with data files
Returns:
MigrationResult if migration was run, None if directory invalid
"""
if not anime_directory or not anime_directory.strip():
logger.debug("Empty anime directory provided, skipping migration")
return None
anime_directory = anime_directory.strip()
# Validate directory exists
anime_path = Path(anime_directory)
if not anime_path.exists():
logger.warning(
"Anime directory does not exist: %s, skipping migration",
anime_directory
)
return None
if not anime_path.is_dir():
logger.warning(
"Anime directory path is not a directory: %s",
anime_directory
)
return None
logger.info(
"Running migration for directory: %s",
anime_directory
)
try:
result = await run_startup_migration(anime_directory)
return result
except Exception as e:
logger.error(
"Data file migration failed for %s: %s",
anime_directory,
e,
exc_info=True
)
return MigrationResult(
total_found=0,
failed=1,
errors=[f"Migration failed: {str(e)}"]
)