fix test and add doc
This commit is contained in:
parent
1637835fe6
commit
9692dfc63b
@ -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
308
docs/README.md
Normal 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
943
docs/api_reference.md
Normal 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
772
docs/deployment.md
Normal 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
628
docs/user_guide.md
Normal 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
|
||||
@ -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
|
||||
|
||||
|
||||
255
src/server/exceptions/__init__.py
Normal file
255
src/server/exceptions/__init__.py
Normal 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,
|
||||
)
|
||||
35
src/server/exceptions/exceptions.py
Normal file
35
src/server/exceptions/exceptions.py
Normal 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",
|
||||
]
|
||||
@ -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
|
||||
|
||||
|
||||
236
src/server/middleware/error_handler.py
Normal file
236
src/server/middleware/error_handler.py
Normal 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),
|
||||
),
|
||||
)
|
||||
@ -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()
|
||||
|
||||
227
src/server/utils/error_tracking.py
Normal file
227
src/server/utils/error_tracking.py
Normal 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
|
||||
@ -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])
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user