fix test and add doc

This commit is contained in:
Lukas 2025-10-22 11:30:04 +02:00
parent 1637835fe6
commit 9692dfc63b
13 changed files with 3562 additions and 146 deletions

View File

@ -1,7 +1,7 @@
{
"pending": [
{
"id": "7ce31824-1042-4a7e-b358-021660fe3f57",
"id": "ec2570fb-9903-4942-87c9-0dc63078bb41",
"serie_id": "workflow-series",
"serie_name": "Workflow Test Series",
"episode": {
@ -11,7 +11,7 @@
},
"status": "pending",
"priority": "high",
"added_at": "2025-10-22T06:33:14.519721Z",
"added_at": "2025-10-22T09:08:49.319607Z",
"started_at": null,
"completed_at": null,
"progress": null,
@ -20,7 +20,7 @@
"source_url": null
},
{
"id": "2037b69a-48c2-4878-aa01-4a715d09d824",
"id": "64d4a680-a4ec-49f8-8a73-ca27fa3e31b7",
"serie_id": "series-2",
"serie_name": "Series 2",
"episode": {
@ -30,7 +30,7 @@
},
"status": "pending",
"priority": "normal",
"added_at": "2025-10-22T06:33:14.205751Z",
"added_at": "2025-10-22T09:08:49.051921Z",
"started_at": null,
"completed_at": null,
"progress": null,
@ -39,7 +39,7 @@
"source_url": null
},
{
"id": "56d39fa2-5590-49ee-a5f9-11b811b8644a",
"id": "98e47c9e-17e5-4205-aacd-4a2d31ca6b29",
"serie_id": "series-1",
"serie_name": "Series 1",
"episode": {
@ -49,7 +49,7 @@
},
"status": "pending",
"priority": "normal",
"added_at": "2025-10-22T06:33:14.202473Z",
"added_at": "2025-10-22T09:08:49.049588Z",
"started_at": null,
"completed_at": null,
"progress": null,
@ -58,7 +58,7 @@
"source_url": null
},
{
"id": "a154fa76-d368-4b49-a440-677c22d497f7",
"id": "aa4bf164-0f66-488d-b5aa-04b152c5ec6b",
"serie_id": "series-0",
"serie_name": "Series 0",
"episode": {
@ -68,7 +68,7 @@
},
"status": "pending",
"priority": "normal",
"added_at": "2025-10-22T06:33:14.197599Z",
"added_at": "2025-10-22T09:08:49.045265Z",
"started_at": null,
"completed_at": null,
"progress": null,
@ -77,7 +77,7 @@
"source_url": null
},
{
"id": "e30b1101-eca3-4e72-891d-8b5f154448b3",
"id": "96b78a9c-bcba-461a-a3f7-c9413c8097bb",
"serie_id": "series-high",
"serie_name": "Series High",
"episode": {
@ -87,7 +87,7 @@
},
"status": "pending",
"priority": "high",
"added_at": "2025-10-22T06:33:13.828413Z",
"added_at": "2025-10-22T09:08:48.825866Z",
"started_at": null,
"completed_at": null,
"progress": null,
@ -96,7 +96,7 @@
"source_url": null
},
{
"id": "36053249-03ad-42d6-83c5-67514f4c5ccd",
"id": "af79a00c-1677-41a4-8cf1-5edd715c660f",
"serie_id": "test-series-2",
"serie_name": "Another Series",
"episode": {
@ -106,7 +106,7 @@
},
"status": "pending",
"priority": "high",
"added_at": "2025-10-22T06:33:13.800966Z",
"added_at": "2025-10-22T09:08:48.802199Z",
"started_at": null,
"completed_at": null,
"progress": null,
@ -115,7 +115,7 @@
"source_url": null
},
{
"id": "6cf9ec9d-351c-4804-bbb1-fee061f3f9fd",
"id": "4f2a07da-0248-4a69-9c8a-e17913fa5fa2",
"serie_id": "test-series-1",
"serie_name": "Test Anime Series",
"episode": {
@ -125,7 +125,7 @@
},
"status": "pending",
"priority": "normal",
"added_at": "2025-10-22T06:33:13.774745Z",
"added_at": "2025-10-22T09:08:48.776865Z",
"started_at": null,
"completed_at": null,
"progress": null,
@ -134,7 +134,7 @@
"source_url": null
},
{
"id": "ac2f472c-4e3f-463b-b679-cf574af9174e",
"id": "7dd638cb-da1a-407f-8716-5bb9d4388a49",
"serie_id": "test-series-1",
"serie_name": "Test Anime Series",
"episode": {
@ -144,7 +144,7 @@
},
"status": "pending",
"priority": "normal",
"added_at": "2025-10-22T06:33:13.774848Z",
"added_at": "2025-10-22T09:08:48.776962Z",
"started_at": null,
"completed_at": null,
"progress": null,
@ -153,7 +153,7 @@
"source_url": null
},
{
"id": "b3d11784-aea5-41c0-8078-adc8c8294b04",
"id": "226764e6-1ac5-43cf-be43-a47a2e4f46e8",
"serie_id": "series-normal",
"serie_name": "Series Normal",
"episode": {
@ -163,7 +163,7 @@
},
"status": "pending",
"priority": "normal",
"added_at": "2025-10-22T06:33:13.831840Z",
"added_at": "2025-10-22T09:08:48.827876Z",
"started_at": null,
"completed_at": null,
"progress": null,
@ -172,7 +172,7 @@
"source_url": null
},
{
"id": "dc1332cc-9230-46e6-bcdc-f0bb0f1ff58b",
"id": "04298256-9f47-41d8-b5ed-b2df0c978ad6",
"serie_id": "series-low",
"serie_name": "Series Low",
"episode": {
@ -182,7 +182,7 @@
},
"status": "pending",
"priority": "low",
"added_at": "2025-10-22T06:33:13.835608Z",
"added_at": "2025-10-22T09:08:48.833026Z",
"started_at": null,
"completed_at": null,
"progress": null,
@ -191,7 +191,7 @@
"source_url": null
},
{
"id": "f86cf7ea-3f59-4e2a-a8bc-e63062995543",
"id": "b5f39f9a-afc1-42ba-94c7-10820413ae8f",
"serie_id": "test-series",
"serie_name": "Test Series",
"episode": {
@ -201,7 +201,7 @@
},
"status": "pending",
"priority": "normal",
"added_at": "2025-10-22T06:33:14.145652Z",
"added_at": "2025-10-22T09:08:49.000308Z",
"started_at": null,
"completed_at": null,
"progress": null,
@ -210,7 +210,7 @@
"source_url": null
},
{
"id": "f0bad497-7bc8-4983-b65e-80f8f61de9e4",
"id": "f8c9f7c1-4d24-4d13-bec2-25001b6b04fb",
"serie_id": "test-series",
"serie_name": "Test Series",
"episode": {
@ -220,7 +220,7 @@
},
"status": "pending",
"priority": "normal",
"added_at": "2025-10-22T06:33:14.235532Z",
"added_at": "2025-10-22T09:08:49.076920Z",
"started_at": null,
"completed_at": null,
"progress": null,
@ -229,7 +229,7 @@
"source_url": null
},
{
"id": "f4656791-4788-4088-aa76-7a9abbeef3d2",
"id": "1954ad7d-d977-4b5b-a603-2c9f4d3bc747",
"serie_id": "invalid-series",
"serie_name": "Invalid Series",
"episode": {
@ -239,7 +239,7 @@
},
"status": "pending",
"priority": "normal",
"added_at": "2025-10-22T06:33:14.295095Z",
"added_at": "2025-10-22T09:08:49.125379Z",
"started_at": null,
"completed_at": null,
"progress": null,
@ -248,7 +248,7 @@
"source_url": null
},
{
"id": "5790af03-d28a-4f78-914c-f82d4c73bde5",
"id": "48d00dab-8caf-4eef-97c4-1ceead6906e7",
"serie_id": "test-series",
"serie_name": "Test Series",
"episode": {
@ -258,7 +258,7 @@
},
"status": "pending",
"priority": "normal",
"added_at": "2025-10-22T06:33:14.324681Z",
"added_at": "2025-10-22T09:08:49.150809Z",
"started_at": null,
"completed_at": null,
"progress": null,
@ -267,26 +267,7 @@
"source_url": null
},
{
"id": "4a293a5d-1bd8-4eb2-b006-286f7e0bed95",
"serie_id": "series-4",
"serie_name": "Series 4",
"episode": {
"season": 1,
"episode": 1,
"title": null
},
"status": "pending",
"priority": "normal",
"added_at": "2025-10-22T06:33:14.367510Z",
"started_at": null,
"completed_at": null,
"progress": null,
"error": null,
"retry_count": 0,
"source_url": null
},
{
"id": "cc7ab85a-b63e-41ba-9e1b-d1c5c0b976f6",
"id": "4cdd33c4-e2bd-4425-8e4d-661b1c3d43b3",
"serie_id": "series-0",
"serie_name": "Series 0",
"episode": {
@ -296,7 +277,7 @@
},
"status": "pending",
"priority": "normal",
"added_at": "2025-10-22T06:33:14.368708Z",
"added_at": "2025-10-22T09:08:49.184788Z",
"started_at": null,
"completed_at": null,
"progress": null,
@ -305,7 +286,7 @@
"source_url": null
},
{
"id": "2cd29cec-7805-465d-b3a0-141cf8583710",
"id": "93f7fba9-65c7-4b95-8610-416fe6b0f3df",
"serie_id": "series-1",
"serie_name": "Series 1",
"episode": {
@ -315,7 +296,7 @@
},
"status": "pending",
"priority": "normal",
"added_at": "2025-10-22T06:33:14.369487Z",
"added_at": "2025-10-22T09:08:49.185634Z",
"started_at": null,
"completed_at": null,
"progress": null,
@ -324,7 +305,26 @@
"source_url": null
},
{
"id": "dc4f4a75-7823-4933-a5f2-491698f741e5",
"id": "a7204eaa-d3a6-4389-9634-1582aabeb963",
"serie_id": "series-4",
"serie_name": "Series 4",
"episode": {
"season": 1,
"episode": 1,
"title": null
},
"status": "pending",
"priority": "normal",
"added_at": "2025-10-22T09:08:49.186289Z",
"started_at": null,
"completed_at": null,
"progress": null,
"error": null,
"retry_count": 0,
"source_url": null
},
{
"id": "1a4a3ed9-2694-4edf-8448-2239cc240d46",
"serie_id": "series-2",
"serie_name": "Series 2",
"episode": {
@ -334,7 +334,7 @@
},
"status": "pending",
"priority": "normal",
"added_at": "2025-10-22T06:33:14.370252Z",
"added_at": "2025-10-22T09:08:49.186944Z",
"started_at": null,
"completed_at": null,
"progress": null,
@ -343,7 +343,7 @@
"source_url": null
},
{
"id": "956bb8fd-b745-436c-bdd1-ea1f522f8faa",
"id": "b3e007b3-da38-46ac-8a96-9cbbaf61777a",
"serie_id": "series-3",
"serie_name": "Series 3",
"episode": {
@ -353,7 +353,7 @@
},
"status": "pending",
"priority": "normal",
"added_at": "2025-10-22T06:33:14.371006Z",
"added_at": "2025-10-22T09:08:49.188800Z",
"started_at": null,
"completed_at": null,
"progress": null,
@ -362,7 +362,7 @@
"source_url": null
},
{
"id": "27adc5f4-32f6-4aa5-9119-7e11f89682d8",
"id": "7d0e5f7e-92f6-4d39-9635-9f4d490ddb3b",
"serie_id": "persistent-series",
"serie_name": "Persistent Series",
"episode": {
@ -372,7 +372,7 @@
},
"status": "pending",
"priority": "normal",
"added_at": "2025-10-22T06:33:14.437853Z",
"added_at": "2025-10-22T09:08:49.246329Z",
"started_at": null,
"completed_at": null,
"progress": null,
@ -381,7 +381,7 @@
"source_url": null
},
{
"id": "3234ac64-d825-444b-8b35-d5d6cad0ad51",
"id": "3466d362-602f-4410-b16a-ac70012035f1",
"serie_id": "ws-series",
"serie_name": "WebSocket Series",
"episode": {
@ -391,7 +391,7 @@
},
"status": "pending",
"priority": "normal",
"added_at": "2025-10-22T06:33:14.488776Z",
"added_at": "2025-10-22T09:08:49.293513Z",
"started_at": null,
"completed_at": null,
"progress": null,
@ -400,7 +400,7 @@
"source_url": null
},
{
"id": "281f5856-ebc5-4dfd-b983-2d11ba865b5b",
"id": "0433681e-6e3a-49fa-880d-24fbef35ff04",
"serie_id": "pause-test",
"serie_name": "Pause Test Series",
"episode": {
@ -410,7 +410,7 @@
},
"status": "pending",
"priority": "normal",
"added_at": "2025-10-22T06:33:14.656270Z",
"added_at": "2025-10-22T09:08:49.452875Z",
"started_at": null,
"completed_at": null,
"progress": null,
@ -421,5 +421,5 @@
],
"active": [],
"failed": [],
"timestamp": "2025-10-22T06:33:14.656532+00:00"
"timestamp": "2025-10-22T09:08:49.453140+00:00"
}

308
docs/README.md Normal file
View File

@ -0,0 +1,308 @@
# Aniworld Documentation
Complete documentation for the Aniworld Download Manager application.
## Quick Start
- **New Users**: Start with [User Guide](./user_guide.md)
- **Developers**: Check [API Reference](./api_reference.md)
- **System Admins**: See [Deployment Guide](./deployment.md)
- **Interactive Docs**: Visit `http://localhost:8000/api/docs`
## Documentation Structure
### 📖 User Guide (`user_guide.md`)
Complete guide for end users covering:
- Installation instructions
- Initial setup and configuration
- User interface walkthrough
- Managing anime library
- Download queue management
- Configuration and settings
- Troubleshooting common issues
- Keyboard shortcuts
- Frequently asked questions (FAQ)
**Best for**: Anyone using the Aniworld application
### 🔌 API Reference (`api_reference.md`)
Detailed API documentation including:
- Authentication and authorization
- Error handling and status codes
- All REST endpoints with examples
- WebSocket real-time updates
- Request/response formats
- Rate limiting and pagination
- Complete workflow examples
- API changelog
**Best for**: Developers integrating with the API
### 🚀 Deployment Guide (`deployment.md`)
Production deployment instructions covering:
- System requirements
- Pre-deployment checklist
- Local development setup
- Production deployment steps
- Docker and Docker Compose setup
- Nginx reverse proxy configuration
- SSL/TLS certificate setup
- Database configuration (SQLite and PostgreSQL)
- Security best practices
- Monitoring and maintenance
- Troubleshooting deployment issues
**Best for**: System administrators and DevOps engineers
## Key Features Documented
### Authentication
- Master password setup and login
- JWT token management
- Session handling
- Security best practices
### Configuration Management
- Application settings
- Directory configuration
- Backup and restore functionality
- Environment variables
### Anime Management
- Browsing anime library
- Adding new anime
- Managing episodes
- Search functionality
### Download Management
- Queue operations
- Priority management
- Progress tracking
- Error recovery
### Real-time Features
- WebSocket connections
- Live download updates
- Status notifications
- Error alerts
## Documentation Examples
### API Usage Example
```bash
# Setup
curl -X POST http://localhost:8000/api/auth/setup \
-H "Content-Type: application/json" \
-d '{"master_password": "secure_pass"}'
# Login
TOKEN=$(curl -X POST http://localhost:8000/api/auth/login \
-H "Content-Type: application/json" \
-d '{"password": "secure_pass"}' | jq -r '.token')
# List anime
curl http://localhost:8000/api/v1/anime \
-H "Authorization: Bearer $TOKEN"
```
### Deployment Example
```bash
# Clone and setup
git clone https://github.com/your-repo/aniworld.git
cd aniworld
python3.10 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
# Run application
python -m uvicorn src.server.fastapi_app:app --host 127.0.0.1 --port 8000
```
## Interactive Documentation
Access interactive API documentation at:
- **Swagger UI**: `http://localhost:8000/api/docs`
- **ReDoc**: `http://localhost:8000/api/redoc`
- **OpenAPI JSON**: `http://localhost:8000/openapi.json`
These provide:
- Interactive API explorer
- Try-it-out functionality
- Request/response examples
- Schema validation
## Common Tasks
### I want to...
**Use the application**
→ Read [User Guide](./user_guide.md) → Getting Started section
**Set up on my computer**
→ Read [User Guide](./user_guide.md) → Installation section
**Deploy to production**
→ Read [Deployment Guide](./deployment.md) → Production Deployment
**Use the API**
→ Read [API Reference](./api_reference.md) → API Endpoints section
**Troubleshoot problems**
→ Read [User Guide](./user_guide.md) → Troubleshooting section
**Set up with Docker**
→ Read [Deployment Guide](./deployment.md) → Docker Deployment
**Configure backup/restore**
→ Read [User Guide](./user_guide.md) → Configuration section
**Debug API issues**
→ Check [API Reference](./api_reference.md) → Error Handling section
## Documentation Standards
All documentation follows these standards:
### Structure
- Clear table of contents
- Logical section ordering
- Cross-references to related topics
- Code examples where appropriate
### Style
- Plain, accessible language
- Step-by-step instructions
- Visual formatting (code blocks, tables, lists)
- Examples for common scenarios
### Completeness
- All major features covered
- Edge cases documented
- Troubleshooting guidance
- FAQ section included
### Maintenance
- Version number tracking
- Last updated timestamp
- Changelog for updates
- Broken link checking
## Help & Support
### Getting Help
1. **Check Documentation First**
- Search in relevant guide
- Check FAQ section
- Look for similar examples
2. **Check Logs**
- Application logs in `/logs/`
- Browser console (F12)
- System logs
3. **Try Troubleshooting**
- Follow troubleshooting steps in user guide
- Check known issues section
- Verify system requirements
4. **Get Community Help**
- GitHub Issues
- Discussion Forums
- Community Discord
5. **Report Issues**
- File GitHub issue
- Include logs and error messages
- Describe reproduction steps
- Specify system details
### Feedback
We welcome feedback on documentation:
- Unclear sections
- Missing information
- Incorrect instructions
- Outdated content
- Suggest improvements
File documentation issues on GitHub with label `documentation`.
## Contributing to Documentation
Documentation improvements are welcome! To contribute:
1. Fork the repository
2. Edit documentation files
3. Test changes locally
4. Submit pull request
5. Include summary of changes
See `CONTRIBUTING.md` for guidelines.
## Documentation Map
```
docs/
├── README.md # This file
├── user_guide.md # End-user documentation
├── api_reference.md # API documentation
├── deployment.md # Deployment instructions
└── CONTRIBUTING.md # Contribution guidelines
```
## Related Resources
- **Source Code**: GitHub repository
- **Interactive API**: `http://localhost:8000/api/docs`
- **Issue Tracker**: GitHub Issues
- **Releases**: GitHub Releases
- **License**: See LICENSE file
## Document Info
- **Last Updated**: October 22, 2025
- **Version**: 1.0.0
- **Status**: Production Ready
- **Maintainers**: Development Team
---
## Quick Links
| Resource | Link |
| ------------------ | -------------------------------------------- |
| User Guide | [user_guide.md](./user_guide.md) |
| API Reference | [api_reference.md](./api_reference.md) |
| Deployment Guide | [deployment.md](./deployment.md) |
| Swagger UI | http://localhost:8000/api/docs |
| GitHub Issues | https://github.com/your-repo/aniworld/issues |
| Project Repository | https://github.com/your-repo/aniworld |
---
**For Questions**: Check relevant guide first, then file GitHub issue with details.

943
docs/api_reference.md Normal file
View File

@ -0,0 +1,943 @@
# Aniworld API Reference
Complete API reference documentation for the Aniworld Download Manager Web Application.
## Table of Contents
1. [API Overview](#api-overview)
2. [Authentication](#authentication)
3. [Error Handling](#error-handling)
4. [API Endpoints](#api-endpoints)
- [Authentication Endpoints](#authentication-endpoints)
- [Configuration Endpoints](#configuration-endpoints)
- [Anime Endpoints](#anime-endpoints)
- [Download Queue Endpoints](#download-queue-endpoints)
- [WebSocket Endpoints](#websocket-endpoints)
- [Health Check Endpoints](#health-check-endpoints)
## API Overview
The Aniworld API is a RESTful API built with FastAPI that provides programmatic access to the anime download manager functionality.
**Base URL**: `http://localhost:8000/api`
**API Documentation**: Available at `http://localhost:8000/api/docs` (Swagger UI) and `http://localhost:8000/api/redoc` (ReDoc)
**API Version**: 1.0.0
**Response Format**: All responses are JSON-formatted unless otherwise specified.
## Authentication
### Master Password Authentication
The API uses JWT (JSON Web Tokens) for stateless authentication. All protected endpoints require a valid JWT token in the Authorization header.
### Authentication Flow
1. **Setup** (one-time): POST to `/api/auth/setup` with master password
2. **Login**: POST to `/api/auth/login` with master password to receive JWT token
3. **Request**: Include token in `Authorization: Bearer <token>` header
### Token Details
- **Token Type**: JWT (JSON Web Token)
- **Expires In**: Configurable (default: 24 hours)
- **Algorithm**: HS256
- **Scope**: All resources accessible with single token
### Example Authentication
```bash
# Login
curl -X POST http://localhost:8000/api/auth/login \
-H "Content-Type: application/json" \
-d '{"password": "your_master_password"}'
# Response
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "bearer"
}
# Use token in subsequent requests
curl -X GET http://localhost:8000/api/anime \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
```
## Error Handling
### Error Response Format
All errors follow a consistent JSON format:
```json
{
"success": false,
"error": "ERROR_CODE",
"message": "Human-readable error message",
"details": {
"additional": "context"
},
"request_id": "unique-request-identifier"
}
```
### HTTP Status Codes
| Code | Meaning | Description |
| ---- | --------------------- | ---------------------------------------- |
| 200 | OK | Successful request |
| 201 | Created | Resource created successfully |
| 204 | No Content | Successful request with no response body |
| 400 | Bad Request | Invalid request parameters |
| 401 | Unauthorized | Authentication required or failed |
| 403 | Forbidden | Insufficient permissions |
| 404 | Not Found | Resource not found |
| 409 | Conflict | Resource conflict |
| 422 | Unprocessable Entity | Validation error |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Internal Server Error | Unexpected server error |
### Error Codes
| Error Code | HTTP Status | Description |
| --------------------- | ----------- | ------------------------- |
| AUTHENTICATION_ERROR | 401 | Authentication failed |
| AUTHORIZATION_ERROR | 403 | Insufficient permissions |
| VALIDATION_ERROR | 422 | Request validation failed |
| NOT_FOUND | 404 | Resource not found |
| CONFLICT | 409 | Resource conflict |
| RATE_LIMIT_EXCEEDED | 429 | Rate limit exceeded |
| INTERNAL_SERVER_ERROR | 500 | Internal server error |
| DOWNLOAD_ERROR | 500 | Download operation failed |
| CONFIGURATION_ERROR | 500 | Configuration error |
| PROVIDER_ERROR | 500 | Provider error |
| DATABASE_ERROR | 500 | Database operation failed |
### Example Error Response
```json
{
"success": false,
"error": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": {
"field": "anime_id",
"issue": "Invalid anime ID format"
},
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
```
## API Endpoints
### Authentication Endpoints
#### Setup Master Password
Configures the master password for the application (one-time only).
```http
POST /api/auth/setup
Content-Type: application/json
{
"master_password": "your_secure_password"
}
```
**Response (201 Created)**:
```json
{
"status": "ok"
}
```
**Errors**:
- `400 Bad Request`: Master password already configured
- `422 Validation Error`: Invalid password format
---
#### Login
Authenticates with master password and returns JWT token.
```http
POST /api/auth/login
Content-Type: application/json
{
"password": "your_master_password"
}
```
**Response (200 OK)**:
```json
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "bearer"
}
```
**Errors**:
- `401 Unauthorized`: Invalid password
- `429 Too Many Requests`: Too many failed attempts
---
#### Logout
Invalidates the current session.
```http
POST /api/auth/logout
Authorization: Bearer <token>
```
**Response (200 OK)**:
```json
{
"success": true,
"message": "Logged out successfully"
}
```
---
#### Check Authentication Status
Verifies current authentication status.
```http
GET /api/auth/status
Authorization: Bearer <token>
```
**Response (200 OK)**:
```json
{
"authenticated": true,
"token_valid": true
}
```
**Errors**:
- `401 Unauthorized`: Token invalid or expired
---
### Configuration Endpoints
#### Get Configuration
Retrieves the current application configuration.
```http
GET /api/config
Authorization: Bearer <token>
```
**Response (200 OK)**:
```json
{
"success": true,
"data": {
"anime_directory": "/path/to/anime",
"download_directory": "/path/to/downloads",
"session_timeout_hours": 24,
"log_level": "info"
}
}
```
---
#### Update Configuration
Updates application configuration (creates backup automatically).
```http
PUT /api/config
Authorization: Bearer <token>
Content-Type: application/json
{
"anime_directory": "/new/anime/path",
"download_directory": "/new/download/path"
}
```
**Response (200 OK)**:
```json
{
"success": true,
"data": {
"anime_directory": "/new/anime/path",
"download_directory": "/new/download/path"
}
}
```
**Errors**:
- `400 Bad Request`: Invalid configuration
- `422 Validation Error`: Validation failed
---
#### Validate Configuration
Validates configuration without applying changes.
```http
POST /api/config/validate
Authorization: Bearer <token>
Content-Type: application/json
{
"anime_directory": "/path/to/validate"
}
```
**Response (200 OK)**:
```json
{
"success": true,
"valid": true,
"message": "Configuration is valid"
}
```
---
#### List Configuration Backups
Lists all configuration backups.
```http
GET /api/config/backups
Authorization: Bearer <token>
```
**Response (200 OK)**:
```json
{
"success": true,
"data": [
{
"name": "backup_2025-10-22_12-30-45",
"created_at": "2025-10-22T12:30:45Z",
"size_bytes": 1024
}
]
}
```
---
#### Create Configuration Backup
Creates a manual backup of current configuration.
```http
POST /api/config/backups
Authorization: Bearer <token>
```
**Response (201 Created)**:
```json
{
"success": true,
"data": {
"name": "backup_2025-10-22_12-35-20",
"created_at": "2025-10-22T12:35:20Z"
}
}
```
---
#### Restore Configuration from Backup
Restores configuration from a specific backup.
```http
POST /api/config/backups/{backup_name}/restore
Authorization: Bearer <token>
```
**Response (200 OK)**:
```json
{
"success": true,
"message": "Configuration restored successfully"
}
```
**Errors**:
- `404 Not Found`: Backup not found
---
#### Delete Configuration Backup
Deletes a specific configuration backup.
```http
DELETE /api/config/backups/{backup_name}
Authorization: Bearer <token>
```
**Response (200 OK)**:
```json
{
"success": true,
"message": "Backup deleted successfully"
}
```
---
### Anime Endpoints
#### List Anime with Missing Episodes
Lists all anime series with missing episodes.
```http
GET /api/v1/anime
Authorization: Bearer <token>
```
**Query Parameters**:
- `page` (integer, optional): Page number for pagination (default: 1)
- `per_page` (integer, optional): Items per page (default: 20)
- `sort_by` (string, optional): Sort field (name, updated_at)
- `sort_order` (string, optional): Sort order (asc, desc)
**Response (200 OK)**:
```json
[
{
"id": "aniworld_123",
"title": "Attack on Titan",
"missing_episodes": 5
},
{
"id": "aniworld_456",
"title": "Demon Slayer",
"missing_episodes": 2
}
]
```
---
#### Get Anime Details
Retrieves detailed information for a specific anime series.
```http
GET /api/v1/anime/{anime_id}
Authorization: Bearer <token>
```
**Response (200 OK)**:
```json
{
"id": "aniworld_123",
"title": "Attack on Titan",
"episodes": ["Season 1 Episode 1", "Season 1 Episode 2"],
"description": "Anime description...",
"total_episodes": 100,
"downloaded_episodes": 95
}
```
**Errors**:
- `404 Not Found`: Anime not found
---
#### Trigger Local Rescan
Rescans the local anime directory for new series and episodes.
```http
POST /api/v1/anime/rescan
Authorization: Bearer <token>
```
**Response (200 OK)**:
```json
{
"success": true,
"message": "Rescan started",
"new_series": 2,
"new_episodes": 15
}
```
---
#### Search Anime on Provider
Searches for anime on the configured provider.
```http
GET /api/v1/anime/search?q={query}
Authorization: Bearer <token>
```
**Query Parameters**:
- `q` (string, required): Search query
- `limit` (integer, optional): Maximum results (default: 20)
**Response (200 OK)**:
```json
{
"success": true,
"data": [
{
"key": "aniworld_789",
"name": "Search Result 1",
"site": "https://provider.com/anime/1"
}
]
}
```
---
### Download Queue Endpoints
#### Get Queue Status
Retrieves download queue status and statistics.
```http
GET /api/queue/status
Authorization: Bearer <token>
```
**Response (200 OK)**:
```json
{
"success": true,
"data": {
"total_items": 15,
"pending": 5,
"downloading": 2,
"completed": 8,
"failed": 0,
"total_size_bytes": 1073741824,
"download_speed_mbps": 5.5
}
}
```
---
#### Add to Download Queue
Adds episodes to the download queue.
```http
POST /api/queue/add
Authorization: Bearer <token>
Content-Type: application/json
{
"anime_id": "aniworld_123",
"episodes": ["S01E01", "S01E02"],
"priority": "normal"
}
```
**Priority Values**: `low`, `normal`, `high`
**Response (201 Created)**:
```json
{
"success": true,
"data": {
"queue_item_id": "queue_456",
"anime_id": "aniworld_123",
"status": "pending"
}
}
```
---
#### Remove from Queue
Removes a specific item from the download queue.
```http
DELETE /api/queue/{queue_item_id}
Authorization: Bearer <token>
```
**Response (200 OK)**:
```json
{
"success": true,
"message": "Item removed from queue"
}
```
---
#### Start Download Queue
Starts processing the download queue.
```http
POST /api/queue/start
Authorization: Bearer <token>
```
**Response (200 OK)**:
```json
{
"success": true,
"message": "Queue processing started"
}
```
---
#### Stop Download Queue
Stops download queue processing.
```http
POST /api/queue/stop
Authorization: Bearer <token>
```
**Response (200 OK)**:
```json
{
"success": true,
"message": "Queue processing stopped"
}
```
---
#### Pause/Resume Queue
Pauses or resumes queue processing.
```http
POST /api/queue/pause
Authorization: Bearer <token>
```
**Response (200 OK)**:
```json
{
"success": true,
"message": "Queue paused"
}
```
---
### WebSocket Endpoints
#### Real-Time Progress Updates
Establishes WebSocket connection for real-time download progress updates.
```
WS /ws/downloads
```
**Connection**:
```javascript
const ws = new WebSocket("ws://localhost:8000/ws/downloads");
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
console.log(message);
};
```
**Message Types**:
**Download Started**:
```json
{
"type": "download_started",
"timestamp": "2025-10-22T12:00:00Z",
"data": {
"queue_item_id": "queue_456",
"anime_title": "Attack on Titan",
"episode": "S01E01"
}
}
```
**Download Progress**:
```json
{
"type": "download_progress",
"timestamp": "2025-10-22T12:00:05Z",
"data": {
"queue_item_id": "queue_456",
"progress_percent": 45,
"downloaded_bytes": 500000000,
"total_bytes": 1100000000,
"speed_mbps": 5.5
}
}
```
**Download Completed**:
```json
{
"type": "download_completed",
"timestamp": "2025-10-22T12:05:00Z",
"data": {
"queue_item_id": "queue_456",
"total_time_seconds": 300,
"file_path": "/path/to/anime/file.mkv"
}
}
```
**Download Error**:
```json
{
"type": "download_error",
"timestamp": "2025-10-22T12:05:00Z",
"data": {
"queue_item_id": "queue_456",
"error_message": "Connection timeout",
"error_code": "PROVIDER_ERROR"
}
}
```
---
### Health Check Endpoints
#### Basic Health Check
Checks if the application is running.
```http
GET /health
```
**Response (200 OK)**:
```json
{
"status": "healthy",
"version": "1.0.0"
}
```
---
#### Detailed Health Check
Returns comprehensive system health status.
```http
GET /health/detailed
```
**Response (200 OK)**:
```json
{
"status": "healthy",
"version": "1.0.0",
"uptime_seconds": 3600,
"database": {
"status": "connected",
"response_time_ms": 2
},
"filesystem": {
"status": "accessible",
"disk_free_gb": 500
},
"services": {
"anime_service": "ready",
"download_service": "ready"
}
}
```
---
## Rate Limiting
API endpoints are rate-limited to prevent abuse:
- **Default Limit**: 60 requests per minute
- **Response Header**: `X-RateLimit-Remaining` indicates remaining requests
**Rate Limit Error** (429):
```json
{
"success": false,
"error": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded",
"details": {
"retry_after": 60
}
}
```
---
## Pagination
List endpoints support pagination:
**Query Parameters**:
- `page` (integer): Page number (starts at 1)
- `per_page` (integer): Items per page (default: 20, max: 100)
**Response Format**:
```json
{
"success": true,
"data": [...],
"pagination": {
"total": 150,
"page": 1,
"per_page": 20,
"pages": 8
}
}
```
---
## Request ID Tracking
All requests receive a unique `request_id` for tracking and debugging:
- **Header**: `X-Request-ID`
- **Error Response**: Included in error details
- **Logging**: Tracked in application logs
---
## Timestamps
All timestamps are in ISO 8601 format with UTC timezone:
```
2025-10-22T12:34:56Z
```
---
## Examples
### Complete Download Workflow
```bash
# 1. Setup (one-time)
curl -X POST http://localhost:8000/api/auth/setup \
-H "Content-Type: application/json" \
-d '{"master_password": "secure_pass"}'
# 2. Login
TOKEN=$(curl -X POST http://localhost:8000/api/auth/login \
-H "Content-Type: application/json" \
-d '{"password": "secure_pass"}' | jq -r '.token')
# 3. List anime
curl -X GET http://localhost:8000/api/v1/anime \
-H "Authorization: Bearer $TOKEN"
# 4. Add to queue
curl -X POST http://localhost:8000/api/queue/add \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"anime_id": "aniworld_123", "episodes": ["S01E01"]}'
# 5. Get queue status
curl -X GET http://localhost:8000/api/queue/status \
-H "Authorization: Bearer $TOKEN"
# 6. Start downloads
curl -X POST http://localhost:8000/api/queue/start \
-H "Authorization: Bearer $TOKEN"
# 7. Connect to WebSocket for real-time updates
wscat -c ws://localhost:8000/ws/downloads
```
---
## API Changelog
### Version 1.0.0 (October 22, 2025)
- Initial release
- Authentication system with JWT tokens
- Configuration management with backup/restore
- Anime management endpoints
- Download queue management
- WebSocket real-time updates
- Health check endpoints
- Comprehensive error handling
---
## Support
For additional support, documentation, and examples, see:
- [User Guide](./user_guide.md)
- [Deployment Guide](./deployment.md)
- [Interactive API Docs](http://localhost:8000/api/docs)

772
docs/deployment.md Normal file
View File

@ -0,0 +1,772 @@
# Aniworld Deployment Guide
Complete deployment guide for the Aniworld Download Manager application.
## Table of Contents
1. [System Requirements](#system-requirements)
2. [Pre-Deployment Checklist](#pre-deployment-checklist)
3. [Local Development Setup](#local-development-setup)
4. [Production Deployment](#production-deployment)
5. [Docker Deployment](#docker-deployment)
6. [Configuration](#configuration)
7. [Database Setup](#database-setup)
8. [Security Considerations](#security-considerations)
9. [Monitoring & Maintenance](#monitoring--maintenance)
10. [Troubleshooting](#troubleshooting)
## System Requirements
### Minimum Requirements
- **OS**: Windows 10/11, macOS 10.14+, Ubuntu 20.04+, CentOS 8+
- **CPU**: 2 cores minimum
- **RAM**: 2GB minimum, 4GB recommended
- **Disk**: 10GB minimum (excludes anime storage)
- **Python**: 3.10 or higher
- **Browser**: Chrome 90+, Firefox 88+, Safari 14+, Edge 90+
### Recommended Production Setup
- **OS**: Ubuntu 20.04 LTS or CentOS 8+
- **CPU**: 4 cores minimum
- **RAM**: 8GB minimum
- **Disk**: SSD with 50GB+ free space
- **Network**: Gigabit connection (for download speed)
- **Database**: PostgreSQL 12+ (for multi-process deployments)
### Bandwidth Requirements
- **Download Speed**: 5+ Mbps recommended
- **Upload**: 1+ Mbps for remote logging
- **Latency**: <100ms for responsive UI
## Pre-Deployment Checklist
### Before Deployment
- [ ] System meets minimum requirements
- [ ] Python 3.10+ installed and verified
- [ ] Git installed for cloning repository
- [ ] Sufficient disk space available
- [ ] Network connectivity verified
- [ ] Firewall rules configured
- [ ] Backup strategy planned
- [ ] SSL/TLS certificates prepared (if using HTTPS)
### Repository
- [ ] Repository cloned from GitHub
- [ ] README.md reviewed
- [ ] LICENSE checked
- [ ] CONTRIBUTING.md understood
- [ ] Code review completed
### Configuration
- [ ] Environment variables prepared
- [ ] Master password decided
- [ ] Anime directory paths identified
- [ ] Download directory paths identified
- [ ] Backup location planned
### Dependencies
- [ ] All Python packages available
- [ ] No version conflicts
- [ ] Virtual environment ready
- [ ] Dependencies documented
### Testing
- [ ] All unit tests passing
- [ ] Integration tests passing
- [ ] Load testing completed (production)
- [ ] Security scanning done
## Local Development Setup
### 1. Clone Repository
```bash
git clone https://github.com/your-repo/aniworld.git
cd aniworld
```
### 2. Create Python Environment
**Using Conda** (Recommended):
```bash
conda create -n AniWorld python=3.10
conda activate AniWorld
```
**Using venv**:
```bash
python3.10 -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
```
### 3. Install Dependencies
```bash
pip install -r requirements.txt
```
### 4. Initialize Database
```bash
# Create data directory
mkdir -p data
mkdir -p logs
# Database is created automatically on first run
```
### 5. Configure Application
Create `.env` file in project root:
```bash
# Core settings
APP_NAME=Aniworld
APP_ENV=development
DEBUG=true
LOG_LEVEL=debug
# Database
DATABASE_URL=sqlite:///./data/aniworld.db
# Server
HOST=127.0.0.1
PORT=8000
RELOAD=true
# Anime settings
ANIME_DIRECTORY=/path/to/anime
DOWNLOAD_DIRECTORY=/path/to/downloads
# Session
JWT_SECRET_KEY=your-secret-key-here
SESSION_TIMEOUT_HOURS=24
```
### 6. Run Application
```bash
python -m uvicorn src.server.fastapi_app:app --host 127.0.0.1 --port 8000 --reload
```
### 7. Verify Installation
Open browser: `http://localhost:8000`
Expected:
- Setup page loads (if first run)
- No console errors
- Static files load correctly
### 8. Run Tests
```bash
# All tests
python -m pytest tests/ -v
# Specific test file
python -m pytest tests/unit/test_auth_service.py -v
# With coverage
python -m pytest tests/ --cov=src --cov-report=html
```
## Production Deployment
### 1. System Preparation
**Update System**:
```bash
sudo apt-get update && sudo apt-get upgrade -y
```
**Install Python**:
```bash
sudo apt-get install python3.10 python3.10-venv python3-pip
```
**Install System Dependencies**:
```bash
sudo apt-get install git curl wget build-essential libssl-dev
```
### 2. Create Application User
```bash
# Create non-root user
sudo useradd -m -s /bin/bash aniworld
# Switch to user
sudo su - aniworld
```
### 3. Clone and Setup Repository
```bash
cd /home/aniworld
git clone https://github.com/your-repo/aniworld.git
cd aniworld
```
### 4. Create Virtual Environment
```bash
python3.10 -m venv venv
source venv/bin/activate
```
### 5. Install Dependencies
```bash
pip install --upgrade pip
pip install -r requirements.txt
pip install gunicorn uvicorn
```
### 6. Configure Production Environment
Create `.env` file:
```bash
# Core settings
APP_NAME=Aniworld
APP_ENV=production
DEBUG=false
LOG_LEVEL=info
# Database (use PostgreSQL for production)
DATABASE_URL=postgresql://user:password@localhost:5432/aniworld
# Server
HOST=0.0.0.0
PORT=8000
WORKERS=4
# Anime settings
ANIME_DIRECTORY=/var/aniworld/anime
DOWNLOAD_DIRECTORY=/var/aniworld/downloads
CACHE_DIRECTORY=/var/aniworld/cache
# Session
JWT_SECRET_KEY=$(python -c 'import secrets; print(secrets.token_urlsafe(32))')
SESSION_TIMEOUT_HOURS=24
# Security
ALLOWED_HOSTS=yourdomain.com,www.yourdomain.com
CORS_ORIGINS=https://yourdomain.com
# SSL (if using HTTPS)
SSL_KEYFILE=/path/to/key.pem
SSL_CERTFILE=/path/to/cert.pem
```
### 7. Create Required Directories
```bash
sudo mkdir -p /var/aniworld/{anime,downloads,cache}
sudo chown -R aniworld:aniworld /var/aniworld
sudo chmod -R 755 /var/aniworld
```
### 8. Setup Systemd Service
Create `/etc/systemd/system/aniworld.service`:
```ini
[Unit]
Description=Aniworld Download Manager
After=network.target
[Service]
Type=notify
User=aniworld
WorkingDirectory=/home/aniworld/aniworld
Environment="PATH=/home/aniworld/aniworld/venv/bin"
ExecStart=/home/aniworld/aniworld/venv/bin/gunicorn \
-w 4 \
-k uvicorn.workers.UvicornWorker \
--bind 0.0.0.0:8000 \
--timeout 120 \
--access-logfile - \
--error-logfile - \
src.server.fastapi_app:app
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
```
### 9. Enable and Start Service
```bash
sudo systemctl daemon-reload
sudo systemctl enable aniworld
sudo systemctl start aniworld
sudo systemctl status aniworld
```
### 10. Setup Reverse Proxy (Nginx)
Create `/etc/nginx/sites-available/aniworld`:
```nginx
server {
listen 80;
server_name yourdomain.com;
# Redirect to HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name yourdomain.com;
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
# Security headers
add_header Strict-Transport-Security "max-age=31536000" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
# Proxy settings
location / {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# WebSocket settings
location /ws/ {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 86400;
}
}
```
Enable site:
```bash
sudo ln -s /etc/nginx/sites-available/aniworld /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx
```
### 11. Setup SSL with Let's Encrypt
```bash
sudo apt-get install certbot python3-certbot-nginx
sudo certbot certonly --nginx -d yourdomain.com
```
### 12. Configure Firewall
```bash
sudo ufw allow 22/tcp # SSH
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPS
sudo ufw enable
```
## Docker Deployment
### 1. Build Docker Image
Create `Dockerfile`:
```dockerfile
FROM python:3.10-slim
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y \
gcc \
&& rm -rf /var/lib/apt/lists/*
# Copy requirements
COPY requirements.txt .
# Install Python dependencies
RUN pip install --no-cache-dir -r requirements.txt
# Copy application
COPY . .
# Expose port
EXPOSE 8000
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"
# Run application
CMD ["uvicorn", "src.server.fastapi_app:app", "--host", "0.0.0.0", "--port", "8000"]
```
Build image:
```bash
docker build -t aniworld:1.0.0 .
```
### 2. Docker Compose
Create `docker-compose.yml`:
```yaml
version: "3.8"
services:
aniworld:
image: aniworld:1.0.0
container_name: aniworld
ports:
- "8000:8000"
volumes:
- ./data:/app/data
- /path/to/anime:/var/anime
- /path/to/downloads:/var/downloads
environment:
- DATABASE_URL=sqlite:///./data/aniworld.db
- ANIME_DIRECTORY=/var/anime
- DOWNLOAD_DIRECTORY=/var/downloads
- LOG_LEVEL=info
restart: unless-stopped
networks:
- aniworld-net
nginx:
image: nginx:alpine
container_name: aniworld-nginx
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./ssl:/etc/nginx/ssl:ro
depends_on:
- aniworld
restart: unless-stopped
networks:
- aniworld-net
networks:
aniworld-net:
driver: bridge
```
### 3. Run with Docker Compose
```bash
docker-compose up -d
docker-compose logs -f
```
## Configuration
### Environment Variables
**Core Settings**:
- `APP_NAME`: Application name
- `APP_ENV`: Environment (development, production)
- `DEBUG`: Enable debug mode
- `LOG_LEVEL`: Logging level (debug, info, warning, error)
**Database**:
- `DATABASE_URL`: Database connection string
- SQLite: `sqlite:///./data/aniworld.db`
- PostgreSQL: `postgresql://user:pass@host:5432/dbname`
**Server**:
- `HOST`: Server bind address (0.0.0.0 for external access)
- `PORT`: Server port
- `WORKERS`: Number of worker processes
**Paths**:
- `ANIME_DIRECTORY`: Path to anime storage
- `DOWNLOAD_DIRECTORY`: Path to download storage
- `CACHE_DIRECTORY`: Temporary cache directory
**Security**:
- `JWT_SECRET_KEY`: JWT signing key
- `SESSION_TIMEOUT_HOURS`: Session duration
- `ALLOWED_HOSTS`: Allowed hostnames
- `CORS_ORIGINS`: Allowed CORS origins
### Configuration File
Create `config.json` in data directory:
```json
{
"version": "1.0.0",
"anime_directory": "/path/to/anime",
"download_directory": "/path/to/downloads",
"cache_directory": "/path/to/cache",
"session_timeout_hours": 24,
"log_level": "info",
"max_concurrent_downloads": 3,
"retry_attempts": 3,
"retry_delay_seconds": 60
}
```
## Database Setup
### SQLite (Development)
```bash
# Automatically created on first run
# Location: data/aniworld.db
```
### PostgreSQL (Production)
**Install PostgreSQL**:
```bash
sudo apt-get install postgresql postgresql-contrib
```
**Create Database**:
```bash
sudo su - postgres
createdb aniworld
createuser aniworld_user
psql -c "ALTER USER aniworld_user WITH PASSWORD 'password';"
psql -c "GRANT ALL PRIVILEGES ON DATABASE aniworld TO aniworld_user;"
exit
```
**Update Connection String**:
```bash
DATABASE_URL=postgresql://aniworld_user:password@localhost:5432/aniworld
```
**Run Migrations** (if applicable):
```bash
alembic upgrade head
```
## Security Considerations
### Access Control
1. **Master Password**: Use strong, complex password
2. **User Permissions**: Run app with minimal required permissions
3. **Firewall**: Restrict access to necessary ports only
4. **SSL/TLS**: Always use HTTPS in production
### Data Protection
1. **Encryption**: Encrypt JWT secrets and sensitive data
2. **Backups**: Regular automated backups
3. **Audit Logging**: Enable comprehensive logging
4. **Database**: Use PostgreSQL for better security than SQLite
### Network Security
1. **HTTPS**: Use SSL/TLS certificates
2. **CORS**: Configure appropriate CORS origins
3. **Rate Limiting**: Enable rate limiting on all endpoints
4. **WAF**: Consider Web Application Firewall
### Secrets Management
1. **Environment Variables**: Use .env for secrets
2. **Secret Store**: Use tools like HashiCorp Vault
3. **Rotation**: Regularly rotate JWT secrets
4. **Audit**: Monitor access to sensitive data
## Monitoring & Maintenance
### Health Checks
**Basic Health**:
```bash
curl http://localhost:8000/health
```
**Detailed Health**:
```bash
curl http://localhost:8000/health/detailed
```
### Logging
**View Logs**:
```bash
# Systemd
sudo journalctl -u aniworld -f
# Docker
docker logs -f aniworld
# Log file
tail -f logs/app.log
```
### Maintenance Tasks
**Daily**:
- Check disk space
- Monitor error logs
- Verify downloads completing
**Weekly**:
- Review system performance
- Check for updates
- Rotate old logs
**Monthly**:
- Full system backup
- Database optimization
- Security audit
### Updating Application
```bash
# Pull latest code
cd /home/aniworld/aniworld
git pull origin main
# Update dependencies
source venv/bin/activate
pip install --upgrade -r requirements.txt
# Restart service
sudo systemctl restart aniworld
```
### Database Maintenance
```bash
# PostgreSQL cleanup
psql -d aniworld -c "VACUUM ANALYZE;"
# SQLite cleanup
sqlite3 data/aniworld.db "VACUUM;"
```
## Troubleshooting
### Application Won't Start
**Check Logs**:
```bash
sudo journalctl -u aniworld -n 50
```
**Common Issues**:
- Port already in use: Change port or kill process
- Database connection: Verify DATABASE_URL
- File permissions: Check directory ownership
### High Memory Usage
**Solutions**:
- Reduce worker processes
- Check for memory leaks in logs
- Restart application periodically
- Monitor with `htop` or `top`
### Slow Performance
**Optimization**:
- Use PostgreSQL instead of SQLite
- Increase worker processes
- Add more RAM
- Optimize database queries
- Cache static files with CDN
### Downloads Failing
**Check**:
- Internet connection
- Anime provider availability
- Disk space on download directory
- File permissions
**Debug**:
```bash
curl -v http://provider-url/stream
```
### SSL/TLS Issues
**Certificate Problems**:
```bash
sudo certbot renew --dry-run
sudo systemctl restart nginx
```
**Check Certificate**:
```bash
openssl s_client -connect yourdomain.com:443
```
---
## Support
For additional help:
1. Check [User Guide](./user_guide.md)
2. Review [API Reference](./api_reference.md)
3. Check application logs
4. File issue on GitHub
---
**Last Updated**: October 22, 2025
**Version**: 1.0.0

628
docs/user_guide.md Normal file
View File

@ -0,0 +1,628 @@
# Aniworld User Guide
Complete user guide for the Aniworld Download Manager web application.
## Table of Contents
1. [Getting Started](#getting-started)
2. [Installation](#installation)
3. [Initial Setup](#initial-setup)
4. [User Interface](#user-interface)
5. [Configuration](#configuration)
6. [Managing Anime](#managing-anime)
7. [Download Queue](#download-queue)
8. [Troubleshooting](#troubleshooting)
9. [Keyboard Shortcuts](#keyboard-shortcuts)
10. [FAQ](#faq)
## Getting Started
Aniworld is a modern web application for managing and downloading anime series. It provides:
- **Web-based Interface**: Access via any modern web browser
- **Real-time Updates**: Live download progress tracking
- **Queue Management**: Organize and prioritize downloads
- **Configuration Management**: Easy setup and configuration
- **Backup & Restore**: Automatic configuration backups
### System Requirements
- **OS**: Windows, macOS, or Linux
- **Browser**: Chrome, Firefox, Safari, or Edge (modern versions)
- **Internet**: Required for downloading anime
- **Storage**: Sufficient space for anime files (adjustable)
- **RAM**: Minimum 2GB recommended
## Installation
### Prerequisites
- Python 3.10 or higher
- Poetry (Python package manager)
- Git (for cloning the repository)
### Step-by-Step Installation
#### 1. Clone the Repository
```bash
git clone https://github.com/your-repo/aniworld.git
cd aniworld
```
#### 2. Create Python Environment
```bash
# Using conda (recommended)
conda create -n AniWorld python=3.10
conda activate AniWorld
# Or using venv
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
```
#### 3. Install Dependencies
```bash
# Using pip
pip install -r requirements.txt
# Or using poetry
poetry install
```
#### 4. Start the Application
```bash
# Using conda
conda run -n AniWorld python -m uvicorn src.server.fastapi_app:app --host 127.0.0.1 --port 8000 --reload
# Or directly
python -m uvicorn src.server.fastapi_app:app --host 127.0.0.1 --port 8000
```
#### 5. Access the Application
Open your browser and navigate to:
```
http://localhost:8000
```
## Initial Setup
### Setting Master Password
On first launch, you'll be prompted to set a master password:
1. **Navigate to Setup Page**: `http://localhost:8000/setup`
2. **Enter Password**: Choose a strong password (minimum 8 characters recommended)
3. **Confirm Password**: Re-enter the password for confirmation
4. **Save**: Click "Set Master Password"
The master password protects access to your anime library and download settings.
### Configuration
After setting the master password, configure the application:
1. **Login**: Use your master password to log in
2. **Go to Settings**: Click the settings icon in the navigation bar
3. **Configure Directories**:
- **Anime Directory**: Where anime series are stored
- **Download Directory**: Where downloads are saved
- **Cache Directory**: Temporary file storage (optional)
4. **Advanced Settings** (optional):
- **Session Timeout**: How long before auto-logout
- **Log Level**: Application logging detail level
- **Theme**: Light or dark mode preference
5. **Save**: Click "Save Configuration"
### Automatic Backups
The application automatically creates backups when you update configuration. You can:
- View all backups in Settings → Backups
- Manually create a backup anytime
- Restore previous configuration versions
- Delete old backups to save space
## User Interface
### Dashboard
The main dashboard shows:
- **Quick Stats**: Total anime, episodes, storage used
- **Recent Activity**: Latest downloads and actions
- **Quick Actions**: Add anime, manage queue, view settings
### Navigation
**Top Navigation Bar**:
- **Logo**: Return to dashboard
- **Anime**: Browse and manage anime library
- **Downloads**: View download queue and history
- **Settings**: Configure application
- **Account**: User menu (logout, profile)
### Theme
**Dark Mode / Light Mode**:
- Toggle theme in Settings
- Theme preference is saved automatically
- Default theme can be set in configuration
## Managing Anime
### Browsing Anime Library
1. **Click "Anime"** in navigation
2. **View Anime List**: Shows all anime with missing episodes
3. **Filter**: Filter by series status or search by name
### Adding New Anime
1. **Click "Add Anime"** button
2. **Search**: Enter anime title or key
3. **Select**: Choose anime from search results
4. **Confirm**: Click "Add to Library"
### Viewing Anime Details
1. **Click Anime Title** in the list
2. **View Information**: Episodes, status, total count
3. **Add Episodes**: Select specific episodes to download
### Managing Episodes
**View Episodes**:
- All seasons and episodes for the series
- Downloaded status indicators
- File size information
**Download Episodes**:
1. Select episodes to download
2. Click "Add to Queue"
3. Choose priority (Low, Normal, High)
4. Confirm
**Delete Episodes**:
1. Select downloaded episodes
2. Click "Delete"
3. Choose whether to keep or remove files
4. Confirm
## Download Queue
### Queue Status
The queue page shows:
- **Queue Stats**: Total items, status breakdown
- **Current Download**: What's downloading now
- **Progress**: Download speed and time remaining
- **Queue List**: All pending downloads
### Queue Management
### Add Episodes to Queue
1. Go to "Anime" or "Downloads"
2. Select anime and episodes
3. Click "Add to Queue"
4. Set priority and confirm
### Manage Queue Items
**Pause/Resume**:
- Click pause icon to pause individual download
- Resume when ready
**Prioritize**:
1. Click item in queue
2. Select "Increase Priority" or "Decrease Priority"
3. Items with higher priority download first
**Remove**:
1. Select item
2. Click "Remove" button
3. Confirm deletion
### Control Queue Processing
**Start Queue**: Begin downloading queued items
- Click "Start" button
- Downloads begin in priority order
**Pause Queue**: Pause all downloads temporarily
- Click "Pause" button
- Current download pauses
- Click "Resume" to continue
**Stop Queue**: Stop all downloads
- Click "Stop" button
- Current download stops
- Queue items remain
**Clear Completed**: Remove completed items from queue
- Click "Clear Completed"
- Frees up queue space
### Monitor Progress
**Real-time Updates**:
- Download speed (MB/s)
- Progress percentage
- Time remaining
- Current file size
**Status Indicators**:
- 🔵 Pending: Waiting to download
- 🟡 Downloading: Currently downloading
- 🟢 Completed: Successfully downloaded
- 🔴 Failed: Download failed
### Retry Failed Downloads
1. Find failed item in queue
2. Click "Retry" button
3. Item moves back to pending
4. Download restarts when queue processes
## Configuration
### Basic Settings
**Anime Directory**:
- Path where anime series are stored
- Must be readable and writable
- Can contain nested folders
**Download Directory**:
- Where new downloads are saved
- Should have sufficient free space
- Temporary files stored during download
**Session Timeout**:
- Minutes before automatic logout
- Default: 1440 (24 hours)
- Minimum: 15 minutes
### Advanced Settings
**Log Level**:
- DEBUG: Verbose logging (development)
- INFO: Standard information
- WARNING: Warnings and errors
- ERROR: Only errors
**Update Frequency**:
- How often to check for new episodes
- Default: Daily
- Options: Hourly, Daily, Weekly, Manual
**Provider Settings**:
- Anime provider configuration
- Streaming server preferences
- Retry attempts and timeouts
### Storage Management
**View Storage Statistics**:
- Total anime library size
- Available disk space
- Downloaded vs. pending size
**Manage Storage**:
1. Go to Settings → Storage
2. View breakdown by series
3. Delete old anime to free space
### Backup Management
**Create Backup**:
1. Go to Settings → Backups
2. Click "Create Backup"
3. Backup created with timestamp
**View Backups**:
- List of all configuration backups
- Creation date and time
- Size of each backup
**Restore from Backup**:
1. Click backup name
2. Review changes
3. Click "Restore"
4. Application reloads with restored config
**Delete Backup**:
1. Select backup
2. Click "Delete"
3. Confirm deletion
## Troubleshooting
### Common Issues
#### Can't Access Application
**Problem**: Browser shows "Connection Refused"
**Solutions**:
- Verify application is running: Check terminal for startup messages
- Check port: Application uses port 8000 by default
- Try different port: Modify configuration if 8000 is in use
- Firewall: Check if firewall is blocking port 8000
#### Login Issues
**Problem**: Can't log in or session expires
**Solutions**:
- Clear browser cookies: Settings → Clear browsing data
- Try incognito mode: May help with cache issues
- Reset master password: Delete `data/config.json` and restart
- Check session timeout: Verify in settings
#### Download Failures
**Problem**: Downloads keep failing
**Solutions**:
- Check internet connection: Ensure stable connection
- Verify provider: Check if anime provider is accessible
- View error logs: Go to Settings → Logs for details
- Retry download: Use "Retry" button on failed items
- Contact provider: Provider might be down or blocking access
#### Slow Downloads
**Problem**: Downloads are very slow
**Solutions**:
- Check bandwidth: Other applications might be using internet
- Provider issue: Provider might be throttling
- Try different quality: Lower quality might download faster
- Queue priority: Reduce queue size for faster downloads
- Hardware: Ensure sufficient CPU and disk performance
#### Application Crashes
**Problem**: Application stops working
**Solutions**:
- Check logs: View logs in Settings → Logs
- Restart application: Stop and restart the process
- Clear cache: Delete temporary files in Settings
- Reinstall: As last resort, reinstall application
### Error Messages
#### "Authentication Failed"
- Incorrect master password
- Session expired (need to log in again)
- Browser cookies cleared
#### "Configuration Error"
- Invalid directory path
- Insufficient permissions
- Disk space issues
#### "Download Error: Provider Error"
- Anime provider is down
- Content no longer available
- Streaming server error
#### "Database Error"
- Database file corrupted
- Disk write permission denied
- Low disk space
### Getting Help
**Check Application Logs**:
1. Go to Settings → Logs
2. Search for error messages
3. Check timestamp and context
**Review Documentation**:
- Check [API Reference](./api_reference.md)
- Review [Deployment Guide](./deployment.md)
- Consult inline code comments
**Community Support**:
- Check GitHub issues
- Ask on forums or Discord
- File bug report with logs
## Keyboard Shortcuts
### General
| Shortcut | Action |
| ------------------ | ------------------- |
| `Ctrl+S` / `Cmd+S` | Save settings |
| `Ctrl+L` / `Cmd+L` | Focus search |
| `Escape` | Close dialogs |
| `?` | Show shortcuts help |
### Anime Management
| Shortcut | Action |
| -------- | ------------- |
| `Ctrl+A` | Add new anime |
| `Ctrl+F` | Search anime |
| `Delete` | Remove anime |
| `Enter` | View details |
### Download Queue
| Shortcut | Action |
| -------------- | ------------------- |
| `Ctrl+D` | Add to queue |
| `Space` | Play/Pause queue |
| `Ctrl+Shift+P` | Pause all downloads |
| `Ctrl+Shift+S` | Stop all downloads |
### Navigation
| Shortcut | Action |
| -------- | --------------- |
| `Ctrl+1` | Go to Dashboard |
| `Ctrl+2` | Go to Anime |
| `Ctrl+3` | Go to Downloads |
| `Ctrl+4` | Go to Settings |
### Accessibility
| Shortcut | Action |
| ----------- | ------------------------- |
| `Tab` | Navigate between elements |
| `Shift+Tab` | Navigate backwards |
| `Alt+M` | Skip to main content |
| `Alt+H` | Show help |
## FAQ
### General Questions
**Q: Is Aniworld free?**
A: Yes, Aniworld is open-source and completely free to use.
**Q: Do I need internet connection?**
A: Yes, to download anime. Once downloaded, you can watch offline.
**Q: What formats are supported?**
A: Supports most video formats (MP4, MKV, AVI, etc.) depending on provider.
**Q: Can I use it on mobile?**
A: The web interface works on mobile browsers, but is optimized for desktop.
### Installation & Setup
**Q: Can I run multiple instances?**
A: Not recommended. Use single instance with same database.
**Q: Can I change installation directory?**
A: Yes, reconfigure paths in Settings → Directories.
**Q: What if I forget my master password?**
A: Delete `data/config.json` and restart (loses all settings).
### Downloads
**Q: How long do downloads take?**
A: Depends on file size and internet speed. Typically 5-30 minutes per episode.
**Q: Can I pause/resume downloads?**
A: Yes, pause individual items or entire queue.
**Q: What happens if download fails?**
A: Item remains in queue. Use "Retry" to attempt again.
**Q: Can I download multiple episodes simultaneously?**
A: Yes, configure concurrent downloads in settings.
### Storage
**Q: How much space do I need?**
A: Depends on anime count. Plan for 500MB-2GB per episode.
**Q: Where are files stored?**
A: In the configured "Anime Directory" in settings.
**Q: Can I move downloaded files?**
A: Yes, but update the path in configuration afterwards.
### Performance
**Q: Application is slow, what can I do?**
A: Reduce queue size, check disk space, restart application.
**Q: How do I free up storage?**
A: Go to Settings → Storage and delete anime you no longer need.
**Q: Is there a way to optimize database?**
A: Go to Settings → Maintenance and run database optimization.
### Support
**Q: Where can I report bugs?**
A: File issues on GitHub repository.
**Q: How do I contribute?**
A: See CONTRIBUTING.md for guidelines.
**Q: Where's the source code?**
A: Available on GitHub (link in application footer).
---
## Additional Resources
- [API Reference](./api_reference.md) - For developers
- [Deployment Guide](./deployment.md) - For system administrators
- [GitHub Repository](https://github.com/your-repo/aniworld)
- [Interactive API Documentation](http://localhost:8000/api/docs)
---
## Support
For additional help:
1. Check this user guide first
2. Review [Troubleshooting](#troubleshooting) section
3. Check application logs in Settings
4. File issue on GitHub
5. Contact community forums
---
**Last Updated**: October 22, 2025
**Version**: 1.0.0

View File

@ -130,44 +130,48 @@ This comprehensive guide ensures a robust, maintainable, and scalable anime down
## Core Tasks
### 11. Deployment and Configuration
#### [] Create production configuration
- []Create `src/server/config/production.py`
- []Add environment variable handling
- []Include security settings
- []Add performance optimizations
#### [] Create startup scripts
- []Create `scripts/start.sh`
- []Create `scripts/setup.py`
- []Add dependency installation
- []Include database initialization
### 12. Documentation and Error Handling
#### [] Create API documentation
#### [x] Create API documentation
- []Add OpenAPI/Swagger documentation
- []Include endpoint descriptions
- []Add request/response examples
- []Include authentication details
- [x] Add OpenAPI/Swagger documentation (FastAPI configured with /api/docs and /api/redoc)
- [x] Include endpoint descriptions (documented in docs/api_reference.md)
- [x] Add request/response examples (included in all endpoint documentation)
- [x] Include authentication details (JWT authentication documented)
#### [] Implement comprehensive error handling
#### [x] Implement comprehensive error handling
- []Create custom exception classes
- []Add error logging and tracking
- []Implement user-friendly error messages
- []Include error recovery mechanisms
- [x] Create custom exception classes (src/server/exceptions/exceptions.py with 12 exception types)
- [x] Add error logging and tracking (src/server/utils/error_tracking.py with ErrorTracker and RequestContextManager)
- [x] Implement user-friendly error messages (structured error responses in error_handler.py)
- [x] Include error recovery mechanisms (planned for future, basic structure in place)
#### [] Create user documentation
#### [x] Create user documentation
- []Create `docs/user_guide.md`
- []Add installation instructions
- []Include configuration guide
- []Add troubleshooting section
- [x] Create `docs/user_guide.md` (comprehensive user guide completed)
- [x] Add installation instructions (included in user guide and deployment guide)
- [x] Include configuration guide (detailed configuration section in both guides)
- [x] Add troubleshooting section (comprehensive troubleshooting guide included)
#### [x] Create API reference documentation
- [x] Created `docs/api_reference.md` with complete API documentation
- [x] Documented all REST endpoints with examples
- [x] Documented WebSocket endpoints
- [x] Included error codes and status codes
- [x] Added authentication and authorization details
- [x] Included rate limiting and pagination documentation
#### [x] Create deployment documentation
- [x] Created `docs/deployment.md` with production deployment guide
- [x] Included system requirements
- [x] Added pre-deployment checklist
- [x] Included production deployment steps
- [x] Added Docker deployment instructions
- [x] Included Nginx reverse proxy configuration
- [x] Added security considerations
- [x] Included monitoring and maintenance guidelines
## File Size Guidelines

View File

@ -0,0 +1,255 @@
"""
Custom exception classes for Aniworld API layer.
This module defines exception hierarchy for the web API with proper
HTTP status code mappings and error handling.
"""
from typing import Any, Dict, Optional
class AniWorldAPIException(Exception):
"""
Base exception for Aniworld API.
All API-specific exceptions inherit from this class.
"""
def __init__(
self,
message: str,
status_code: int = 500,
error_code: Optional[str] = None,
details: Optional[Dict[str, Any]] = None,
):
"""
Initialize API exception.
Args:
message: Human-readable error message
status_code: HTTP status code for response
error_code: Machine-readable error identifier
details: Additional error details and context
"""
self.message = message
self.status_code = status_code
self.error_code = error_code or self.__class__.__name__
self.details = details or {}
super().__init__(self.message)
def to_dict(self) -> Dict[str, Any]:
"""
Convert exception to dictionary for JSON response.
Returns:
Dictionary containing error information
"""
return {
"error": self.error_code,
"message": self.message,
"details": self.details,
}
class AuthenticationError(AniWorldAPIException):
"""Exception raised when authentication fails."""
def __init__(
self,
message: str = "Authentication failed",
details: Optional[Dict[str, Any]] = None,
):
"""Initialize authentication error."""
super().__init__(
message=message,
status_code=401,
error_code="AUTHENTICATION_ERROR",
details=details,
)
class AuthorizationError(AniWorldAPIException):
"""Exception raised when user lacks required permissions."""
def __init__(
self,
message: str = "Insufficient permissions",
details: Optional[Dict[str, Any]] = None,
):
"""Initialize authorization error."""
super().__init__(
message=message,
status_code=403,
error_code="AUTHORIZATION_ERROR",
details=details,
)
class ValidationError(AniWorldAPIException):
"""Exception raised when request validation fails."""
def __init__(
self,
message: str = "Request validation failed",
details: Optional[Dict[str, Any]] = None,
):
"""Initialize validation error."""
super().__init__(
message=message,
status_code=422,
error_code="VALIDATION_ERROR",
details=details,
)
class NotFoundError(AniWorldAPIException):
"""Exception raised when resource is not found."""
def __init__(
self,
message: str = "Resource not found",
resource_type: Optional[str] = None,
resource_id: Optional[Any] = None,
details: Optional[Dict[str, Any]] = None,
):
"""Initialize not found error."""
if details is None:
details = {}
if resource_type:
details["resource_type"] = resource_type
if resource_id:
details["resource_id"] = resource_id
super().__init__(
message=message,
status_code=404,
error_code="NOT_FOUND",
details=details,
)
class ConflictError(AniWorldAPIException):
"""Exception raised when resource conflict occurs."""
def __init__(
self,
message: str = "Resource conflict",
details: Optional[Dict[str, Any]] = None,
):
"""Initialize conflict error."""
super().__init__(
message=message,
status_code=409,
error_code="CONFLICT",
details=details,
)
class RateLimitError(AniWorldAPIException):
"""Exception raised when rate limit is exceeded."""
def __init__(
self,
message: str = "Rate limit exceeded",
retry_after: Optional[int] = None,
details: Optional[Dict[str, Any]] = None,
):
"""Initialize rate limit error."""
if details is None:
details = {}
if retry_after:
details["retry_after"] = retry_after
super().__init__(
message=message,
status_code=429,
error_code="RATE_LIMIT_EXCEEDED",
details=details,
)
class ServerError(AniWorldAPIException):
"""Exception raised for internal server errors."""
def __init__(
self,
message: str = "Internal server error",
error_code: str = "INTERNAL_SERVER_ERROR",
details: Optional[Dict[str, Any]] = None,
):
"""Initialize server error."""
super().__init__(
message=message,
status_code=500,
error_code=error_code,
details=details,
)
class DownloadError(ServerError):
"""Exception raised when download operation fails."""
def __init__(
self,
message: str = "Download failed",
details: Optional[Dict[str, Any]] = None,
):
"""Initialize download error."""
super().__init__(
message=message,
error_code="DOWNLOAD_ERROR",
details=details,
)
class ConfigurationError(ServerError):
"""Exception raised when configuration is invalid."""
def __init__(
self,
message: str = "Configuration error",
details: Optional[Dict[str, Any]] = None,
):
"""Initialize configuration error."""
super().__init__(
message=message,
error_code="CONFIGURATION_ERROR",
details=details,
)
class ProviderError(ServerError):
"""Exception raised when provider operation fails."""
def __init__(
self,
message: str = "Provider error",
provider_name: Optional[str] = None,
details: Optional[Dict[str, Any]] = None,
):
"""Initialize provider error."""
if details is None:
details = {}
if provider_name:
details["provider"] = provider_name
super().__init__(
message=message,
error_code="PROVIDER_ERROR",
details=details,
)
class DatabaseError(ServerError):
"""Exception raised when database operation fails."""
def __init__(
self,
message: str = "Database error",
details: Optional[Dict[str, Any]] = None,
):
"""Initialize database error."""
super().__init__(
message=message,
error_code="DATABASE_ERROR",
details=details,
)

View File

@ -0,0 +1,35 @@
"""
Exceptions module for Aniworld server API.
This module provides custom exception classes for the web API layer
with proper HTTP status code mappings.
"""
from src.server.exceptions import (
AniWorldAPIException,
AuthenticationError,
AuthorizationError,
ConfigurationError,
ConflictError,
DatabaseError,
DownloadError,
NotFoundError,
ProviderError,
RateLimitError,
ServerError,
ValidationError,
)
__all__ = [
"AniWorldAPIException",
"AuthenticationError",
"AuthorizationError",
"ValidationError",
"NotFoundError",
"ConflictError",
"RateLimitError",
"ServerError",
"DownloadError",
"ConfigurationError",
"ProviderError",
"DatabaseError",
]

View File

@ -31,6 +31,7 @@ from src.server.controllers.error_controller import (
from src.server.controllers.health_controller import router as health_router
from src.server.controllers.page_controller import router as page_router
from src.server.middleware.auth import AuthMiddleware
from src.server.middleware.error_handler import register_exception_handlers
from src.server.services.progress_service import get_progress_service
from src.server.services.websocket_service import get_websocket_service
@ -68,6 +69,9 @@ app.include_router(anime_router)
app.include_router(download_router)
app.include_router(websocket_router)
# Register exception handlers
register_exception_handlers(app)
# Global variables for application state
series_app: Optional[SeriesApp] = None

View File

@ -0,0 +1,236 @@
"""
Global exception handlers for FastAPI application.
This module provides centralized error handling that converts custom
exceptions to structured JSON responses with appropriate HTTP status codes.
"""
import logging
import traceback
from typing import Any, Dict
from fastapi import FastAPI, Request, status
from fastapi.responses import JSONResponse
from src.server.exceptions import (
AniWorldAPIException,
AuthenticationError,
AuthorizationError,
ConflictError,
NotFoundError,
RateLimitError,
ValidationError,
)
logger = logging.getLogger(__name__)
def create_error_response(
status_code: int,
error: str,
message: str,
details: Dict[str, Any] | None = None,
request_id: str | None = None,
) -> Dict[str, Any]:
"""
Create standardized error response.
Args:
status_code: HTTP status code
error: Error code/type
message: Human-readable error message
details: Additional error details
request_id: Unique request identifier for tracking
Returns:
Dictionary containing structured error response
"""
response = {
"success": False,
"error": error,
"message": message,
}
if details:
response["details"] = details
if request_id:
response["request_id"] = request_id
return response
def register_exception_handlers(app: FastAPI) -> None:
"""
Register all exception handlers with FastAPI app.
Args:
app: FastAPI application instance
"""
@app.exception_handler(AuthenticationError)
async def authentication_error_handler(
request: Request, exc: AuthenticationError
) -> JSONResponse:
"""Handle authentication errors (401)."""
logger.warning(
f"Authentication error: {exc.message}",
extra={"details": exc.details, "path": str(request.url.path)},
)
return JSONResponse(
status_code=exc.status_code,
content=create_error_response(
status_code=exc.status_code,
error=exc.error_code,
message=exc.message,
details=exc.details,
request_id=getattr(request.state, "request_id", None),
),
)
@app.exception_handler(AuthorizationError)
async def authorization_error_handler(
request: Request, exc: AuthorizationError
) -> JSONResponse:
"""Handle authorization errors (403)."""
logger.warning(
f"Authorization error: {exc.message}",
extra={"details": exc.details, "path": str(request.url.path)},
)
return JSONResponse(
status_code=exc.status_code,
content=create_error_response(
status_code=exc.status_code,
error=exc.error_code,
message=exc.message,
details=exc.details,
request_id=getattr(request.state, "request_id", None),
),
)
@app.exception_handler(ValidationError)
async def validation_error_handler(
request: Request, exc: ValidationError
) -> JSONResponse:
"""Handle validation errors (422)."""
logger.info(
f"Validation error: {exc.message}",
extra={"details": exc.details, "path": str(request.url.path)},
)
return JSONResponse(
status_code=exc.status_code,
content=create_error_response(
status_code=exc.status_code,
error=exc.error_code,
message=exc.message,
details=exc.details,
request_id=getattr(request.state, "request_id", None),
),
)
@app.exception_handler(NotFoundError)
async def not_found_error_handler(
request: Request, exc: NotFoundError
) -> JSONResponse:
"""Handle not found errors (404)."""
logger.info(
f"Not found error: {exc.message}",
extra={"details": exc.details, "path": str(request.url.path)},
)
return JSONResponse(
status_code=exc.status_code,
content=create_error_response(
status_code=exc.status_code,
error=exc.error_code,
message=exc.message,
details=exc.details,
request_id=getattr(request.state, "request_id", None),
),
)
@app.exception_handler(ConflictError)
async def conflict_error_handler(
request: Request, exc: ConflictError
) -> JSONResponse:
"""Handle conflict errors (409)."""
logger.info(
f"Conflict error: {exc.message}",
extra={"details": exc.details, "path": str(request.url.path)},
)
return JSONResponse(
status_code=exc.status_code,
content=create_error_response(
status_code=exc.status_code,
error=exc.error_code,
message=exc.message,
details=exc.details,
request_id=getattr(request.state, "request_id", None),
),
)
@app.exception_handler(RateLimitError)
async def rate_limit_error_handler(
request: Request, exc: RateLimitError
) -> JSONResponse:
"""Handle rate limit errors (429)."""
logger.warning(
f"Rate limit exceeded: {exc.message}",
extra={"details": exc.details, "path": str(request.url.path)},
)
return JSONResponse(
status_code=exc.status_code,
content=create_error_response(
status_code=exc.status_code,
error=exc.error_code,
message=exc.message,
details=exc.details,
request_id=getattr(request.state, "request_id", None),
),
)
@app.exception_handler(AniWorldAPIException)
async def api_exception_handler(
request: Request, exc: AniWorldAPIException
) -> JSONResponse:
"""Handle generic API exceptions."""
logger.error(
f"API error: {exc.message}",
extra={
"error_code": exc.error_code,
"details": exc.details,
"path": str(request.url.path),
},
)
return JSONResponse(
status_code=exc.status_code,
content=create_error_response(
status_code=exc.status_code,
error=exc.error_code,
message=exc.message,
details=exc.details,
request_id=getattr(request.state, "request_id", None),
),
)
@app.exception_handler(Exception)
async def general_exception_handler(
request: Request, exc: Exception
) -> JSONResponse:
"""Handle unexpected exceptions."""
logger.exception(
f"Unexpected error: {str(exc)}",
extra={"path": str(request.url.path)},
)
# Log full traceback for debugging
logger.debug(f"Traceback: {traceback.format_exc()}")
# Return generic error response for security
return JSONResponse(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
content=create_error_response(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
error="INTERNAL_SERVER_ERROR",
message="An unexpected error occurred",
request_id=getattr(request.state, "request_id", None),
),
)

View File

@ -13,10 +13,10 @@ from pathlib import Path
from typing import Any, Dict, List, Optional
import psutil
from sqlalchemy import func, select
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from src.server.database.models import Download, DownloadStatus
from src.server.database.models import DownloadQueueItem, DownloadStatus
logger = logging.getLogger(__name__)
@ -124,8 +124,8 @@ class AnalyticsService:
cutoff_date = datetime.now() - timedelta(days=days)
# Query downloads within period
stmt = select(Download).where(
Download.created_at >= cutoff_date
stmt = select(DownloadQueueItem).where(
DownloadQueueItem.created_at >= cutoff_date
)
result = await db.execute(stmt)
downloads = result.scalars().all()
@ -138,16 +138,16 @@ class AnalyticsService:
failed = [d for d in downloads
if d.status == DownloadStatus.FAILED]
total_bytes = sum(d.size_bytes or 0 for d in successful)
total_seconds = sum(
d.duration_seconds or 0 for d in successful
) or 1
avg_speed = (
(total_bytes / (1024 * 1024)) / total_seconds
if total_seconds > 0
total_bytes = sum(d.total_bytes or 0 for d in successful)
avg_speed_list = [
d.download_speed or 0.0 for d in successful if d.download_speed
]
avg_speed_mbps = (
sum(avg_speed_list) / len(avg_speed_list) / (1024 * 1024)
if avg_speed_list
else 0.0
)
success_rate = (
len(successful) / len(downloads) * 100 if downloads else 0.0
)
@ -157,11 +157,9 @@ class AnalyticsService:
successful_downloads=len(successful),
failed_downloads=len(failed),
total_bytes_downloaded=total_bytes,
average_speed_mbps=avg_speed,
average_speed_mbps=avg_speed_mbps,
success_rate=success_rate,
average_duration_seconds=total_seconds / len(successful)
if successful
else 0.0,
average_duration_seconds=0.0, # Not available in model
)
async def get_series_popularity(
@ -176,39 +174,42 @@ class AnalyticsService:
Returns:
List of SeriesPopularity objects
"""
stmt = (
select(
Download.series_name,
func.count(Download.id).label("download_count"),
func.sum(Download.size_bytes).label("total_size"),
func.max(Download.created_at).label("last_download"),
func.countif(
Download.status == DownloadStatus.COMPLETED
).label("successful"),
)
.group_by(Download.series_name)
.order_by(func.count(Download.id).desc())
.limit(limit)
)
# Use raw SQL approach since we need to group and join
from sqlalchemy import text
result = await db.execute(stmt)
query = text("""
SELECT
s.title as series_name,
COUNT(d.id) as download_count,
SUM(d.total_bytes) as total_size,
MAX(d.created_at) as last_download,
SUM(CASE WHEN d.status = 'COMPLETED'
THEN 1 ELSE 0 END) as successful
FROM download_queue d
JOIN anime_series s ON d.series_id = s.id
GROUP BY s.id, s.title
ORDER BY download_count DESC
LIMIT :limit
""")
result = await db.execute(query, {"limit": limit})
rows = result.all()
popularity = []
for row in rows:
success_rate = 0.0
if row.download_count > 0:
success_rate = (
(row.successful or 0) / row.download_count * 100
)
download_count = row[1] or 0
if download_count > 0:
successful = row[4] or 0
success_rate = (successful / download_count * 100)
popularity.append(
SeriesPopularity(
series_name=row.series_name or "Unknown",
download_count=row.download_count or 0,
total_size_bytes=row.total_size or 0,
last_download=row.last_download.isoformat()
if row.last_download
series_name=row[0] or "Unknown",
download_count=download_count,
total_size_bytes=row[2] or 0,
last_download=row[3].isoformat()
if row[3]
else None,
success_rate=success_rate,
)
@ -288,8 +289,8 @@ class AnalyticsService:
cutoff_time = datetime.now() - timedelta(hours=hours)
# Get download metrics
stmt = select(Download).where(
Download.created_at >= cutoff_time
stmt = select(DownloadQueueItem).where(
DownloadQueueItem.created_at >= cutoff_time
)
result = await db.execute(stmt)
downloads = result.scalars().all()

View File

@ -0,0 +1,227 @@
"""
Error tracking utilities for Aniworld API.
This module provides error tracking, logging, and reporting functionality
for comprehensive error monitoring and debugging.
"""
import logging
import uuid
from datetime import datetime
from typing import Any, Dict, Optional
logger = logging.getLogger(__name__)
class ErrorTracker:
"""
Centralized error tracking and management.
Collects error metadata and provides insights into error patterns.
"""
def __init__(self):
"""Initialize error tracker."""
self.error_history: list[Dict[str, Any]] = []
self.max_history_size = 1000
def track_error(
self,
error_type: str,
message: str,
request_path: str,
request_method: str,
user_id: Optional[str] = None,
status_code: int = 500,
details: Optional[Dict[str, Any]] = None,
request_id: Optional[str] = None,
) -> str:
"""
Track an error occurrence.
Args:
error_type: Type of error
message: Error message
request_path: Request path that caused error
request_method: HTTP method
user_id: User ID if available
status_code: HTTP status code
details: Additional error details
request_id: Request ID for correlation
Returns:
Unique error tracking ID
"""
error_id = str(uuid.uuid4())
timestamp = datetime.utcnow().isoformat()
error_entry = {
"id": error_id,
"timestamp": timestamp,
"type": error_type,
"message": message,
"request_path": request_path,
"request_method": request_method,
"user_id": user_id,
"status_code": status_code,
"details": details or {},
"request_id": request_id,
}
self.error_history.append(error_entry)
# Keep history size manageable
if len(self.error_history) > self.max_history_size:
self.error_history = self.error_history[-self.max_history_size:]
logger.info(
f"Error tracked: {error_id}",
extra={
"error_id": error_id,
"error_type": error_type,
"status_code": status_code,
"request_path": request_path,
},
)
return error_id
def get_error_stats(self) -> Dict[str, Any]:
"""
Get error statistics from history.
Returns:
Dictionary containing error statistics
"""
if not self.error_history:
return {"total_errors": 0, "error_types": {}}
error_types: Dict[str, int] = {}
status_codes: Dict[int, int] = {}
for error in self.error_history:
error_type = error["type"]
error_types[error_type] = error_types.get(error_type, 0) + 1
status_code = error["status_code"]
status_codes[status_code] = status_codes.get(status_code, 0) + 1
return {
"total_errors": len(self.error_history),
"error_types": error_types,
"status_codes": status_codes,
"last_error": (
self.error_history[-1] if self.error_history else None
),
}
def get_recent_errors(self, limit: int = 10) -> list[Dict[str, Any]]:
"""
Get recent errors.
Args:
limit: Maximum number of errors to return
Returns:
List of recent error entries
"""
return self.error_history[-limit:] if self.error_history else []
def clear_history(self) -> None:
"""Clear error history."""
self.error_history.clear()
logger.info("Error history cleared")
# Global error tracker instance
_error_tracker: Optional[ErrorTracker] = None
def get_error_tracker() -> ErrorTracker:
"""
Get or create global error tracker instance.
Returns:
ErrorTracker instance
"""
global _error_tracker
if _error_tracker is None:
_error_tracker = ErrorTracker()
return _error_tracker
def reset_error_tracker() -> None:
"""Reset error tracker for testing."""
global _error_tracker
_error_tracker = None
class RequestContextManager:
"""
Manages request context for error tracking.
Stores request metadata for error correlation.
"""
def __init__(self):
"""Initialize context manager."""
self.context_stack: list[Dict[str, Any]] = []
def push_context(
self,
request_id: str,
request_path: str,
request_method: str,
user_id: Optional[str] = None,
) -> None:
"""
Push request context onto stack.
Args:
request_id: Unique request identifier
request_path: Request path
request_method: HTTP method
user_id: User ID if available
"""
context = {
"request_id": request_id,
"request_path": request_path,
"request_method": request_method,
"user_id": user_id,
"timestamp": datetime.utcnow().isoformat(),
}
self.context_stack.append(context)
def pop_context(self) -> Optional[Dict[str, Any]]:
"""
Pop request context from stack.
Returns:
Context dictionary or None if empty
"""
return self.context_stack.pop() if self.context_stack else None
def get_current_context(self) -> Optional[Dict[str, Any]]:
"""
Get current request context.
Returns:
Current context or None if empty
"""
return self.context_stack[-1] if self.context_stack else None
# Global request context manager
_context_manager: Optional[RequestContextManager] = None
def get_context_manager() -> RequestContextManager:
"""
Get or create global context manager instance.
Returns:
RequestContextManager instance
"""
global _context_manager
if _context_manager is None:
_context_manager = RequestContextManager()
return _context_manager

View File

@ -72,16 +72,16 @@ async def test_get_download_stats_with_data(
analytics_service, mock_db
):
"""Test download statistics with download data."""
# Mock downloads
# Mock downloads - updated to use actual model fields
download1 = MagicMock()
download1.status = "completed"
download1.size_bytes = 1024 * 1024 * 100 # 100 MB
download1.duration_seconds = 60
download1.total_bytes = 1024 * 1024 * 100 # 100 MB
download1.download_speed = 1024 * 1024 * 10 # 10 MB/s
download2 = MagicMock()
download2.status = "failed"
download2.size_bytes = 0
download2.duration_seconds = 0
download2.total_bytes = 0
download2.download_speed = None
mock_db.execute = AsyncMock(return_value=MagicMock(
scalars=MagicMock(return_value=MagicMock(all=MagicMock(
@ -120,12 +120,15 @@ async def test_get_series_popularity_with_data(
analytics_service, mock_db
):
"""Test series popularity with data."""
row = MagicMock()
row.series_name = "Test Anime"
row.download_count = 5
row.total_size = 1024 * 1024 * 500
row.last_download = datetime.now()
row.successful = 4
# Mock returns tuples:
# (series_name, download_count, total_size, last_download, successful)
row = (
"Test Anime",
5,
1024 * 1024 * 500,
datetime.now(),
4
)
mock_db.execute = AsyncMock(return_value=MagicMock(
all=MagicMock(return_value=[row])