Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8bb8c6aa64 | |||
| 109d3c8ac9 | |||
| 6a934db8ac | |||
| ac7302b1dd | |||
| ac5ee3bb27 | |||
| a9084202e3 | |||
| be9f2a4c0c | |||
| 53fe09351f |
@@ -1 +1 @@
|
|||||||
v1.4.4
|
v1.4.8
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "aniworld-web",
|
"name": "aniworld-web",
|
||||||
"version": "1.4.4",
|
"version": "1.4.8",
|
||||||
"description": "Aniworld Anime Download Manager - Web Frontend",
|
"description": "Aniworld Anime Download Manager - Web Frontend",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"""Authentication API endpoints for Aniworld."""
|
"""Authentication API endpoints for Aniworld."""
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
import structlog
|
||||||
from fastapi import APIRouter, Depends, HTTPException
|
from fastapi import APIRouter, Depends, HTTPException
|
||||||
from fastapi import status as http_status
|
from fastapi import status as http_status
|
||||||
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
||||||
@@ -16,6 +17,8 @@ from src.server.models.config import AppConfig
|
|||||||
from src.server.services.auth_service import AuthError, LockedOutError, auth_service
|
from src.server.services.auth_service import AuthError, LockedOutError, auth_service
|
||||||
from src.server.services.config_service import get_config_service
|
from src.server.services.config_service import get_config_service
|
||||||
|
|
||||||
|
logger = structlog.get_logger(__name__)
|
||||||
|
|
||||||
# NOTE: import dependencies (optional_auth, security) lazily inside handlers
|
# NOTE: import dependencies (optional_auth, security) lazily inside handlers
|
||||||
# to avoid importing heavyweight modules (e.g. sqlalchemy) at import time.
|
# to avoid importing heavyweight modules (e.g. sqlalchemy) at import time.
|
||||||
|
|
||||||
@@ -144,10 +147,7 @@ async def setup_auth(req: SetupRequest):
|
|||||||
# Trigger initialization in background task
|
# Trigger initialization in background task
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
from src.server.services.initialization_service import (
|
from src.server.services.initialization_service import perform_initial_setup
|
||||||
perform_initial_setup,
|
|
||||||
perform_nfo_scan_if_needed,
|
|
||||||
)
|
|
||||||
from src.server.services.progress_service import get_progress_service
|
from src.server.services.progress_service import get_progress_service
|
||||||
|
|
||||||
progress_service = get_progress_service()
|
progress_service = get_progress_service()
|
||||||
@@ -158,9 +158,6 @@ async def setup_auth(req: SetupRequest):
|
|||||||
# Perform the initial series sync and mark as completed
|
# Perform the initial series sync and mark as completed
|
||||||
await perform_initial_setup(progress_service)
|
await perform_initial_setup(progress_service)
|
||||||
|
|
||||||
# Perform NFO scan if configured
|
|
||||||
await perform_nfo_scan_if_needed(progress_service)
|
|
||||||
|
|
||||||
# Start scheduler if anime_directory is now set
|
# Start scheduler if anime_directory is now set
|
||||||
try:
|
try:
|
||||||
from src.server.services.scheduler.scheduler_service import (
|
from src.server.services.scheduler.scheduler_service import (
|
||||||
|
|||||||
@@ -344,7 +344,6 @@ async def lifespan(_application: FastAPI):
|
|||||||
from src.server.services.initialization_service import (
|
from src.server.services.initialization_service import (
|
||||||
perform_initial_setup,
|
perform_initial_setup,
|
||||||
perform_media_scan_if_needed,
|
perform_media_scan_if_needed,
|
||||||
perform_nfo_scan_if_needed,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -373,9 +372,6 @@ async def lifespan(_application: FastAPI):
|
|||||||
"exist yet): %s", e
|
"exist yet): %s", e
|
||||||
)
|
)
|
||||||
|
|
||||||
# Run NFO scan only on first run (if configured)
|
|
||||||
await perform_nfo_scan_if_needed()
|
|
||||||
|
|
||||||
# Initialize download service
|
# Initialize download service
|
||||||
try:
|
try:
|
||||||
from src.server.utils.dependencies import get_download_service
|
from src.server.utils.dependencies import get_download_service
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ class SetupRedirectMiddleware(BaseHTTPMiddleware):
|
|||||||
# Paths that should always be accessible, even without setup
|
# Paths that should always be accessible, even without setup
|
||||||
EXEMPT_PATHS = {
|
EXEMPT_PATHS = {
|
||||||
"/setup", # Setup page itself
|
"/setup", # Setup page itself
|
||||||
|
"/setup/unresolved", # Unresolved folders page (after setup)
|
||||||
"/loading", # Loading page (initialization progress)
|
"/loading", # Loading page (initialization progress)
|
||||||
"/login", # Login page (needs to be accessible after setup)
|
"/login", # Login page (needs to be accessible after setup)
|
||||||
"/queue", # Queue page (for initial load)
|
"/queue", # Queue page (for initial load)
|
||||||
@@ -126,20 +127,9 @@ class SetupRedirectMiddleware(BaseHTTPMiddleware):
|
|||||||
# Otherwise redirect to login
|
# Otherwise redirect to login
|
||||||
return RedirectResponse(url="/login", status_code=302)
|
return RedirectResponse(url="/login", status_code=302)
|
||||||
elif path == "/loading":
|
elif path == "/loading":
|
||||||
# Check if initialization is complete
|
# Always allow access to loading page - it handles its own
|
||||||
try:
|
# redirect flow via WebSocket events (initialization_complete
|
||||||
from src.server.database.connection import get_db_session
|
# event triggers redirect to /setup/unresolved)
|
||||||
from src.server.database.system_settings_service import (
|
|
||||||
SystemSettingsService,
|
|
||||||
)
|
|
||||||
|
|
||||||
async with get_db_session() as db:
|
|
||||||
is_complete = await SystemSettingsService.is_initial_scan_completed(db)
|
|
||||||
if is_complete:
|
|
||||||
# Initialization complete, redirect to login
|
|
||||||
return RedirectResponse(url="/login", status_code=302)
|
|
||||||
except Exception:
|
|
||||||
# If we can't check, allow access to loading page
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Skip setup check for exempt paths
|
# Skip setup check for exempt paths
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ async def _cleanup_legacy_key_files() -> int:
|
|||||||
db_folders: set[str] = {series.folder for series in all_series if series.folder}
|
db_folders: set[str] = {series.folder for series in all_series if series.folder}
|
||||||
|
|
||||||
for folder_name in db_folders:
|
for folder_name in db_folders:
|
||||||
folder_path = settings.anime_directory / folder_name
|
folder_path = Path(settings.anime_directory) / folder_name
|
||||||
key_file = folder_path / "key"
|
key_file = folder_path / "key"
|
||||||
|
|
||||||
if not key_file.exists():
|
if not key_file.exists():
|
||||||
|
|||||||
@@ -378,6 +378,18 @@ class SetupService:
|
|||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# Also check if a series with this key already exists (different folder, same anime)
|
||||||
|
existing_by_key = await AnimeSeriesService.get_by_key(db, resolved_key)
|
||||||
|
if existing_by_key:
|
||||||
|
logger.debug(
|
||||||
|
"Series with key already exists, skipping",
|
||||||
|
folder=folder_name,
|
||||||
|
key=resolved_key,
|
||||||
|
existing_folder=existing_by_key.folder
|
||||||
|
)
|
||||||
|
skipped_existing += 1
|
||||||
|
continue
|
||||||
|
|
||||||
# Check filesystem properties
|
# Check filesystem properties
|
||||||
props = cls._get_series_properties(folder)
|
props = cls._get_series_properties(folder)
|
||||||
|
|
||||||
|
|||||||
@@ -281,15 +281,11 @@
|
|||||||
let isComplete = false;
|
let isComplete = false;
|
||||||
|
|
||||||
const stepOrder = [
|
const stepOrder = [
|
||||||
'series_sync',
|
'series_sync'
|
||||||
'nfo_scan',
|
|
||||||
'media_scan'
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const stepTitles = {
|
const stepTitles = {
|
||||||
'series_sync': 'Syncing Series Database',
|
'series_sync': 'Syncing Series Database'
|
||||||
'nfo_scan': 'Processing NFO Metadata',
|
|
||||||
'media_scan': 'Scanning Media Files'
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function connectWebSocket() {
|
function connectWebSocket() {
|
||||||
@@ -479,32 +475,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function checkUnresolvedAndProceed() {
|
async function checkUnresolvedAndProceed() {
|
||||||
try {
|
// Always redirect to /setup/unresolved after initialization
|
||||||
const token = localStorage.getItem('auth_token');
|
// so users can manually enter unresolved animes
|
||||||
if (!token) {
|
|
||||||
// No token, go to login
|
|
||||||
document.getElementById('completionMessage').style.display = 'block';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await fetch('/api/setup/unresolved', {
|
|
||||||
headers: { 'Authorization': `Bearer ${token}` }
|
|
||||||
});
|
|
||||||
|
|
||||||
if (res.ok) {
|
|
||||||
const unresolved = await res.json();
|
|
||||||
if (unresolved && unresolved.length > 0) {
|
|
||||||
// Has unresolved folders - redirect to unresolved page
|
|
||||||
window.location.href = '/setup/unresolved';
|
window.location.href = '/setup/unresolved';
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Error checking unresolved folders:', e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// No unresolved folders or error - show completion message
|
|
||||||
document.getElementById('completionMessage').style.display = 'block';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function showError(message) {
|
function showError(message) {
|
||||||
|
|||||||
@@ -167,10 +167,16 @@ class TestSetupServiceRun:
|
|||||||
mock_get_db = MagicMock()
|
mock_get_db = MagicMock()
|
||||||
mock_get_db.__aenter__.return_value = mock_db
|
mock_get_db.__aenter__.return_value = mock_db
|
||||||
mock_get_db.__aexit__.return_value = None
|
mock_get_db.__aexit__.return_value = None
|
||||||
|
mock_series_app = AsyncMock()
|
||||||
|
mock_series_app.search.return_value = []
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
'src.server.services.setup_service.settings'
|
'src.server.services.setup_service.settings'
|
||||||
) as mock_settings, \
|
) as mock_settings, \
|
||||||
|
patch(
|
||||||
|
'src.server.services.setup_service.get_series_app',
|
||||||
|
return_value=mock_series_app
|
||||||
|
), \
|
||||||
patch(
|
patch(
|
||||||
'src.server.services.setup_service.get_db_session',
|
'src.server.services.setup_service.get_db_session',
|
||||||
return_value=mock_get_db
|
return_value=mock_get_db
|
||||||
@@ -179,6 +185,10 @@ class TestSetupServiceRun:
|
|||||||
'src.server.services.setup_service.AnimeSeriesService.get_by_folder',
|
'src.server.services.setup_service.AnimeSeriesService.get_by_folder',
|
||||||
new_callable=AsyncMock, return_value=None
|
new_callable=AsyncMock, return_value=None
|
||||||
), \
|
), \
|
||||||
|
patch(
|
||||||
|
'src.server.services.setup_service.AnimeSeriesService.get_by_key',
|
||||||
|
new_callable=AsyncMock, return_value=None
|
||||||
|
), \
|
||||||
patch(
|
patch(
|
||||||
'src.server.services.setup_service.UnresolvedFolderService.get_by_folder_name',
|
'src.server.services.setup_service.UnresolvedFolderService.get_by_folder_name',
|
||||||
new_callable=AsyncMock, return_value=None
|
new_callable=AsyncMock, return_value=None
|
||||||
@@ -258,10 +268,16 @@ class TestSetupServiceRun:
|
|||||||
mock_get_db = MagicMock()
|
mock_get_db = MagicMock()
|
||||||
mock_get_db.__aenter__.return_value = mock_db
|
mock_get_db.__aenter__.return_value = mock_db
|
||||||
mock_get_db.__aexit__.return_value = None
|
mock_get_db.__aexit__.return_value = None
|
||||||
|
mock_series_app = AsyncMock()
|
||||||
|
mock_series_app.search.return_value = []
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
'src.server.services.setup_service.settings'
|
'src.server.services.setup_service.settings'
|
||||||
) as mock_settings, \
|
) as mock_settings, \
|
||||||
|
patch(
|
||||||
|
'src.server.services.setup_service.get_series_app',
|
||||||
|
return_value=mock_series_app
|
||||||
|
), \
|
||||||
patch(
|
patch(
|
||||||
'src.server.services.setup_service.get_db_session',
|
'src.server.services.setup_service.get_db_session',
|
||||||
return_value=mock_get_db
|
return_value=mock_get_db
|
||||||
@@ -270,6 +286,10 @@ class TestSetupServiceRun:
|
|||||||
'src.server.services.setup_service.AnimeSeriesService.get_by_folder',
|
'src.server.services.setup_service.AnimeSeriesService.get_by_folder',
|
||||||
new_callable=AsyncMock, return_value=None
|
new_callable=AsyncMock, return_value=None
|
||||||
), \
|
), \
|
||||||
|
patch(
|
||||||
|
'src.server.services.setup_service.AnimeSeriesService.get_by_key',
|
||||||
|
new_callable=AsyncMock, return_value=None
|
||||||
|
), \
|
||||||
patch(
|
patch(
|
||||||
'src.server.services.setup_service.UnresolvedFolderService.get_by_folder_name',
|
'src.server.services.setup_service.UnresolvedFolderService.get_by_folder_name',
|
||||||
new_callable=AsyncMock, return_value=None
|
new_callable=AsyncMock, return_value=None
|
||||||
@@ -323,6 +343,10 @@ class TestSetupServiceRun:
|
|||||||
'src.server.services.setup_service.AnimeSeriesService.get_by_folder',
|
'src.server.services.setup_service.AnimeSeriesService.get_by_folder',
|
||||||
new_callable=AsyncMock, return_value=None
|
new_callable=AsyncMock, return_value=None
|
||||||
), \
|
), \
|
||||||
|
patch(
|
||||||
|
'src.server.services.setup_service.AnimeSeriesService.get_by_key',
|
||||||
|
new_callable=AsyncMock, return_value=None
|
||||||
|
), \
|
||||||
patch(
|
patch(
|
||||||
'src.server.services.setup_service.UnresolvedFolderService.get_by_folder_name',
|
'src.server.services.setup_service.UnresolvedFolderService.get_by_folder_name',
|
||||||
new_callable=AsyncMock, return_value=None
|
new_callable=AsyncMock, return_value=None
|
||||||
@@ -401,6 +425,10 @@ class TestSetupServiceRun:
|
|||||||
'src.server.services.setup_service.AnimeSeriesService.get_by_folder',
|
'src.server.services.setup_service.AnimeSeriesService.get_by_folder',
|
||||||
new_callable=AsyncMock, return_value=None
|
new_callable=AsyncMock, return_value=None
|
||||||
), \
|
), \
|
||||||
|
patch(
|
||||||
|
'src.server.services.setup_service.AnimeSeriesService.get_by_key',
|
||||||
|
new_callable=AsyncMock, return_value=None
|
||||||
|
), \
|
||||||
patch(
|
patch(
|
||||||
'src.server.services.setup_service.UnresolvedFolderService.get_by_folder_name',
|
'src.server.services.setup_service.UnresolvedFolderService.get_by_folder_name',
|
||||||
new_callable=AsyncMock, return_value=None
|
new_callable=AsyncMock, return_value=None
|
||||||
|
|||||||
Reference in New Issue
Block a user