Compare commits

..

8 Commits

Author SHA1 Message Date
576d9f7a7b chore: bump version 2026-06-06 23:09:47 +02:00
af93daeddc fix: allow unresolved page access during setup flow
- Remove premature auth redirect in unresolved.html fetchUnresolved()
- Add /api/setup/ to middleware exempt paths
- Unresolved page now loads without auth token (part of setup flow)
- Only redirect to login on 401 (expired token) or when all folders resolved
2026-06-06 23:08:54 +02:00
a05795bb35 chore: bump version 2026-06-06 22:47:56 +02:00
d22df947e4 feat(setup): redirect to /loading instead of / after setup flow
- loading.html: check for unresolved folders before redirecting, go to /login if none
- unresolved.html: redirect to /loading instead of / after skip/timeout
- add docs/NAVIGATION.md navigation flow documentation
2026-06-06 22:46:02 +02:00
8bb8c6aa64 chore: bump version 2026-06-06 21:53:57 +02:00
109d3c8ac9 fix: streamline initialization flow after setup
- Remove nfo_scan and media_scan from loading page steps (no longer shown in UI)
- Remove perform_nfo_scan_if_needed calls from fastapi_app and auth.py
- Always redirect to /setup/unresolved after initialization completes
  instead of conditionally checking for unresolved folders
- Fix middleware to allow access to /loading page - let it handle
  its own redirect flow via WebSocket events

This ensures users always reach the unresolved folders page after
initial setup to manually configure any unmatched anime series.
2026-06-06 21:33:41 +02:00
6a934db8ac chore: bump version 2026-06-06 20:38:21 +02:00
ac7302b1dd fix: add /setup/unresolved to exempt paths and improve error handling
- Add /setup/unresolved to EXEMPT_PATHS to allow access after initial setup
- Handle 401 Unauthorized response in loading page (clear invalid token)
- Add console.log statements for debugging setup flow issues
2026-06-06 20:37:11 +02:00
8 changed files with 201 additions and 58 deletions

View File

@@ -1 +1 @@
v1.4.6
v1.4.10

174
docs/NAVIGATION.md Normal file
View File

