soem fixes
This commit is contained in:
@@ -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):
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)}"]
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user