@@ -0,0 +1,174 @@
# Navigation & Redirect Logic
This document describes the setup flow navigation, covering how users progress from initial setup through to the main application.
## Overview
The application uses a middleware-based redirect system to ensure users complete setup before accessing the main app. The flow involves multiple pages handling setup completion, unresolved folder detection, and initialization.
## Setup Flow
```
┌─────────────────────────────────────────────────────────────────────┐
│ SETUP FLOW │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ /setup ──► /loading ──┬──► /setup/unresolved ──► /loading │
│ │ │ │ │ │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ (first time) (WebSocket) (has folders) (all resolved) │
│ │ │ │
│ ▼ │ │
│ /login ◄───────────────────┴──────────────────────┤
│ │
└─────────────────────────────────────────────────────────────────────┘
```
## Middleware: SetupRedirectMiddleware
**File:** `src/server/middleware/setup_redirect.py`
The middleware intercepts all requests and redirects to `/setup` if:
- No master password is configured
- Configuration file is missing or invalid
### Exempt Paths (always accessible)
| Path | Purpose |
|------|---------|
| `/setup` | Initial setup page |
| `/setup/unresolved` | Unresolved folder resolution |
| `/loading` | Initialization progress page |
| `/login` | Authentication |
| `/api/auth/*` | Auth endpoints |
| `/api/config/*` | Config API |
| `/api/health` | Health check |
| `/static/*` | Static assets |
### Middleware Logic
1. **Setup incomplete** → Redirect to `/setup`
2. **Setup complete, accessing `/setup`** → Redirect to `/loading`
3. **Setup complete, accessing `/loading`** → Allow access (page handles its own redirect)
4. **API requests during setup** → Return 503 with `setup_url`
## Pages
### 1. Setup Page (`/setup`)
**File:** `src/server/web/templates/setup.html`
Handles initial configuration:
- Master password creation
- Anime directory selection
- Database initialization
**Post-completion flow:**
- Redirects to `/loading` to begin initialization
### 2. Loading Page (`/loading`)
**File:** `src/server/web/templates/loading.html`
Shows initialization progress via WebSocket:
- Series scanning
- Database population
- Logo/image loading
**Post-initialization flow:**
```javascript
async function checkUnresolvedAndProceed() {
// Fetch unresolved folders via API
const res = await fetch('/api/setup/unresolved', {
headers: { 'Authorization': `Bearer ${token}` }
});
const folders = await res.json();
if (folders.length > 0) {
// Has unresolved folders → go to resolution page
window.location.href = '/setup/unresolved';
} else {
// No unresolved folders → go to login
window.location.href = '/login';
}
}
```
### 3. Unresolved Folders Page (`/setup/unresolved`)
**File:** `src/server/web/templates/unresolved.html`
Allows manual resolution of folders that couldn't be auto-matched:
- Shows list of unresolved folders
- Provides search suggestions
- Input field for entering provider key
- Resolve/delete actions
**Post-resolution flow:**
```javascript
function checkEmptyList() {
if (listEl.children.length === 0) {
// All folders resolved → return to loading
setTimeout(() => { window.location.href = '/loading'; }, 2000);
}
}
```
### 4. Login Page (`/login`)
**File:** `src/server/web/templates/login.html`
Authentication page. After successful login → redirect to `/` (main app).
## API Endpoints
### Unresolved Folders API
| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | `/api/setup/unresolved` | List all unresolved folders |
| `GET` | `/api/setup/unresolved/{folder_name}` | Get specific folder details |
| `POST` | `/api/setup/unresolved/{folder_name}/resolve` | Resolve with provider key |
| `POST` | `/api/setup/unresolved/{folder_name}/search` | Re-search for matches |
| `DELETE` | `/api/setup/unresolved/{folder_name}` | Remove folder from tracking |
### Auth API
| Method | Endpoint | Description |
|--------|----------|-------------|
| `POST` | `/api/auth/setup` | Create master password |
| `POST` | `/api/auth/login` | Authenticate |
| `POST` | `/api/auth/logout` | End session |
## Key Files
| File | Purpose |
|------|---------|
| `src/server/middleware/setup_redirect.py` | Redirect middleware |
| `src/server/controllers/page_controller.py` | Page route handlers |
| `src/server/web/templates/setup.html` | Setup template |
| `src/server/web/templates/loading.html` | Loading template |
| `src/server/web/templates/unresolved.html` | Unresolved folders template |
| `src/server/api/setup_endpoints.py` | Unresolved folders API |
| `src/server/database/service.py` | UnresolvedFolderService |
## Common Issues
### Redirect Loop
**Symptom:** Browser keeps redirecting between pages.
**Causes:**
1. `loading.html` always redirected to `/setup/unresolved` without checking if any exist
2. `unresolved.html` redirected to `/` which middleware redirected back to `/login`
**Fix:** See the navigation logic updates in loading.html and unresolved.html.
### Can't Access Unresolved Page After Setup
**Symptom:** Middleware redirects to `/login` instead of allowing access to `/setup/unresolved`.
**Cause:** `/setup/unresolved` is in the exempt paths but the request may not be reaching it due to completion check timing.
**Fix:** The middleware allows access to `/loading` which handles the redirect to `/setup/unresolved` after initialization.

View File

@@ -1,6 +1,6 @@
{
"name": "aniworld-web",
"version": "1.4.6",
"version": "1.4.10",
"description": "Aniworld Anime Download Manager - Web Frontend",
"type": "module",
"scripts": {

View File

@@ -147,10 +147,7 @@ async def setup_auth(req: SetupRequest):
# Trigger initialization in background task
import asyncio
from src.server.services.initialization_service import (
perform_initial_setup,
perform_nfo_scan_if_needed,
)
from src.server.services.initialization_service import perform_initial_setup
from src.server.services.progress_service import get_progress_service
progress_service = get_progress_service()
@@ -161,9 +158,6 @@ async def setup_auth(req: SetupRequest):
# Perform the initial series sync and mark as completed
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
try:
from src.server.services.scheduler.scheduler_service import (

View File

@@ -344,7 +344,6 @@ async def lifespan(_application: FastAPI):
from src.server.services.initialization_service import (
perform_initial_setup,
perform_media_scan_if_needed,
perform_nfo_scan_if_needed,
)
try:
@@ -373,9 +372,6 @@ async def lifespan(_application: FastAPI):
"exist yet): %s", e
)
# Run NFO scan only on first run (if configured)
await perform_nfo_scan_if_needed()
# Initialize download service
try:
from src.server.utils.dependencies import get_download_service

View File

@@ -32,10 +32,12 @@ class SetupRedirectMiddleware(BaseHTTPMiddleware):
# Paths that should always be accessible, even without setup
EXEMPT_PATHS = {
"/setup", # Setup page itself
"/setup/unresolved", # Unresolved folders page (after setup)
"/loading", # Loading page (initialization progress)
"/login", # Login page (needs to be accessible after setup)
"/queue", # Queue page (for initial load)
"/api/auth/", # All auth endpoints (setup, login, logout, register)
"/api/setup/", # Setup API (unresolved folders, etc.)
"/ws/connect", # WebSocket connection (needed for loading page)
"/api/queue/", # Queue API endpoints
"/api/downloads/", # Download API endpoints
@@ -126,21 +128,10 @@ class SetupRedirectMiddleware(BaseHTTPMiddleware):
# Otherwise redirect to login
return RedirectResponse(url="/login", status_code=302)
elif path == "/loading":
# Check if initialization is complete
try:
from src.server.database.connection import get_db_session
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
# Always allow access to loading page - it handles its own
# redirect flow via WebSocket events (initialization_complete
# event triggers redirect to /setup/unresolved)
pass
# Skip setup check for exempt paths
if self._is_path_exempt(path):

View File

@@ -281,15 +281,11 @@
let isComplete = false;
const stepOrder = [
'series_sync',
'nfo_scan',
'media_scan'
'series_sync'
];
const stepTitles = {
'series_sync': 'Syncing Series Database',
'nfo_scan': 'Processing NFO Metadata',
'media_scan': 'Scanning Media Files'
'series_sync': 'Syncing Series Database'
};
function connectWebSocket() {
@@ -479,32 +475,26 @@
}
async function checkUnresolvedAndProceed() {
// Fetch unresolved folders and only redirect if there are any
// Otherwise go directly to login
try {
const token = localStorage.getItem('auth_token');
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
const folders = await res.json();
if (folders && folders.length > 0) {
// Has unresolved folders - go to resolution page
window.location.href = '/setup/unresolved';
return;
}
}
} catch (e) {
console.error('Error checking unresolved folders:', e);
} catch (err) {
console.error('Failed to check unresolved folders:', err);
}
// No unresolved folders or error - show completion message
document.getElementById('completionMessage').style.display = 'block';
// No unresolved folders or error - go to login
window.location.href = '/login';
}
function showError(message) {

View File

@@ -443,15 +443,13 @@
// API client helpers
async function fetchUnresolved() {
// Note: /api/setup/unresolved does not require auth
// It's accessible during the initial setup flow
const token = localStorage.getItem('auth_token');
if (!token) {
window.location.href = '/login';
return null;
}
const res = await fetch('/api/setup/unresolved', {
headers: { 'Authorization': `Bearer ${token}` }
});
const headers = token ? { 'Authorization': `Bearer ${token}` } : {};
const res = await fetch('/api/setup/unresolved', { headers });
if (res.status === 401) {
// Redirect to login only if we had a token but it expired
localStorage.removeItem('auth_token');
window.location.href = '/login';
return null;
@@ -552,7 +550,7 @@
listEl.style.display = 'none';
emptyEl.style.display = 'block';
document.getElementById('skip-link').style.display = 'block';
setTimeout(() => { window.location.href = '/'; }, 2000);
setTimeout(() => { window.location.href = '/loading'; }, 2000);
} else {
listEl.style.display = 'flex';
emptyEl.style.display = 'none';
@@ -690,7 +688,7 @@
emptyEl.style.display = 'block';
skipLink.style.display = 'block';
showToast('All series configured!', 'success');
setTimeout(() => { window.location.href = '/'; }, 2000);
setTimeout(() => { window.location.href = '/loading'; }, 2000);
}
}