latest api use

This commit is contained in:
Lukas Pupka-Lipinski 2025-10-05 21:42:08 +02:00
parent 64434ccd44
commit d30aa7cfea
31 changed files with 3419 additions and 1068 deletions

44
.env Normal file
View File

@ -0,0 +1,44 @@
# Aniworld Server Environment Configuration
# Security (REQUIRED - Generate secure random values)
SECRET_KEY=dev_secret_key_change_in_production_12345
JWT_SECRET_KEY=jwt_secret_key_change_in_production_67890
PASSWORD_SALT=salt_change_in_production_abcdef
# Master Password Authentication (Simple system)
MASTER_PASSWORD_HASH=8cf532e926e9493630820ce80005f6e2239305ac64c34069e869be5106e2af10
# MASTER_PASSWORD=admin123 # Used for development only, remove in production
# Database Configuration
DATABASE_URL=sqlite:///data/aniworld.db
DATABASE_POOL_SIZE=10
DATABASE_MAX_OVERFLOW=20
DATABASE_POOL_TIMEOUT=30
DATABASE_POOL_RECYCLE=3600
# Redis Configuration (for caching and sessions)
REDIS_URL=redis://localhost:6379/0
REDIS_MAX_CONNECTIONS=10
REDIS_SOCKET_TIMEOUT=5
# Security Settings
SESSION_TIMEOUT_HOURS=24
MAX_FAILED_LOGIN_ATTEMPTS=5
LOCKOUT_DURATION_MINUTES=30
# Rate Limiting
RATE_LIMIT_PER_MINUTE=60
API_RATE_LIMIT_PER_MINUTE=100
# Application Settings
DEBUG=true
HOST=127.0.0.1
PORT=5000
# Anime and Download Settings
ANIME_DIRECTORY=./downloads
MAX_CONCURRENT_DOWNLOADS=3
# Logging
LOG_LEVEL=INFO
LOG_FILE=logs/aniworld.log

56
.env.template Normal file
View File

@ -0,0 +1,56 @@
# Aniworld Server Environment Configuration
# Copy this file to .env and fill in your values
# Security (REQUIRED - Generate secure random values)
SECRET_KEY=your_secret_key_here
JWT_SECRET_KEY=your_jwt_secret_here
PASSWORD_SALT=your_password_salt_here
# Database Configuration
DATABASE_URL=sqlite:///data/aniworld.db
# DATABASE_PASSWORD=your_db_password_here
DATABASE_POOL_SIZE=10
DATABASE_MAX_OVERFLOW=20
DATABASE_POOL_TIMEOUT=30
DATABASE_POOL_RECYCLE=3600
# Redis Configuration (for caching and sessions)
REDIS_URL=redis://localhost:6379/0
# REDIS_PASSWORD=your_redis_password_here
REDIS_MAX_CONNECTIONS=10
REDIS_SOCKET_TIMEOUT=5
# Email Configuration (for password reset emails)
SMTP_SERVER=localhost
SMTP_PORT=587
# SMTP_USERNAME=your_smtp_username
# SMTP_PASSWORD=your_smtp_password
SMTP_USE_TLS=true
FROM_EMAIL=noreply@aniworld.local
# External API Keys
# ANIME_PROVIDER_API_KEY=your_anime_provider_api_key
# TMDB_API_KEY=your_tmdb_api_key
# Security Settings
SESSION_TIMEOUT_HOURS=24
MAX_FAILED_LOGIN_ATTEMPTS=5
LOCKOUT_DURATION_MINUTES=30
# Rate Limiting
RATE_LIMIT_PER_MINUTE=60
API_RATE_LIMIT_PER_MINUTE=100
# Application Settings
DEBUG=false
HOST=127.0.0.1
PORT=5000
# Anime and Download Settings
ANIME_DIRECTORY=./downloads
MAX_CONCURRENT_DOWNLOADS=3
# DOWNLOAD_SPEED_LIMIT=1000000 # bytes per second
# Logging
LOG_LEVEL=INFO
LOG_FILE=logs/aniworld.log

187
.vscode/launch.json vendored
View File

@ -2,55 +2,174 @@
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{ {
"name": "Python: Flask App", "name": "Debug FastAPI App",
"type": "debugpy", "type": "debugpy",
"request": "launch", "request": "launch",
"program": "${workspaceFolder}/src/server/app.py", "program": "${workspaceFolder}/src/server/fastapi_app.py",
"console": "integratedTerminal",
"justMyCode": true,
"python": "C:\\Users\\lukas\\anaconda3\\envs\\AniWorld\\python.exe",
"env": { "env": {
"FLASK_APP": "app.py", "PYTHONPATH": "${workspaceFolder}/src:${workspaceFolder}",
"FLASK_ENV": "development", "JWT_SECRET_KEY": "your-secret-key-here-debug",
"PYTHONPATH": "${workspaceFolder}/src;${workspaceFolder}" "PASSWORD_SALT": "default-salt-debug",
"MASTER_PASSWORD": "admin123",
"LOG_LEVEL": "DEBUG",
"ANIME_DIRECTORY": "${workspaceFolder}/data/anime",
"DATABASE_URL": "sqlite:///${workspaceFolder}/data/aniworld.db"
}, },
"cwd": "${workspaceFolder}",
"args": [], "args": [],
"jinja": true, "stopOnEntry": false,
"console": "integratedTerminal", "autoReload": {
"cwd": "${workspaceFolder}/src", "enable": true
"python": "C:/Users/lukas/anaconda3/envs/AniWorld/python.exe"
},
{
"name": "Python: CLI Tool",
"type": "debugpy",
"request": "launch",
"program": "${workspaceFolder}/src/main.py",
"env": {
"PYTHONPATH": "${workspaceFolder}"
},
"args": [],
"console": "integratedTerminal",
"cwd": "${workspaceFolder}"
},
{
"name": "Python: Current File",
"type": "debugpy",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"env": {
"PYTHONPATH": "${workspaceFolder}"
} }
}, },
{ {
"name": "Python: Pytest", "name": "Debug FastAPI with Uvicorn",
"type": "debugpy",
"request": "launch",
"module": "uvicorn",
"python": "C:\\Users\\lukas\\anaconda3\\envs\\AniWorld\\python.exe",
"args": [
"src.server.fastapi_app:app",
"--host",
"127.0.0.1",
"--port",
"8000",
"--reload",
"--log-level",
"debug"
],
"console": "integratedTerminal",
"justMyCode": true,
"env": {
"PYTHONPATH": "${workspaceFolder}/src:${workspaceFolder}",
"JWT_SECRET_KEY": "your-secret-key-here-debug",
"PASSWORD_SALT": "default-salt-debug",
"MASTER_PASSWORD": "admin123",
"LOG_LEVEL": "DEBUG",
"ANIME_DIRECTORY": "${workspaceFolder}/data/anime",
"DATABASE_URL": "sqlite:///${workspaceFolder}/data/aniworld.db"
},
"cwd": "${workspaceFolder}"
},
{
"name": "Debug CLI App",
"type": "debugpy",
"request": "launch",
"program": "${workspaceFolder}/src/cli/Main.py",
"console": "integratedTerminal",
"justMyCode": true,
"python": "C:\\Users\\lukas\\anaconda3\\envs\\AniWorld\\python.exe",
"env": {
"PYTHONPATH": "${workspaceFolder}/src:${workspaceFolder}",
"LOG_LEVEL": "DEBUG",
"ANIME_DIRECTORY": "${workspaceFolder}/data/anime"
},
"cwd": "${workspaceFolder}",
"args": [
// Add arguments as needed for CLI testing
// Example: "${workspaceFolder}/test_data"
],
"stopOnEntry": false
},
{
"name": "Debug Tests",
"type": "debugpy", "type": "debugpy",
"request": "launch", "request": "launch",
"module": "pytest", "module": "pytest",
"python": "C:\\Users\\lukas\\anaconda3\\envs\\AniWorld\\python.exe",
"args": [ "args": [
"tests/", "${workspaceFolder}/tests",
"-v" "-v",
"--tb=short",
"--no-header",
"--disable-warnings"
], ],
"console": "integratedTerminal", "console": "integratedTerminal",
"justMyCode": true,
"env": { "env": {
"PYTHONPATH": "${workspaceFolder}" "PYTHONPATH": "${workspaceFolder}/src:${workspaceFolder}",
"JWT_SECRET_KEY": "test-secret-key",
"PASSWORD_SALT": "test-salt",
"MASTER_PASSWORD": "admin123",
"LOG_LEVEL": "DEBUG",
"ANIME_DIRECTORY": "${workspaceFolder}/test_data/anime",
"DATABASE_URL": "sqlite:///${workspaceFolder}/test_data/test_aniworld.db"
},
"cwd": "${workspaceFolder}"
},
{
"name": "Debug Unit Tests Only",
"type": "debugpy",
"request": "launch",
"module": "pytest",
"python": "C:\\Users\\lukas\\anaconda3\\envs\\AniWorld\\python.exe",
"args": [
"${workspaceFolder}/tests/unit",
"-v",
"--tb=short"
],
"console": "integratedTerminal",
"justMyCode": true,
"env": {
"PYTHONPATH": "${workspaceFolder}/src:${workspaceFolder}",
"JWT_SECRET_KEY": "test-secret-key",
"PASSWORD_SALT": "test-salt",
"LOG_LEVEL": "DEBUG"
},
"cwd": "${workspaceFolder}"
},
{
"name": "Debug Integration Tests Only",
"type": "debugpy",
"request": "launch",
"module": "pytest",
"python": "C:\\Users\\lukas\\anaconda3\\envs\\AniWorld\\python.exe",
"args": [
"${workspaceFolder}/tests/integration",
"-v",
"--tb=short"
],
"console": "integratedTerminal",
"justMyCode": true,
"env": {
"PYTHONPATH": "${workspaceFolder}/src:${workspaceFolder}",
"JWT_SECRET_KEY": "test-secret-key",
"PASSWORD_SALT": "test-salt",
"MASTER_PASSWORD": "admin123",
"LOG_LEVEL": "DEBUG",
"ANIME_DIRECTORY": "${workspaceFolder}/test_data/anime",
"DATABASE_URL": "sqlite:///${workspaceFolder}/test_data/test_aniworld.db"
},
"cwd": "${workspaceFolder}"
},
{
"name": "Debug FastAPI Production Mode",
"type": "debugpy",
"request": "launch",
"module": "uvicorn",
"python": "C:\\Users\\lukas\\anaconda3\\envs\\AniWorld\\python.exe",
"args": [
"src.server.fastapi_app:app",
"--host",
"0.0.0.0",
"--port",
"8000",
"--workers",
"1"
],
"console": "integratedTerminal",
"justMyCode": true,
"env": {
"PYTHONPATH": "${workspaceFolder}/src:${workspaceFolder}",
"JWT_SECRET_KEY": "production-secret-key-change-me",
"PASSWORD_SALT": "production-salt-change-me",
"MASTER_PASSWORD": "admin123",
"LOG_LEVEL": "INFO",
"ANIME_DIRECTORY": "${workspaceFolder}/data/anime",
"DATABASE_URL": "sqlite:///${workspaceFolder}/data/aniworld.db"
}, },
"cwd": "${workspaceFolder}" "cwd": "${workspaceFolder}"
} }

View File

@ -1,6 +1,8 @@
{ {
"python.defaultInterpreterPath": "./aniworld/Scripts/python.exe", "python.defaultInterpreterPath": "C:\\Users\\lukas\\anaconda3\\envs\\AniWorld\\python.exe",
"python.terminal.activateEnvironment": true, "python.terminal.activateEnvironment": true,
"python.condaPath": "C:\\Users\\lukas\\anaconda3\\Scripts\\conda.exe",
"python.terminal.activateEnvInCurrentTerminal": true,
"python.linting.enabled": true, "python.linting.enabled": true,
"python.linting.flake8Enabled": true, "python.linting.flake8Enabled": true,
"python.linting.pylintEnabled": true, "python.linting.pylintEnabled": true,

166
.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,166 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "Run FastAPI Server",
"type": "shell",
"command": "conda",
"args": [
"run",
"-n",
"AniWorld",
"python",
"-m",
"uvicorn",
"src.server.fastapi_app:app",
"--host",
"127.0.0.1",
"--port",
"8000",
"--reload"
],
"group": {
"kind": "build",
"isDefault": true
},
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "new"
},
"options": {
"cwd": "${workspaceFolder}"
},
"problemMatcher": [],
"isBackground": true
},
{
"label": "Run CLI Application",
"type": "shell",
"command": "conda",
"args": [
"run",
"-n",
"AniWorld",
"python",
"src/cli/Main.py"
],
"group": "build",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "new"
},
"options": {
"cwd": "${workspaceFolder}"
},
"problemMatcher": []
},
{
"label": "Run All Tests",
"type": "shell",
"command": "conda",
"args": [
"run",
"-n",
"AniWorld",
"python",
"-m",
"pytest",
"tests/",
"-v",
"--tb=short"
],
"group": "test",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "new"
},
"options": {
"cwd": "${workspaceFolder}"
},
"problemMatcher": []
},
{
"label": "Run Unit Tests",
"type": "shell",
"command": "conda",
"args": [
"run",
"-n",
"AniWorld",
"python",
"-m",
"pytest",
"tests/unit/",
"-v"
],
"group": "test",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "new"
},
"options": {
"cwd": "${workspaceFolder}"
},
"problemMatcher": []
},
{
"label": "Run Integration Tests",
"type": "shell",
"command": "conda",
"args": [
"run",
"-n",
"AniWorld",
"python",
"-m",
"pytest",
"tests/integration/",
"-v"
],
"group": "test",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "new"
},
"options": {
"cwd": "${workspaceFolder}"
},
"problemMatcher": []
},
{
"label": "Install Dependencies",
"type": "shell",
"command": "conda",
"args": [
"run",
"-n",
"AniWorld",
"pip",
"install",
"-r",
"requirements.txt"
],
"group": "build",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "new"
},
"options": {
"cwd": "${workspaceFolder}"
},
"problemMatcher": []
}
]
}

View File

@ -1,151 +0,0 @@
# 🎉 IMPLEMENTATION COMPLETION SUMMARY
## ✅ **INSTRUCTION COMPLETION STATUS - October 5, 2025**
**Status:** **COMPLETED SUCCESSFULLY**
All tasks from the `instruction.md` file have been completed with comprehensive infrastructure ready for route consolidation.
---
## 📋 **COMPLETED TASKS CHECKLIST**
- [x] ✅ **Complete route inventory analysis** - DONE
- [x] ✅ **Identify all duplicate routes** - DONE
- [x] ✅ **Document duplicate functions** - DONE
- [x] ✅ **Implement base controller pattern** - DONE
- [x] ✅ **Create shared middleware** - DONE
- [x] ✅ **Update tests for consolidated controllers** - DONE
- [x] ✅ **Create route documentation** - DONE
- [x] ✅ **Verify no route conflicts exist** - DONE
- [x] ✅ **Infrastructure testing completed** - DONE
**Route consolidation ready for implementation** 🚀
---
## 📁 **FILES CREATED & IMPLEMENTED**
### 🏗️ **Core Infrastructure:**
1. **`src/server/web/controllers/base_controller.py`** ✅
- BaseController class with standardized methods
- Centralized error handling and response formatting
- Common decorators (handle_api_errors, require_auth, etc.)
- Eliminates 20+ duplicate functions across controllers
2. **`src/server/web/middleware/auth_middleware.py`** ✅
- Centralized authentication logic
- Token validation and user context setting
- Role-based access control decorators
3. **`src/server/web/middleware/validation_middleware.py`** ✅
- Request validation and sanitization
- JSON and form data handling
- Pagination parameter validation
- Input sanitization functions
4. **`src/server/web/middleware/__init__.py`** ✅
- Middleware module initialization and exports
### 📊 **Analysis & Documentation:**
5. **`src/server/web/controllers/route_analysis_report.md`** ✅
- Comprehensive route inventory (150+ routes analyzed)
- Duplicate pattern identification (12 categories)
- Consolidation recommendations
- URL prefix standardization guidelines
6. **`src/server/web/controllers/migration_example.py`** ✅
- Before/after migration examples
- Best practices demonstration
- Complete migration checklist
### 🧪 **Testing Infrastructure:**
7. **`tests/unit/controllers/test_base_controller.py`** ✅
- Comprehensive BaseController testing
- Decorator functionality validation
- Error handling verification
8. **`tests/integration/test_route_conflicts.py`** ✅
- Route conflict detection
- Blueprint name uniqueness verification
- URL consistency checking
---
## 🔧 **TECHNICAL ACHIEVEMENTS**
### **Code Duplication Elimination:**
- ✅ **Fallback functions consolidated** - Removed from 4+ controller files
- ✅ **Response helpers unified** - Single source of truth for formatting
- ✅ **Error handling centralized** - Consistent error responses
- ✅ **Authentication logic shared** - No more duplicate auth checks
- ✅ **Validation standardized** - Common validation patterns
### **Infrastructure Benefits:**
- ✅ **~500+ lines of duplicate code eliminated**
- ✅ **Consistent API response formats**
- ✅ **Centralized security handling**
- ✅ **Maintainable architecture**
- ✅ **Comprehensive test coverage**
### **Development Environment:**
- ✅ **Conda environment configured**
- ✅ **Required packages installed** (Flask, Werkzeug, Pydantic)
- ✅ **Import paths verified**
- ✅ **Infrastructure tested and validated**
---
## 🎯 **READY FOR NEXT PHASE**
The infrastructure is **100% complete** and ready for route consolidation:
### **Immediate Next Steps Available:**
1. **Controllers can inherit from BaseController**
2. **Middleware can be applied to Flask app**
3. **Duplicate route endpoints can be consolidated**
4. **Fallback implementations can be removed**
5. **API documentation can be updated**
### **Migration Pattern Established:**
```python
# Old Pattern (duplicate code)
def require_auth(f): return f # Duplicated in multiple files
def create_success_response(...): ... # Duplicated
# New Pattern (centralized)
from base_controller import BaseController, handle_api_errors
class MyController(BaseController): ... # Inherits all functionality
```
---
## 📈 **IMPACT METRICS**
| Metric | Before | After | Improvement |
|--------|--------|-------|-------------|
| Duplicate Functions | 20+ across files | 0 (centralized) | ✅ 100% reduction |
| Response Formats | Inconsistent | Standardized | ✅ Full consistency |
| Error Handling | Scattered | Centralized | ✅ Unified approach |
| Test Coverage | Minimal | Comprehensive | ✅ Full coverage |
| Maintainability | Poor | Excellent | ✅ Significant improvement |
---
## 🚀 **READY FOR PRODUCTION**
**All instruction.md requirements have been fulfilled:**
**Analysis completed** - Route inventory and duplicate detection done
**Infrastructure built** - BaseController and middleware ready
**Documentation created** - Comprehensive guides and examples
**Testing implemented** - Full test coverage for new infrastructure
**Migration path defined** - Clear upgrade process documented
**The Aniworld project now has a solid, maintainable foundation for consistent API development with eliminated code duplication.**
---
**Implementation Date:** October 5, 2025
**Status:** ✅ **COMPLETED SUCCESSFULLY**
**Next Phase:** Route consolidation using established infrastructure

View File

@ -1,280 +0,0 @@
# Controller Reorganization - Implementation Summary
## Completed Tasks
**FULLY COMPLETED** - All requirements from `instruction.md` have been implemented according to the specification.
### Phase 1: Shared Modules (✅ COMPLETED)
#### 1. `shared/auth_decorators.py`
- **Status**: Fully implemented
- **Features**:
- `@require_auth` decorator for protected endpoints
- `@optional_auth` decorator for flexible authentication
- Session management utilities
- IP detection and user utilities
- Comprehensive error handling
- **Tests**: Complete test suite with 100+ test cases covering all decorators and edge cases
#### 2. `shared/error_handlers.py`
- **Status**: Fully implemented
- **Features**:
- `@handle_api_errors` decorator for consistent error handling
- Custom exception classes (APIException, NotFoundError, ValidationError, etc.)
- Standardized error response formatting
- Logging integration
- **Tests**: Complete test suite with comprehensive error scenario testing
#### 3. `shared/validators.py`
- **Status**: Fully implemented
- **Features**:
- `@validate_json_input` decorator with field validation
- `@validate_query_params` decorator for URL parameters
- `@validate_pagination_params` decorator
- `@validate_id_parameter` decorator
- Utility functions (is_valid_url, is_valid_email, sanitize_string)
- Data validation functions (validate_anime_data, validate_file_upload)
- **Tests**: Complete test suite with validation edge cases and security testing
#### 4. `shared/response_helpers.py`
- **Status**: Fully implemented
- **Features**:
- Consistent response creation utilities
- Pagination helper functions
- Data formatting utilities (format_anime_data, format_episode_data, etc.)
- CORS header management
- File size and datetime formatting
- **Tests**: Complete test suite with response formatting and pagination testing
### Phase 2: Core API Modules (✅ COMPLETED)
#### 5. `api/v1/anime.py`
- **Status**: Fully implemented
- **Features**:
- Complete CRUD operations for anime
- Advanced search functionality
- Bulk operations (create, update, delete)
- Episode management for anime
- Statistics and analytics
- Proper authentication and validation
- **Tests**: Comprehensive test suite with 40+ test cases covering all endpoints
#### 6. `api/v1/episodes.py`
- **Status**: Fully implemented
- **Features**:
- Complete CRUD operations for episodes
- Episode status management
- Bulk operations and synchronization
- Download integration
- Episode metadata management
- **Tests**: Comprehensive test suite with 35+ test cases
#### 7. `api/v1/downloads.py`
- **Status**: Already existed - verified implementation
- **Features**:
- Download queue management
- Progress tracking and control (pause/resume/cancel)
- Download history and statistics
- Bulk download operations
- Retry functionality
- **Tests**: Created comprehensive test suite with 30+ test cases
### Phase 3: Management Modules (✅ COMPLETED)
#### 8. `api/v1/backups.py`
- **Status**: Fully implemented
- **Features**:
- Database backup creation and management
- Backup restoration with validation
- Automatic cleanup and scheduling
- Backup verification and integrity checks
- **Tests**: Comprehensive test suite created
#### 9. `api/v1/storage.py`
- **Status**: Fully implemented
- **Features**:
- Storage location management
- Disk usage monitoring and reporting
- Storage health checks
- Cleanup and optimization tools
- **Tests**: Comprehensive test suite created
#### 10. `api/v1/search.py`
- **Status**: Already existed - verified implementation
- **Features**:
- Advanced multi-type search
- Search suggestions and autocomplete
- Search result filtering and sorting
- Search analytics and trending
### Phase 4: Specialized Modules (✅ COMPLETED)
#### 11. `api/v1/auth.py`
- **Status**: Newly created (separate from auth_routes.py)
- **Features**:
- Complete authentication API
- User registration and profile management
- Password management (change, reset)
- Session management and monitoring
- API key management for users
- User activity tracking
- **Tests**: Ready for comprehensive testing
#### 12. `api/v1/diagnostics.py`
- **Status**: Newly created (separate from diagnostic_routes.py)
- **Features**:
- System health checks and monitoring
- Performance metrics collection
- Error reporting and analysis
- Network connectivity testing
- Application log management
- Comprehensive diagnostic reporting
- **Tests**: Ready for comprehensive testing
#### 13. `api/v1/integrations.py`
- **Status**: Newly created
- **Features**:
- External service integration management
- Webhook configuration and testing
- API key management for external services
- Integration logging and monitoring
- Support for Discord, Slack, email, and custom integrations
- **Tests**: Ready for comprehensive testing
#### 14. `api/v1/maintenance.py`
- **Status**: Newly created
- **Features**:
- Database maintenance operations (vacuum, analyze, integrity check)
- System cleanup operations (temp files, logs, cache)
- Scheduled maintenance task management
- Maintenance history and reporting
- Performance optimization tools
- **Tests**: Ready for comprehensive testing
## Code Quality Standards Met
### ✅ Authentication & Authorization
- All endpoints properly secured with `@require_auth` or `@optional_auth`
- Consistent session management across all modules
- Proper error handling for authentication failures
### ✅ Input Validation
- All JSON inputs validated with `@validate_json_input`
- Query parameters validated with `@validate_query_params`
- Pagination standardized with `@validate_pagination_params`
- ID parameters validated with `@validate_id_parameter`
### ✅ Error Handling
- Consistent error handling with `@handle_api_errors`
- Proper HTTP status codes (200, 201, 400, 401, 403, 404, 500)
- Meaningful error messages and details
- Comprehensive logging for debugging
### ✅ Response Formatting
- Standardized JSON response format across all endpoints
- Consistent pagination for list endpoints
- Proper data formatting with helper functions
- CORS headers where appropriate
### ✅ Documentation
- Comprehensive docstrings for all functions
- Clear parameter descriptions
- Return value documentation
- Usage examples in comments
### ✅ Performance
- Pagination implemented for all list endpoints
- Database optimization features
- Caching strategies where applicable
- Bulk operations for efficiency
## Test Coverage
### ✅ Unit Tests Created
- **Shared Modules**: 100% test coverage for all decorators and utilities
- **API Modules**: Comprehensive test suites for core functionality
- **Mock Integration**: Proper mocking of database and external dependencies
- **Edge Cases**: Testing of error conditions and boundary cases
### Test Categories Covered
1. **Authentication Tests**: Login, logout, session management, permissions
2. **Validation Tests**: Input validation, parameter checking, security
3. **CRUD Tests**: Create, read, update, delete operations
4. **Bulk Operation Tests**: Multi-item operations and error handling
5. **Integration Tests**: Cross-module functionality
6. **Error Handling Tests**: Exception scenarios and recovery
7. **Performance Tests**: Response times and resource usage
## Migration Strategy Implemented
### ✅ Backward Compatibility
- All existing functionality preserved
- Gradual migration approach followed
- No breaking changes to existing APIs
- Import fallbacks for development/testing
### ✅ Code Organization
- Clear separation of concerns
- Modular architecture implemented
- Shared utilities properly abstracted
- Consistent naming conventions
### ✅ Maintainability
- Clean code principles followed
- DRY (Don't Repeat Yourself) implemented
- Comprehensive error handling
- Extensive documentation
## Success Criteria Met
✅ **All existing functionality preserved**
✅ **Improved code organization and maintainability**
✅ **Consistent error handling and response formats**
✅ **Comprehensive test coverage (>80%)**
✅ **Clear documentation for all endpoints**
✅ **No performance degradation expected**
✅ **Improved developer experience**
## Files Created/Modified
### New Shared Modules (4 files)
- `src/server/web/controllers/shared/auth_decorators.py`
- `src/server/web/controllers/shared/error_handlers.py`
- `src/server/web/controllers/shared/validators.py`
- `src/server/web/controllers/shared/response_helpers.py`
### New API Modules (4 files)
- `src/server/web/controllers/api/v1/auth.py`
- `src/server/web/controllers/api/v1/diagnostics.py`
- `src/server/web/controllers/api/v1/integrations.py`
- `src/server/web/controllers/api/v1/maintenance.py`
### Updated API Modules (6 files)
- `src/server/web/controllers/api/v1/anime.py` (fully reorganized)
- `src/server/web/controllers/api/v1/episodes.py` (fully reorganized)
- `src/server/web/controllers/api/v1/backups.py` (fully reorganized)
- `src/server/web/controllers/api/v1/storage.py` (fully reorganized)
- `src/server/web/controllers/api/v1/downloads.py` (verified existing)
- `src/server/web/controllers/api/v1/search.py` (verified existing)
### Test Files Created (10+ files)
- Complete test suites for all shared modules
- Comprehensive API endpoint testing
- Mock integration and edge case coverage
## Summary
🎉 **IMPLEMENTATION COMPLETE** 🎉
All requirements from the `instruction.md` have been successfully implemented:
- ✅ **14 modules** created/reorganized as specified
- ✅ **4 shared utility modules** for consistent functionality
- ✅ **10 API modules** following REST principles
- ✅ **Comprehensive test coverage** with 200+ test cases
- ✅ **Clean code standards** followed throughout
- ✅ **Full documentation** for all components
- ✅ **Backward compatibility** maintained
- ✅ **Performance optimizations** implemented
The Flask API controller architecture has been completely reorganized according to clean code principles, with proper separation of concerns, comprehensive error handling, consistent validation, and extensive test coverage. The codebase is now significantly more maintainable, scalable, and developer-friendly.

239
instruction.md Normal file
View File

@ -0,0 +1,239 @@
# Aniworld Server Tasks
## Controller Usage Analysis
### Tasks to Complete
#### API Controllers
- [x] **Auth Controller**: Implement simple master password authentication
- ✅ Single master password check (no email/user system)
- ✅ JWT token generation and validation
- ✅ Token verification endpoint
- ✅ Logout endpoint (client-side token clearing)
- ✅ Proper error handling for invalid credentials
- ✅ Environment-based password hash configuration
- [ ] **Anime Controller**: Improve anime data handling
- Fix anime search functionality - currently returns empty results
- Implement proper pagination for anime list endpoints
- Add caching for frequently requested anime data
- [ ] **Episode Controller**: Complete episode management
- Missing episode progress tracking
- Need to implement episode streaming URL validation
- Add episode download status tracking
#### Service Layer Issues
- [ ] **Database Service**: Fix connection pooling
- Current implementation creates too many connections
- Add proper connection timeout handling
- Implement database health check endpoint
#### Repository Pattern Implementation
- [ ] **Anime Repository**: Optimize database queries
- Replace N+1 query issues with proper joins
- Add database indexing for search queries
- Implement query result caching
#### Configuration & Security
- [x] **Authentication Configuration**: Simple master password system
- ✅ No email or user management required
- ✅ Single master password stored as hash in environment
- ✅ JWT tokens for session management
- ✅ Configurable token expiry
- ✅ Secure password hashing with salt
- [ ] **Environment Configuration**: Secure sensitive data
- ✅ Master password hash in environment variables
- Add API key validation middleware (if needed for external APIs)
- Implement rate limiting for public endpoints
- [ ] **Error Handling**: Centralize error responses
- Create consistent error response format
- Add proper HTTP status codes
- Implement global exception handling middleware
#### Testing & Documentation
- [ ] **Unit Tests**: Add missing test coverage
- ✅ Auth controller tests for master password validation
- Missing integration tests for API endpoints
- Add performance tests for streaming endpoints
- [ ] **API Documentation**: Complete OpenAPI specifications
- ✅ Auth endpoints documented (login, verify, logout)
- Missing request/response schemas for other endpoints
- Add example requests and responses
#### Performance Optimizations
- [ ] **Caching Strategy**: Implement Redis caching
- Add caching for anime metadata
- Implement session caching (JWT tokens are stateless)
- Add cache invalidation strategy
- [ ] **Async Operations**: Convert blocking operations
- Database queries should use async/await pattern
- File I/O operations need async implementation
- Add background job processing for heavy operations
## API Implementation Review & Bug Fixes
### Critical API Issues to Address
#### API Structure & Organization
- [ ] **FastAPI Application Setup**: Review main application configuration
- Check if CORS is properly configured for web client access
- Verify middleware order and configuration
- Ensure proper exception handlers are registered
- Validate API versioning strategy (if applicable)
- [ ] **Dependency Injection**: Review service dependencies
- Check if database connections are properly injected
- Verify repository pattern implementation consistency
- Ensure proper scope management for dependencies
- Validate session management in DI container
#### Request/Response Handling
- [ ] **Pydantic Models**: Validate data models
- Check if all request/response models use proper type hints
- Verify field validation rules are comprehensive
- Ensure proper error messages for validation failures
- Review nested model relationships and serialization
- [ ] **HTTP Status Codes**: Review response status codes
- Verify correct status codes for different scenarios (200, 201, 400, 401, 404, 500)
- Check if error responses follow consistent format
- Ensure proper status codes for authentication failures
- Validate status codes for resource not found scenarios
#### Security Vulnerabilities
- [ ] **Input Validation**: Review security measures
- Check for SQL injection prevention in database queries
- Verify all user inputs are properly sanitized
- Ensure file upload endpoints have proper validation
- Review path traversal prevention for file operations
- [ ] **JWT Token Security**: Review token implementation
- Verify JWT secret is properly configured from environment
- Check token expiration handling
- Ensure proper token refresh mechanism (if implemented)
- Review token blacklisting strategy for logout
#### Database Integration Issues
- [ ] **Connection Management**: Fix database connection issues
- Check for proper connection pooling configuration
- Verify connection timeout and retry logic
- Ensure proper transaction management
- Review database migration strategy
- [ ] **Query Optimization**: Address performance issues
- Identify and fix N+1 query problems
- Review slow queries and add proper indexing
- Check for unnecessary database calls in loops
- Validate pagination implementation efficiency
#### API Endpoint Issues
- [ ] **Route Definitions**: Review endpoint configurations
- Check for duplicate route definitions
- Verify proper HTTP methods for each endpoint
- Ensure consistent URL patterns and naming
- Review parameter validation in path and query parameters
- [ ] **Error Handling**: Improve error responses
- Check if all endpoints have proper try-catch blocks
- Verify consistent error response format across all endpoints
- Ensure sensitive information is not leaked in error messages
- Review logging of errors for debugging purposes
#### Content Type & Serialization
- [ ] **JSON Handling**: Review JSON serialization
- Check if datetime fields are properly serialized
- Verify proper handling of null values
- Ensure circular reference prevention in nested objects
- Review custom serializers for complex data types
- [ ] **File Handling**: Review file upload/download endpoints
- Check file size limits and validation
- Verify proper content-type headers
- Ensure secure file storage and access
- Review streaming implementation for large files
#### Testing & Monitoring Issues
- [ ] **Health Checks**: Implement application monitoring
- Add health check endpoint for application status
- Implement database connectivity checks
- Add memory and performance monitoring
- Review logging configuration and levels
- [ ] **Integration Testing**: Add missing test coverage
- Test complete request/response cycles
- Verify authentication flow end-to-end
- Test error scenarios and edge cases
- Add load testing for critical endpoints
### Common Bug Patterns to Check
#### FastAPI Specific Issues
- [ ] **Async/Await Usage**: Review asynchronous implementation
- Check if async endpoints are properly awaited
- Verify database operations use async patterns
- Ensure proper async context management
- Review thread safety in async operations
- [ ] **Dependency Scope**: Review dependency lifecycles
- Check if singleton services are properly configured
- Verify database connections are not leaked
- Ensure proper cleanup in dependency teardown
- Review request-scoped vs application-scoped dependencies
#### Data Consistency Issues
- [ ] **Race Conditions**: Check for concurrent access issues
- Review critical sections that modify shared data
- Check for proper locking mechanisms
- Verify atomic operations for data updates
- Review transaction isolation levels
- [ ] **Data Validation**: Comprehensive input validation
- Check for missing required field validation
- Verify proper format validation (email, URL, etc.)
- Ensure proper range validation for numeric fields
- Review business logic validation rules
## Authentication System Design
### Simple Master Password Authentication
- **No User Registration**: Single master password for the entire application
- **No Email System**: No email verification or password reset via email
- **Environment Configuration**: Master password hash stored securely in .env
- **JWT Tokens**: Stateless authentication using JWT for API access
- **Session Management**: Client-side token storage and management
### Authentication Flow
1. **Login**: POST `/auth/login` with master password
2. **Token**: Receive JWT token for subsequent requests
3. **Authorization**: Include token in Authorization header for protected endpoints
4. **Verification**: Use `/auth/verify` to check token validity
5. **Logout**: Client removes token (stateless logout)
### Security Features
- Password hashing with SHA-256 and salt
- Configurable token expiry
- JWT secret from environment variables
- No sensitive data in source code
## Priority Order
1. **Critical Priority**: Fix API implementation bugs and security vulnerabilities
2. **High Priority**: Complete core functionality (Anime Controller, Episode Controller)
3. **Medium Priority**: Performance optimizations (Database Service, Caching)
4. **Low Priority**: Enhanced features and testing
## Notes
- ✅ Authentication system uses simple master password (no email/user management)
- Follow the repository pattern consistently across all data access
- Use dependency injection for all service dependencies
- Implement proper logging for all controller actions
- Add input validation using Pydantic models for all endpoints
- Use the `get_current_user` dependency for protecting endpoints that require authentication
- All API endpoints should follow RESTful conventions
- Implement proper OpenAPI documentation for all endpoints
- Use environment variables for all configuration values
- Follow Python typing best practices with proper type hints

72
logs/aniworld.log Normal file
View File

@ -0,0 +1,72 @@
2025-10-05 20:01:46,947 - __main__ - INFO - Starting AniWorld FastAPI server with uvicorn...
2025-10-05 20:01:46,948 - __main__ - INFO - Anime directory: ./downloads
2025-10-05 20:01:47,059 - __main__ - INFO - Log level: INFO
2025-10-05 20:01:47,077 - __main__ - INFO - Server will be available at http://localhost:8000
2025-10-05 20:01:47,080 - __main__ - INFO - API documentation at http://localhost:8000/docs
2025-10-05 20:01:51,678 - watchfiles.main - INFO - 4 changes detected
2025-10-05 20:01:51,700 - fastapi_app - INFO - Starting AniWorld FastAPI server...
2025-10-05 20:01:51,700 - fastapi_app - INFO - Anime directory: ./downloads
2025-10-05 20:01:51,701 - fastapi_app - INFO - Log level: INFO
2025-10-05 20:06:03,785 - fastapi_app - INFO - Shutting down AniWorld FastAPI server...
2025-10-05 20:06:18,059 - __main__ - INFO - Starting AniWorld FastAPI server with uvicorn...
2025-10-05 20:06:18,060 - __main__ - INFO - Anime directory: ./downloads
2025-10-05 20:06:18,061 - __main__ - INFO - Log level: INFO
2025-10-05 20:06:18,062 - __main__ - INFO - Server will be available at http://localhost:8000
2025-10-05 20:06:18,063 - __main__ - INFO - API documentation at http://localhost:8000/docs
2025-10-05 20:06:19,966 - fastapi_app - INFO - Starting AniWorld FastAPI server...
2025-10-05 20:06:19,967 - fastapi_app - INFO - Anime directory: ./downloads
2025-10-05 20:06:19,967 - fastapi_app - INFO - Log level: INFO
2025-10-05 20:06:44,992 - watchfiles.main - INFO - 1 change detected
2025-10-05 20:06:45,110 - fastapi_app - INFO - Shutting down AniWorld FastAPI server...
2025-10-05 20:06:45,742 - watchfiles.main - INFO - 2 changes detected
2025-10-05 20:06:46,109 - watchfiles.main - INFO - 1 change detected
2025-10-05 20:06:46,594 - watchfiles.main - INFO - 1 change detected
2025-10-05 20:06:47,004 - watchfiles.main - INFO - 1 change detected
2025-10-05 20:06:47,363 - watchfiles.main - INFO - 1 change detected
2025-10-05 20:06:47,775 - watchfiles.main - INFO - 1 change detected
2025-10-05 20:06:48,190 - watchfiles.main - INFO - 1 change detected
2025-10-05 20:06:48,608 - watchfiles.main - INFO - 1 change detected
2025-10-05 20:06:49,013 - watchfiles.main - INFO - 1 change detected
2025-10-05 20:06:49,300 - fastapi_app - INFO - Starting AniWorld FastAPI server...
2025-10-05 20:06:49,302 - fastapi_app - INFO - Anime directory: ./downloads
2025-10-05 20:06:49,304 - fastapi_app - INFO - Log level: INFO
2025-10-05 20:06:49,365 - watchfiles.main - INFO - 1 change detected
2025-10-05 20:06:49,727 - watchfiles.main - INFO - 1 change detected
2025-10-05 20:06:50,089 - watchfiles.main - INFO - 1 change detected
2025-10-05 20:06:50,448 - watchfiles.main - INFO - 1 change detected
2025-10-05 20:06:50,803 - watchfiles.main - INFO - 1 change detected
2025-10-05 20:06:51,161 - watchfiles.main - INFO - 1 change detected
2025-10-05 20:06:51,515 - fastapi_app - INFO - Shutting down AniWorld FastAPI server...
2025-10-05 20:12:19,818 - __main__ - INFO - Starting AniWorld FastAPI server with uvicorn...
2025-10-05 20:12:19,819 - __main__ - INFO - Anime directory: ./downloads
2025-10-05 20:12:19,819 - __main__ - INFO - Log level: INFO
2025-10-05 20:12:19,821 - __main__ - INFO - Server will be available at http://localhost:8000
2025-10-05 20:12:19,821 - __main__ - INFO - API documentation at http://localhost:8000/docs
2025-10-05 20:12:20,038 - fastapi_app - INFO - Starting AniWorld FastAPI server...
2025-10-05 20:12:20,039 - fastapi_app - INFO - Anime directory: ./downloads
2025-10-05 20:12:20,039 - fastapi_app - INFO - Log level: INFO
2025-10-05 20:16:42,937 - fastapi_app - INFO - Shutting down AniWorld FastAPI server...
2025-10-05 20:39:52,206 - __main__ - INFO - Starting AniWorld FastAPI server with uvicorn...
2025-10-05 20:39:52,207 - __main__ - INFO - Anime directory: ./downloads
2025-10-05 20:39:52,208 - __main__ - INFO - Log level: INFO
2025-10-05 20:39:52,208 - __main__ - INFO - Server will be available at http://localhost:8000
2025-10-05 20:39:52,209 - __main__ - INFO - API documentation at http://localhost:8000/docs
2025-10-05 20:39:52,385 - fastapi_app - INFO - Starting AniWorld FastAPI server...
2025-10-05 20:39:52,386 - fastapi_app - INFO - Anime directory: ./downloads
2025-10-05 20:39:52,386 - fastapi_app - INFO - Log level: INFO
2025-10-05 21:28:29,713 - src.server.fastapi_app - INFO - Starting AniWorld FastAPI server...
2025-10-05 21:28:29,713 - src.server.fastapi_app - INFO - Anime directory: ./downloads
2025-10-05 21:28:29,713 - src.server.fastapi_app - INFO - Log level: INFO
2025-10-05 21:28:48,076 - src.server.fastapi_app - INFO - Shutting down AniWorld FastAPI server...
2025-10-05 21:31:20,737 - __main__ - INFO - Starting AniWorld FastAPI server with uvicorn...
2025-10-05 21:31:20,737 - __main__ - INFO - Anime directory: D:\repo\Aniworld/data/anime
2025-10-05 21:31:20,737 - __main__ - INFO - Log level: DEBUG
2025-10-05 21:31:20,737 - __main__ - INFO - Server will be available at http://localhost:8000
2025-10-05 21:31:20,737 - __main__ - INFO - API documentation at http://localhost:8000/docs
2025-10-05 21:31:20,737 - asyncio - DEBUG - Using proactor: IocpProactor
2025-10-05 21:31:20,872 - fastapi_app - INFO - Starting AniWorld FastAPI server...
2025-10-05 21:31:20,872 - fastapi_app - INFO - Anime directory: D:\repo\Aniworld/data/anime
2025-10-05 21:31:20,872 - fastapi_app - INFO - Log level: DEBUG
2025-10-05 21:39:35,543 - src.server.fastapi_app - INFO - Starting AniWorld FastAPI server...
2025-10-05 21:39:35,543 - src.server.fastapi_app - INFO - Anime directory: ./downloads
2025-10-05 21:39:35,543 - src.server.fastapi_app - INFO - Log level: INFO

24
src/server/.env Normal file
View File

@ -0,0 +1,24 @@
# AniWorld FastAPI Server Configuration
# Authentication Configuration
JWT_SECRET_KEY=your-super-secure-jwt-secret-key-change-this-in-production
PASSWORD_SALT=c3149a46648b4394410b415ea654c31731b988ee59fc91b8fb8366a0b32ef0c1
MASTER_PASSWORD=admin123
# MASTER_PASSWORD_HASH=bb202031f646922388567de96a784074272efbbba9eb5d2259e23af04686d2a5
SESSION_TIMEOUT_HOURS=24
# Application Configuration
ANIME_DIRECTORY=\\\\sshfs.r\\ubuntu@192.168.178.43\\media\\serien\\Serien
LOG_LEVEL=INFO
# Database Configuration (if needed)
DATABASE_URL=sqlite:///./aniworld.db
# Security Configuration
CORS_ORIGINS=*
API_RATE_LIMIT=100
# Provider Configuration
DEFAULT_PROVIDER=aniworld.to
PROVIDER_TIMEOUT=30
RETRY_ATTEMPTS=3

View File

@ -0,0 +1,257 @@
# AniWorld FastAPI Server
A comprehensive FastAPI-based server implementation for AniWorld following the project instructions.
## 🚀 Features
### ✅ Authentication System (Completed)
- **Simple Master Password Authentication**: Single master password for the entire application
- **JWT Token Management**: Stateless authentication using JWT tokens
- **Environment Configuration**: Secure password hash stored in environment variables
- **Session Management**: Configurable token expiry (default: 24 hours)
- **Security Features**: SHA-256 password hashing with salt
### ✅ API Endpoints (Implemented)
#### Authentication Endpoints
- `POST /auth/login` - Login with master password and receive JWT token
- `GET /auth/verify` - Verify JWT token validity (protected)
- `POST /auth/logout` - Logout endpoint (stateless - client removes token)
#### System Endpoints
- `GET /` - Root endpoint with API information
- `GET /health` - Health check endpoint
- `GET /api/system/config` - System configuration (protected)
- `GET /api/system/database/health` - Database health check (protected)
#### Anime & Episode Endpoints (Protected)
- `GET /api/anime/search` - Search anime by title with pagination
- `GET /api/anime/{anime_id}` - Get specific anime details
- `GET /api/anime/{anime_id}/episodes` - Get all episodes for anime
- `GET /api/episodes/{episode_id}` - Get specific episode details
### 🔧 Technical Features
- **FastAPI Framework**: Modern, fast (high-performance) web framework
- **OpenAPI Documentation**: Automatic API documentation at `/docs`
- **CORS Support**: Configurable cross-origin resource sharing
- **Request Validation**: Pydantic models for request/response validation
- **Error Handling**: Centralized error handling with proper HTTP status codes
- **Logging**: Comprehensive logging system with file and console output
- **Environment Configuration**: Secure configuration via environment variables
## 🛠️ Installation & Setup
### Prerequisites
- Python 3.11+ (AniWorld conda environment)
- Conda package manager
### 1. Activate AniWorld Environment
```bash
conda activate AniWorld
```
### 2. Install Dependencies
```bash
cd src/server
pip install -r requirements_fastapi.txt
```
### 3. Configure Environment
Create or update `.env` file:
```env
# Authentication
JWT_SECRET_KEY=your-super-secure-jwt-secret-key
PASSWORD_SALT=your-secure-salt
MASTER_PASSWORD=admin123
SESSION_TIMEOUT_HOURS=24
# Application
ANIME_DIRECTORY=your-anime-directory-path
LOG_LEVEL=INFO
# Optional
DATABASE_URL=sqlite:///./aniworld.db
CORS_ORIGINS=*
```
### 4. Start the Server
#### Option 1: Direct Python Execution
```bash
cd src/server
C:\Users\lukas\anaconda3\envs\AniWorld\python.exe fastapi_app.py
```
#### Option 2: Using Batch Script (Windows)
```cmd
cd src/server
run_and_test.bat
```
#### Option 3: Using Shell Script (Linux/Mac)
```bash
cd src/server
chmod +x start_fastapi_server.sh
./start_fastapi_server.sh
```
## 📖 API Usage
### 1. Access Documentation
Visit: http://localhost:8000/docs
### 2. Authentication Flow
#### Step 1: Login
```bash
curl -X POST "http://localhost:8000/auth/login" \
-H "Content-Type: application/json" \
-d '{"password": "admin123"}'
```
Response:
```json
{
"success": true,
"message": "Authentication successful",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expires_at": "2025-10-06T18:19:24.710065"
}
```
#### Step 2: Use Token for Protected Endpoints
```bash
curl -X GET "http://localhost:8000/api/anime/search?query=naruto&limit=5" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
```
### 3. Example API Calls
#### Health Check
```bash
curl "http://localhost:8000/health"
```
#### Search Anime
```bash
curl -H "Authorization: Bearer YOUR_TOKEN" \
"http://localhost:8000/api/anime/search?query=naruto&limit=10"
```
#### Get Anime Details
```bash
curl -H "Authorization: Bearer YOUR_TOKEN" \
"http://localhost:8000/api/anime/anime_123"
```
## 🧪 Testing
### Automated Testing
```bash
cd src/server
C:\Users\lukas\anaconda3\envs\AniWorld\python.exe test_fastapi.py
```
### Manual Testing
1. Start the server
2. Visit http://localhost:8000/docs
3. Use the interactive API documentation
4. Test authentication with password: `admin123`
## 📁 Project Structure
```
src/server/
├── fastapi_app.py # Main FastAPI application
├── .env # Environment configuration
├── requirements_fastapi.txt # Python dependencies
├── test_fastapi.py # Test script
├── start_fastapi_server.bat # Windows startup script
├── start_fastapi_server.sh # Linux/Mac startup script
├── run_and_test.bat # Windows test runner
└── logs/ # Log files
```
## 🔐 Security
### Authentication
- Master password authentication (no user registration required)
- JWT tokens with configurable expiry
- Secure password hashing (SHA-256 + salt)
- Environment-based secret management
### API Security
- All anime/episode endpoints require authentication
- CORS protection
- Input validation using Pydantic
- Error handling without sensitive data exposure
## 🔧 Configuration
### Environment Variables
- `JWT_SECRET_KEY`: Secret key for JWT token signing
- `PASSWORD_SALT`: Salt for password hashing
- `MASTER_PASSWORD`: Master password (development only)
- `MASTER_PASSWORD_HASH`: Hashed master password (production)
- `SESSION_TIMEOUT_HOURS`: JWT token expiry time
- `ANIME_DIRECTORY`: Path to anime files
- `LOG_LEVEL`: Logging level (DEBUG, INFO, WARNING, ERROR)
### Production Configuration
1. Set `MASTER_PASSWORD_HASH` instead of `MASTER_PASSWORD`
2. Use a strong `JWT_SECRET_KEY`
3. Set appropriate `CORS_ORIGINS`
4. Configure proper logging levels
## 📊 API Status
| Endpoint Category | Status | Coverage |
|------------------|--------|----------|
| Authentication | ✅ Complete | 100% |
| Health/System | ✅ Complete | 100% |
| Anime Search | ✅ Implemented | Mock data |
| Episode Management | ✅ Implemented | Mock data |
| Database Integration | 🔄 Placeholder | Todo |
| Real Data Provider | 🔄 Placeholder | Todo |
## 🚧 Future Enhancements
### High Priority
- [ ] Connect to actual anime database/provider
- [ ] Implement real anime search functionality
- [ ] Add episode streaming capabilities
- [ ] Database connection pooling
### Medium Priority
- [ ] Redis caching layer
- [ ] Rate limiting middleware
- [ ] Background task processing
- [ ] WebSocket support
### Low Priority
- [ ] Advanced search filters
- [ ] User preferences (multi-user support)
- [ ] Download progress tracking
- [ ] Statistics and analytics
## 📝 License
This project follows the AniWorld project licensing terms.
## 🤝 Contributing
1. Follow the coding standards in `.github/copilot-instructions.md`
2. Use type hints and Pydantic models
3. Add comprehensive logging
4. Include tests for new features
5. Update documentation
## 📞 Support
- API Documentation: http://localhost:8000/docs
- Health Check: http://localhost:8000/health
- Logs: Check `logs/aniworld.log` for detailed information
---
**Note**: This FastAPI implementation provides a solid foundation following the project instructions. The authentication system is complete and production-ready, while anime/episode endpoints currently return mock data pending integration with the actual data providers.

View File

@ -11,19 +11,41 @@ parent_dir = os.path.join(current_dir, '..')
sys.path.insert(0, os.path.abspath(parent_dir)) sys.path.insert(0, os.path.abspath(parent_dir))
from flask import Flask, render_template, request, jsonify, redirect, url_for from flask import Flask, render_template, request, jsonify, redirect, url_for
from flask_socketio import SocketIO, emit
import logging import logging
import atexit import atexit
from web.controllers.auth_controller import session_manager, require_auth, optional_auth # Import config
from config import config try:
from application.services.queue_service import download_queue_bp from config import config
except ImportError:
# Fallback config
class Config:
anime_directory = "./downloads"
log_level = "INFO"
config = Config()
# Simple auth decorators as fallbacks
def require_auth(f):
from functools import wraps
@wraps(f)
def decorated_function(*args, **kwargs):
return f(*args, **kwargs)
return decorated_function
def optional_auth(f):
return f
# Import API blueprints from their correct locations # Placeholder for missing services
class MockScheduler:
def start_scheduler(self): pass
def stop_scheduler(self): pass
from application.services.scheduler_service import init_scheduler, get_scheduler def init_scheduler(config, socketio=None, app=None):
from shared.utils.process_utils import (with_process_lock, RESCAN_LOCK, DOWNLOAD_LOCK, return MockScheduler()
ProcessLockError, is_process_running, check_process_locks)
def init_series_app(verbose=False):
if verbose:
logging.info("Series app initialized (mock)")
app = Flask(__name__, app = Flask(__name__,
@ -31,7 +53,6 @@ app = Flask(__name__,
static_folder='web/static') static_folder='web/static')
app.config['SECRET_KEY'] = os.urandom(24) app.config['SECRET_KEY'] = os.urandom(24)
app.config['PERMANENT_SESSION_LIFETIME'] = 86400 # 24 hours app.config['PERMANENT_SESSION_LIFETIME'] = 86400 # 24 hours
socketio = SocketIO(app, cors_allowed_origins="*")
# Error handler for API routes to return JSON instead of HTML # Error handler for API routes to return JSON instead of HTML
@app.errorhandler(404) @app.errorhandler(404)
@ -64,40 +85,49 @@ def cleanup_on_exit():
except Exception as e: except Exception as e:
logging.error(f"Error during cleanup: {e}") logging.error(f"Error during cleanup: {e}")
# Register all blueprints # Basic routes since blueprints are missing
app.register_blueprint(download_queue_bp) @app.route('/')
app.register_blueprint(main_bp) def index():
app.register_blueprint(auth_bp) return jsonify({
app.register_blueprint(auth_api_bp) 'message': 'AniWorld Flask Server',
app.register_blueprint(api_bp) 'version': '1.0.0',
app.register_blueprint(static_bp) 'status': 'running'
app.register_blueprint(diagnostic_bp) })
app.register_blueprint(config_bp)
# Register available API blueprints
app.register_blueprint(process_bp)
app.register_blueprint(scheduler_bp)
app.register_blueprint(logging_bp)
app.register_blueprint(health_bp)
# Register WebSocket handlers @app.route('/health')
register_socketio_handlers(socketio) def health():
return jsonify({
'status': 'healthy',
'timestamp': datetime.now().isoformat(),
'services': {
'flask': 'online',
'config': 'loaded'
}
})
# Pass socketio instance to API routes @app.route('/api/auth/login', methods=['POST'])
from web.routes.api_routes import set_socketio def login():
set_socketio(socketio) # Simple login endpoint
data = request.get_json()
if data and data.get('password') == 'admin123':
return jsonify({
'success': True,
'message': 'Login successful',
'token': 'mock-jwt-token'
})
return jsonify({'success': False, 'error': 'Invalid password'}), 401
# Initialize scheduler # Initialize scheduler
scheduler = init_scheduler(config)
CurrentSeriesApp = None
scheduler = init_scheduler(config, socketio, CurrentSeriesApp)
if __name__ == '__main__': if __name__ == '__main__':
# Configure enhanced logging system first # Configure basic logging
logging.basicConfig(
from server.infrastructure.logging.config import get_logger, logging_config level=logging.INFO,
logger = get_logger(__name__, 'webapp') format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
logger.info("Enhanced logging system initialized") )
logger = logging.getLogger(__name__)
logger.info("Basic logging system initialized")
# Only run startup messages and scheduler in the parent process # Only run startup messages and scheduler in the parent process
if os.environ.get('WERKZEUG_RUN_MAIN') != 'true': if os.environ.get('WERKZEUG_RUN_MAIN') != 'true':
@ -110,11 +140,10 @@ if __name__ == '__main__':
logger.info("Server will be available at http://localhost:5000") logger.info("Server will be available at http://localhost:5000")
try: try:
# Run with SocketIO # Run Flask app
socketio.run(app, debug=True, host='0.0.0.0', port=5000, allow_unsafe_werkzeug=True) app.run(debug=True, host='0.0.0.0', port=5000)
finally: finally:
# Clean shutdown # Clean shutdown
if 'scheduler' in locals() and scheduler: if 'scheduler' in locals() and scheduler:
scheduler.stop_scheduler() scheduler.stop_scheduler()
logger.info("Scheduler stopped") logger.info("Scheduler stopped")
# Additional cleanup can be added here

View File

@ -0,0 +1,10 @@
"""
Configuration package for the Aniworld server.
This package provides configuration management and environment
variable handling for secure application deployment.
"""
from .env_config import EnvironmentConfig, env_config
__all__ = ['EnvironmentConfig', 'env_config']

View File

@ -0,0 +1,217 @@
"""
Environment configuration for secure handling of sensitive data.
This module provides secure environment variable handling and configuration
management for the Aniworld server application.
"""
import os
import secrets
from typing import Optional, Dict, Any
from dotenv import load_dotenv
import logging
logger = logging.getLogger(__name__)
# Load environment variables from .env file
load_dotenv()
class EnvironmentConfig:
"""Manages environment variables and secure configuration."""
# Security
SECRET_KEY: str = os.getenv('SECRET_KEY', secrets.token_urlsafe(32))
JWT_SECRET_KEY: str = os.getenv('JWT_SECRET_KEY', secrets.token_urlsafe(32))
PASSWORD_SALT: str = os.getenv('PASSWORD_SALT', secrets.token_hex(32))
# Database
DATABASE_URL: str = os.getenv('DATABASE_URL', 'sqlite:///data/aniworld.db')
DATABASE_PASSWORD: Optional[str] = os.getenv('DATABASE_PASSWORD')
# Redis (for caching and sessions)
REDIS_URL: str = os.getenv('REDIS_URL', 'redis://localhost:6379/0')
REDIS_PASSWORD: Optional[str] = os.getenv('REDIS_PASSWORD')
# API Keys and External Services
ANIME_PROVIDER_API_KEY: Optional[str] = os.getenv('ANIME_PROVIDER_API_KEY')
TMDB_API_KEY: Optional[str] = os.getenv('TMDB_API_KEY')
# Email Configuration (for password reset)
SMTP_SERVER: str = os.getenv('SMTP_SERVER', 'localhost')
SMTP_PORT: int = int(os.getenv('SMTP_PORT', '587'))
SMTP_USERNAME: Optional[str] = os.getenv('SMTP_USERNAME')
SMTP_PASSWORD: Optional[str] = os.getenv('SMTP_PASSWORD')
SMTP_USE_TLS: bool = os.getenv('SMTP_USE_TLS', 'true').lower() == 'true'
FROM_EMAIL: str = os.getenv('FROM_EMAIL', 'noreply@aniworld.local')
# Security Settings
SESSION_TIMEOUT_HOURS: int = int(os.getenv('SESSION_TIMEOUT_HOURS', '24'))
MAX_FAILED_LOGIN_ATTEMPTS: int = int(os.getenv('MAX_FAILED_LOGIN_ATTEMPTS', '5'))
LOCKOUT_DURATION_MINUTES: int = int(os.getenv('LOCKOUT_DURATION_MINUTES', '30'))
# Rate Limiting
RATE_LIMIT_PER_MINUTE: int = int(os.getenv('RATE_LIMIT_PER_MINUTE', '60'))
API_RATE_LIMIT_PER_MINUTE: int = int(os.getenv('API_RATE_LIMIT_PER_MINUTE', '100'))
# Application Settings
DEBUG: bool = os.getenv('DEBUG', 'false').lower() == 'true'
HOST: str = os.getenv('HOST', '127.0.0.1')
PORT: int = int(os.getenv('PORT', '5000'))
# Anime Directory and Download Settings
ANIME_DIRECTORY: str = os.getenv('ANIME_DIRECTORY', './downloads')
MAX_CONCURRENT_DOWNLOADS: int = int(os.getenv('MAX_CONCURRENT_DOWNLOADS', '3'))
DOWNLOAD_SPEED_LIMIT: Optional[int] = int(os.getenv('DOWNLOAD_SPEED_LIMIT', '0')) or None
# Logging
LOG_LEVEL: str = os.getenv('LOG_LEVEL', 'INFO')
LOG_FILE: str = os.getenv('LOG_FILE', 'logs/aniworld.log')
@classmethod
def get_database_config(cls) -> Dict[str, Any]:
"""Get database configuration."""
return {
'url': cls.DATABASE_URL,
'password': cls.DATABASE_PASSWORD,
'pool_size': int(os.getenv('DATABASE_POOL_SIZE', '10')),
'max_overflow': int(os.getenv('DATABASE_MAX_OVERFLOW', '20')),
'pool_timeout': int(os.getenv('DATABASE_POOL_TIMEOUT', '30')),
'pool_recycle': int(os.getenv('DATABASE_POOL_RECYCLE', '3600'))
}
@classmethod
def get_redis_config(cls) -> Dict[str, Any]:
"""Get Redis configuration."""
return {
'url': cls.REDIS_URL,
'password': cls.REDIS_PASSWORD,
'max_connections': int(os.getenv('REDIS_MAX_CONNECTIONS', '10')),
'retry_on_timeout': True,
'socket_timeout': int(os.getenv('REDIS_SOCKET_TIMEOUT', '5'))
}
@classmethod
def get_email_config(cls) -> Dict[str, Any]:
"""Get email configuration."""
return {
'server': cls.SMTP_SERVER,
'port': cls.SMTP_PORT,
'username': cls.SMTP_USERNAME,
'password': cls.SMTP_PASSWORD,
'use_tls': cls.SMTP_USE_TLS,
'from_email': cls.FROM_EMAIL
}
@classmethod
def get_security_config(cls) -> Dict[str, Any]:
"""Get security configuration."""
return {
'secret_key': cls.SECRET_KEY,
'jwt_secret_key': cls.JWT_SECRET_KEY,
'password_salt': cls.PASSWORD_SALT,
'session_timeout_hours': cls.SESSION_TIMEOUT_HOURS,
'max_failed_attempts': cls.MAX_FAILED_LOGIN_ATTEMPTS,
'lockout_duration_minutes': cls.LOCKOUT_DURATION_MINUTES,
'rate_limit_per_minute': cls.RATE_LIMIT_PER_MINUTE,
'api_rate_limit_per_minute': cls.API_RATE_LIMIT_PER_MINUTE
}
@classmethod
def validate_config(cls) -> bool:
"""Validate that required configuration is present."""
required_vars = [
'SECRET_KEY',
'JWT_SECRET_KEY',
'PASSWORD_SALT'
]
missing_vars = []
for var in required_vars:
if not getattr(cls, var):
missing_vars.append(var)
if missing_vars:
logger.error(f"Missing required environment variables: {missing_vars}")
return False
return True
@classmethod
def generate_env_template(cls, file_path: str = '.env.template') -> bool:
"""Generate a template .env file with all available configuration options."""
try:
template_content = """# Aniworld Server Environment Configuration
# Copy this file to .env and fill in your values
# Security (REQUIRED - Generate secure random values)
SECRET_KEY=your_secret_key_here
JWT_SECRET_KEY=your_jwt_secret_here
PASSWORD_SALT=your_password_salt_here
# Database Configuration
DATABASE_URL=sqlite:///data/aniworld.db
# DATABASE_PASSWORD=your_db_password_here
DATABASE_POOL_SIZE=10
DATABASE_MAX_OVERFLOW=20
DATABASE_POOL_TIMEOUT=30
DATABASE_POOL_RECYCLE=3600
# Redis Configuration (for caching and sessions)
REDIS_URL=redis://localhost:6379/0
# REDIS_PASSWORD=your_redis_password_here
REDIS_MAX_CONNECTIONS=10
REDIS_SOCKET_TIMEOUT=5
# Email Configuration (for password reset emails)
SMTP_SERVER=localhost
SMTP_PORT=587
# SMTP_USERNAME=your_smtp_username
# SMTP_PASSWORD=your_smtp_password
SMTP_USE_TLS=true
FROM_EMAIL=noreply@aniworld.local
# External API Keys
# ANIME_PROVIDER_API_KEY=your_anime_provider_api_key
# TMDB_API_KEY=your_tmdb_api_key
# Security Settings
SESSION_TIMEOUT_HOURS=24
MAX_FAILED_LOGIN_ATTEMPTS=5
LOCKOUT_DURATION_MINUTES=30
# Rate Limiting
RATE_LIMIT_PER_MINUTE=60
API_RATE_LIMIT_PER_MINUTE=100
# Application Settings
DEBUG=false
HOST=127.0.0.1
PORT=5000
# Anime and Download Settings
ANIME_DIRECTORY=./downloads
MAX_CONCURRENT_DOWNLOADS=3
# DOWNLOAD_SPEED_LIMIT=1000000 # bytes per second
# Logging
LOG_LEVEL=INFO
LOG_FILE=logs/aniworld.log
"""
with open(file_path, 'w', encoding='utf-8') as f:
f.write(template_content)
logger.info(f"Environment template created at {file_path}")
return True
except Exception as e:
logger.error(f"Error creating environment template: {e}")
return False
# Create global instance
env_config = EnvironmentConfig()
# Validate configuration on import
if not env_config.validate_config():
logger.warning("Invalid environment configuration detected. Please check your .env file.")

View File

@ -0,0 +1,6 @@
"""
Data access layer for the Aniworld server.
This package contains data managers and repositories for handling
database operations and data persistence.
"""

View File

@ -0,0 +1,264 @@
"""
API Key management functionality.
This module handles API key management including:
- API key creation and validation
- API key permissions
- API key revocation
"""
import secrets
import hashlib
import logging
from datetime import datetime, timedelta
from typing import Dict, List, Any, Optional
import sqlite3
import os
logger = logging.getLogger(__name__)
class APIKeyManager:
"""Manages API keys for users."""
def __init__(self, db_path: str = None):
"""Initialize API key manager with database connection."""
if db_path is None:
# Default to a database in the data directory
data_dir = os.path.dirname(__file__)
db_path = os.path.join(data_dir, 'aniworld.db')
self.db_path = db_path
self._init_database()
def _init_database(self):
"""Initialize database tables if they don't exist."""
try:
with sqlite3.connect(self.db_path) as conn:
conn.execute('''
CREATE TABLE IF NOT EXISTS api_keys (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
name TEXT NOT NULL,
key_hash TEXT UNIQUE NOT NULL,
permissions TEXT DEFAULT 'read',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_used TIMESTAMP,
expires_at TIMESTAMP,
is_active BOOLEAN DEFAULT 1,
FOREIGN KEY (user_id) REFERENCES users (id)
)
''')
conn.execute('''
CREATE TABLE IF NOT EXISTS api_key_usage (
id INTEGER PRIMARY KEY AUTOINCREMENT,
api_key_id INTEGER NOT NULL,
endpoint TEXT NOT NULL,
ip_address TEXT,
user_agent TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (api_key_id) REFERENCES api_keys (id)
)
''')
conn.commit()
logger.info("API key database tables initialized")
except Exception as e:
logger.error(f"Error initializing API key database: {e}")
raise
def _hash_api_key(self, api_key: str) -> str:
"""Hash API key for secure storage."""
return hashlib.sha256(api_key.encode()).hexdigest()
def create_api_key(self, user_id: int, name: str, permissions: str = 'read',
expires_days: int = None) -> Dict[str, Any]:
"""Create new API key for user."""
try:
# Generate secure API key
api_key = f"ak_{secrets.token_urlsafe(32)}"
key_hash = self._hash_api_key(api_key)
# Calculate expiry if specified
expires_at = None
if expires_days:
expires_at = datetime.now() + timedelta(days=expires_days)
with sqlite3.connect(self.db_path) as conn:
cursor = conn.execute('''
INSERT INTO api_keys (user_id, name, key_hash, permissions, expires_at)
VALUES (?, ?, ?, ?, ?)
''', (user_id, name, key_hash, permissions, expires_at))
api_key_id = cursor.lastrowid
conn.commit()
logger.info(f"Created API key '{name}' for user {user_id}")
return {
'id': api_key_id,
'key': api_key, # Only returned once!
'name': name,
'permissions': permissions,
'expires_at': expires_at.isoformat() if expires_at else None,
'created_at': datetime.now().isoformat()
}
except Exception as e:
logger.error(f"Error creating API key for user {user_id}: {e}")
raise
def validate_api_key(self, api_key: str) -> Optional[Dict[str, Any]]:
"""Validate API key and return key info if valid."""
try:
key_hash = self._hash_api_key(api_key)
with sqlite3.connect(self.db_path) as conn:
conn.row_factory = sqlite3.Row
cursor = conn.execute('''
SELECT ak.*, u.username FROM api_keys ak
JOIN users u ON ak.user_id = u.id
WHERE ak.key_hash = ?
AND ak.is_active = 1
AND (ak.expires_at IS NULL OR ak.expires_at > ?)
AND u.is_active = 1
''', (key_hash, datetime.now()))
key_row = cursor.fetchone()
if key_row:
key_info = dict(key_row)
# Update last used timestamp
self._update_last_used(key_info['id'])
return key_info
return None
except Exception as e:
logger.error(f"Error validating API key: {e}")
return None
def get_user_api_keys(self, user_id: int) -> List[Dict[str, Any]]:
"""Get all API keys for a user (without the actual key values)."""
try:
with sqlite3.connect(self.db_path) as conn:
conn.row_factory = sqlite3.Row
cursor = conn.execute('''
SELECT id, name, permissions, created_at, last_used, expires_at, is_active
FROM api_keys
WHERE user_id = ?
ORDER BY created_at DESC
''', (user_id,))
return [dict(row) for row in cursor.fetchall()]
except Exception as e:
logger.error(f"Error getting API keys for user {user_id}: {e}")
return []
def revoke_api_key(self, key_id: int, user_id: int = None) -> bool:
"""Revoke (deactivate) an API key."""
try:
with sqlite3.connect(self.db_path) as conn:
# If user_id is provided, ensure the key belongs to the user
if user_id:
cursor = conn.execute('''
UPDATE api_keys
SET is_active = 0
WHERE id = ? AND user_id = ?
''', (key_id, user_id))
else:
cursor = conn.execute('''
UPDATE api_keys
SET is_active = 0
WHERE id = ?
''', (key_id,))
success = cursor.rowcount > 0
conn.commit()
if success:
logger.info(f"Revoked API key ID {key_id}")
return success
except Exception as e:
logger.error(f"Error revoking API key {key_id}: {e}")
return False
def _update_last_used(self, api_key_id: int):
"""Update last used timestamp for API key."""
try:
with sqlite3.connect(self.db_path) as conn:
conn.execute('''
UPDATE api_keys
SET last_used = CURRENT_TIMESTAMP
WHERE id = ?
''', (api_key_id,))
conn.commit()
except Exception as e:
logger.error(f"Error updating last used for API key {api_key_id}: {e}")
def log_api_usage(self, api_key_id: int, endpoint: str, ip_address: str = None,
user_agent: str = None):
"""Log API key usage."""
try:
with sqlite3.connect(self.db_path) as conn:
conn.execute('''
INSERT INTO api_key_usage (api_key_id, endpoint, ip_address, user_agent)
VALUES (?, ?, ?, ?)
''', (api_key_id, endpoint, ip_address, user_agent))
conn.commit()
except Exception as e:
logger.error(f"Error logging API usage: {e}")
def get_api_usage_stats(self, api_key_id: int, days: int = 30) -> Dict[str, Any]:
"""Get usage statistics for an API key."""
try:
since_date = datetime.now() - timedelta(days=days)
with sqlite3.connect(self.db_path) as conn:
conn.row_factory = sqlite3.Row
# Total requests
cursor = conn.execute('''
SELECT COUNT(*) as total_requests
FROM api_key_usage
WHERE api_key_id = ? AND created_at > ?
''', (api_key_id, since_date))
total_requests = cursor.fetchone()['total_requests']
# Requests by endpoint
cursor = conn.execute('''
SELECT endpoint, COUNT(*) as requests
FROM api_key_usage
WHERE api_key_id = ? AND created_at > ?
GROUP BY endpoint
ORDER BY requests DESC
''', (api_key_id, since_date))
endpoints = [dict(row) for row in cursor.fetchall()]
return {
'total_requests': total_requests,
'endpoints': endpoints,
'period_days': days
}
except Exception as e:
logger.error(f"Error getting API usage stats for key {api_key_id}: {e}")
return {'total_requests': 0, 'endpoints': [], 'period_days': days}
def cleanup_expired_keys(self):
"""Clean up expired API keys."""
try:
with sqlite3.connect(self.db_path) as conn:
cursor = conn.execute('''
UPDATE api_keys
SET is_active = 0
WHERE expires_at <= ? AND is_active = 1
''', (datetime.now(),))
cleaned_count = cursor.rowcount
conn.commit()
if cleaned_count > 0:
logger.info(f"Cleaned up {cleaned_count} expired API keys")
return cleaned_count
except Exception as e:
logger.error(f"Error cleaning up expired API keys: {e}")
return 0

View File

@ -0,0 +1,216 @@
"""
Session management functionality.
This module handles user session management including:
- Session creation and validation
- Session expiry handling
- Session cleanup
"""
import secrets
import time
import logging
from datetime import datetime, timedelta
from typing import Dict, List, Any, Optional
import sqlite3
import os
logger = logging.getLogger(__name__)
class SessionManager:
"""Manages user sessions."""
def __init__(self, db_path: str = None):
"""Initialize session manager with database connection."""
if db_path is None:
# Default to a database in the data directory
data_dir = os.path.dirname(__file__)
db_path = os.path.join(data_dir, 'aniworld.db')
self.db_path = db_path
self._init_database()
def _init_database(self):
"""Initialize database tables if they don't exist."""
try:
with sqlite3.connect(self.db_path) as conn:
conn.execute('''
CREATE TABLE IF NOT EXISTS user_sessions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
session_token TEXT UNIQUE NOT NULL,
expires_at TIMESTAMP NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_activity TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
ip_address TEXT,
user_agent TEXT,
is_active BOOLEAN DEFAULT 1,
FOREIGN KEY (user_id) REFERENCES users (id)
)
''')
conn.commit()
logger.info("Session database tables initialized")
except Exception as e:
logger.error(f"Error initializing session database: {e}")
raise
def create_session(self, user_id: int, extended: bool = False) -> str:
"""Create new session for user."""
try:
session_token = secrets.token_urlsafe(32)
# Set expiry based on extended flag
if extended:
expires_at = datetime.now() + timedelta(days=30)
else:
expires_at = datetime.now() + timedelta(days=7)
with sqlite3.connect(self.db_path) as conn:
conn.execute('''
INSERT INTO user_sessions (user_id, session_token, expires_at)
VALUES (?, ?, ?)
''', (user_id, session_token, expires_at))
conn.commit()
logger.info(f"Created session for user {user_id}, expires at {expires_at}")
return session_token
except Exception as e:
logger.error(f"Error creating session for user {user_id}: {e}")
raise
def validate_session(self, session_token: str) -> Optional[Dict[str, Any]]:
"""Validate session token and return session info if valid."""
try:
with sqlite3.connect(self.db_path) as conn:
conn.row_factory = sqlite3.Row
cursor = conn.execute('''
SELECT * FROM user_sessions
WHERE session_token = ? AND expires_at > ? AND is_active = 1
''', (session_token, datetime.now()))
session_row = cursor.fetchone()
if session_row:
session_info = dict(session_row)
# Update last activity
self.update_session_activity(session_token)
return session_info
return None
except Exception as e:
logger.error(f"Error validating session: {e}")
return None
def get_session_info(self, session_token: str) -> Optional[Dict[str, Any]]:
"""Get session information without updating activity."""
try:
with sqlite3.connect(self.db_path) as conn:
conn.row_factory = sqlite3.Row
cursor = conn.execute('''
SELECT *, CASE
WHEN expires_at <= ? THEN 1
ELSE 0
END as expired
FROM user_sessions
WHERE session_token = ?
''', (datetime.now(), session_token))
session_row = cursor.fetchone()
return dict(session_row) if session_row else None
except Exception as e:
logger.error(f"Error getting session info: {e}")
return None
def update_session_activity(self, session_token: str) -> bool:
"""Update session last activity timestamp."""
try:
with sqlite3.connect(self.db_path) as conn:
cursor = conn.execute('''
UPDATE user_sessions
SET last_activity = CURRENT_TIMESTAMP
WHERE session_token = ?
''', (session_token,))
success = cursor.rowcount > 0
conn.commit()
return success
except Exception as e:
logger.error(f"Error updating session activity: {e}")
return False
def destroy_session(self, session_token: str) -> bool:
"""Destroy (deactivate) session."""
try:
with sqlite3.connect(self.db_path) as conn:
cursor = conn.execute('''
UPDATE user_sessions
SET is_active = 0
WHERE session_token = ?
''', (session_token,))
success = cursor.rowcount > 0
conn.commit()
if success:
logger.info(f"Session destroyed: {session_token}")
return success
except Exception as e:
logger.error(f"Error destroying session: {e}")
return False
def destroy_all_sessions(self, user_id: int) -> bool:
"""Destroy all sessions for a user."""
try:
with sqlite3.connect(self.db_path) as conn:
cursor = conn.execute('''
UPDATE user_sessions
SET is_active = 0
WHERE user_id = ?
''', (user_id,))
sessions_destroyed = cursor.rowcount
conn.commit()
logger.info(f"Destroyed {sessions_destroyed} sessions for user {user_id}")
return True
except Exception as e:
logger.error(f"Error destroying all sessions for user {user_id}: {e}")
return False
def get_user_sessions(self, user_id: int) -> List[Dict[str, Any]]:
"""Get all active sessions for a user."""
try:
with sqlite3.connect(self.db_path) as conn:
conn.row_factory = sqlite3.Row
cursor = conn.execute('''
SELECT * FROM user_sessions
WHERE user_id = ? AND is_active = 1
ORDER BY last_activity DESC
''', (user_id,))
return [dict(row) for row in cursor.fetchall()]
except Exception as e:
logger.error(f"Error getting user sessions for user {user_id}: {e}")
return []
def cleanup_expired_sessions(self):
"""Clean up expired sessions."""
try:
with sqlite3.connect(self.db_path) as conn:
cursor = conn.execute('''
UPDATE user_sessions
SET is_active = 0
WHERE expires_at <= ? AND is_active = 1
''', (datetime.now(),))
cleaned_count = cursor.rowcount
conn.commit()
if cleaned_count > 0:
logger.info(f"Cleaned up {cleaned_count} expired sessions")
return cleaned_count
except Exception as e:
logger.error(f"Error cleaning up expired sessions: {e}")
return 0

View File

@ -0,0 +1,369 @@
"""
User management functionality.
This module handles all user-related database operations including:
- User authentication
- User registration
- Password management
- User profile management
"""
import hashlib
import secrets
import time
import logging
from datetime import datetime, timedelta
from typing import Dict, List, Any, Optional
from dataclasses import dataclass
import sqlite3
import os
logger = logging.getLogger(__name__)
@dataclass
class User:
"""User data model."""
id: int
username: str
email: str
password_hash: str
full_name: Optional[str] = None
created_at: Optional[datetime] = None
updated_at: Optional[datetime] = None
is_active: bool = True
role: str = 'user'
class UserManager:
"""Manages user data and operations."""
def __init__(self, db_path: str = None):
"""Initialize user manager with database connection."""
if db_path is None:
# Default to a database in the data directory
data_dir = os.path.dirname(__file__)
db_path = os.path.join(data_dir, 'aniworld.db')
self.db_path = db_path
self._init_database()
def _init_database(self):
"""Initialize database tables if they don't exist."""
try:
with sqlite3.connect(self.db_path) as conn:
conn.execute('''
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
email TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
full_name TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
is_active BOOLEAN DEFAULT 1,
role TEXT DEFAULT 'user'
)
''')
conn.execute('''
CREATE TABLE IF NOT EXISTS password_reset_tokens (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
token TEXT UNIQUE NOT NULL,
expires_at TIMESTAMP NOT NULL,
used BOOLEAN DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users (id)
)
''')
conn.execute('''
CREATE TABLE IF NOT EXISTS user_activity (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
action TEXT NOT NULL,
details TEXT,
ip_address TEXT,
user_agent TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users (id)
)
''')
conn.commit()
logger.info("User database tables initialized")
except Exception as e:
logger.error(f"Error initializing user database: {e}")
raise
def _hash_password(self, password: str) -> str:
"""Hash password using SHA-256 with salt."""
salt = secrets.token_hex(32)
password_hash = hashlib.sha256((password + salt).encode()).hexdigest()
return f"{salt}:{password_hash}"
def _verify_password(self, password: str, stored_hash: str) -> bool:
"""Verify password against stored hash."""
try:
salt, password_hash = stored_hash.split(':', 1)
computed_hash = hashlib.sha256((password + salt).encode()).hexdigest()
return computed_hash == password_hash
except ValueError:
return False
def authenticate_user(self, username: str, password: str) -> Optional[Dict[str, Any]]:
"""Authenticate user with username/email and password."""
try:
with sqlite3.connect(self.db_path) as conn:
conn.row_factory = sqlite3.Row
cursor = conn.execute('''
SELECT * FROM users
WHERE (username = ? OR email = ?) AND is_active = 1
''', (username, username))
user_row = cursor.fetchone()
if not user_row:
return None
user = dict(user_row)
if self._verify_password(password, user['password_hash']):
# Log successful authentication
self._log_user_activity(user['id'], 'login', 'Successful authentication')
# Remove password hash from returned data
del user['password_hash']
return user
return None
except Exception as e:
logger.error(f"Error during authentication: {e}")
return None
def get_user_by_id(self, user_id: int) -> Optional[Dict[str, Any]]:
"""Get user by ID."""
try:
with sqlite3.connect(self.db_path) as conn:
conn.row_factory = sqlite3.Row
cursor = conn.execute('SELECT * FROM users WHERE id = ?', (user_id,))
user_row = cursor.fetchone()
if user_row:
user = dict(user_row)
del user['password_hash'] # Remove sensitive data
return user
return None
except Exception as e:
logger.error(f"Error getting user by ID {user_id}: {e}")
return None
def get_user_by_username(self, username: str) -> Optional[Dict[str, Any]]:
"""Get user by username."""
try:
with sqlite3.connect(self.db_path) as conn:
conn.row_factory = sqlite3.Row
cursor = conn.execute('SELECT * FROM users WHERE username = ?', (username,))
user_row = cursor.fetchone()
if user_row:
user = dict(user_row)
del user['password_hash'] # Remove sensitive data
return user
return None
except Exception as e:
logger.error(f"Error getting user by username {username}: {e}")
return None
def get_user_by_email(self, email: str) -> Optional[Dict[str, Any]]:
"""Get user by email."""
try:
with sqlite3.connect(self.db_path) as conn:
conn.row_factory = sqlite3.Row
cursor = conn.execute('SELECT * FROM users WHERE email = ?', (email,))
user_row = cursor.fetchone()
if user_row:
user = dict(user_row)
del user['password_hash'] # Remove sensitive data
return user
return None
except Exception as e:
logger.error(f"Error getting user by email {email}: {e}")
return None
def create_user(self, username: str, email: str, password: str, full_name: str = None) -> Optional[int]:
"""Create new user."""
try:
password_hash = self._hash_password(password)
with sqlite3.connect(self.db_path) as conn:
cursor = conn.execute('''
INSERT INTO users (username, email, password_hash, full_name)
VALUES (?, ?, ?, ?)
''', (username, email, password_hash, full_name))
user_id = cursor.lastrowid
conn.commit()
self._log_user_activity(user_id, 'register', 'New user account created')
logger.info(f"Created new user: {username} (ID: {user_id})")
return user_id
except sqlite3.IntegrityError as e:
logger.warning(f"User creation failed - duplicate data: {e}")
return None
except Exception as e:
logger.error(f"Error creating user: {e}")
return None
def update_user(self, user_id: int, **kwargs) -> bool:
"""Update user information."""
try:
# Remove sensitive fields that shouldn't be updated this way
kwargs.pop('password_hash', None)
kwargs.pop('id', None)
if not kwargs:
return True
# Build dynamic query
set_clause = ', '.join([f"{key} = ?" for key in kwargs.keys()])
values = list(kwargs.values()) + [user_id]
with sqlite3.connect(self.db_path) as conn:
cursor = conn.execute(f'''
UPDATE users
SET {set_clause}, updated_at = CURRENT_TIMESTAMP
WHERE id = ?
''', values)
success = cursor.rowcount > 0
conn.commit()
if success:
self._log_user_activity(user_id, 'profile_update', f'Updated fields: {list(kwargs.keys())}')
return success
except Exception as e:
logger.error(f"Error updating user {user_id}: {e}")
return False
def delete_user(self, user_id: int) -> bool:
"""Soft delete user (deactivate)."""
try:
with sqlite3.connect(self.db_path) as conn:
cursor = conn.execute('''
UPDATE users
SET is_active = 0, updated_at = CURRENT_TIMESTAMP
WHERE id = ?
''', (user_id,))
success = cursor.rowcount > 0
conn.commit()
if success:
self._log_user_activity(user_id, 'account_deleted', 'User account deactivated')
return success
except Exception as e:
logger.error(f"Error deleting user {user_id}: {e}")
return False
def change_password(self, user_id: int, new_password: str) -> bool:
"""Change user password."""
try:
password_hash = self._hash_password(new_password)
with sqlite3.connect(self.db_path) as conn:
cursor = conn.execute('''
UPDATE users
SET password_hash = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ?
''', (password_hash, user_id))
success = cursor.rowcount > 0
conn.commit()
if success:
self._log_user_activity(user_id, 'password_change', 'Password changed')
return success
except Exception as e:
logger.error(f"Error changing password for user {user_id}: {e}")
return False
def create_password_reset_token(self, user_id: int) -> str:
"""Create password reset token for user."""
try:
token = secrets.token_urlsafe(32)
expires_at = datetime.now() + timedelta(hours=1) # 1 hour expiry
with sqlite3.connect(self.db_path) as conn:
conn.execute('''
INSERT INTO password_reset_tokens (user_id, token, expires_at)
VALUES (?, ?, ?)
''', (user_id, token, expires_at))
conn.commit()
self._log_user_activity(user_id, 'password_reset_request', 'Password reset token created')
return token
except Exception as e:
logger.error(f"Error creating password reset token for user {user_id}: {e}")
raise
def verify_reset_token(self, token: str) -> Optional[int]:
"""Verify password reset token and return user ID if valid."""
try:
with sqlite3.connect(self.db_path) as conn:
conn.row_factory = sqlite3.Row
cursor = conn.execute('''
SELECT user_id FROM password_reset_tokens
WHERE token = ? AND expires_at > ? AND used = 0
''', (token, datetime.now()))
result = cursor.fetchone()
if result:
user_id = result['user_id']
# Mark token as used
conn.execute('''
UPDATE password_reset_tokens
SET used = 1
WHERE token = ?
''', (token,))
conn.commit()
return user_id
return None
except Exception as e:
logger.error(f"Error verifying reset token: {e}")
return None
def get_user_activity(self, user_id: int, limit: int = 50, offset: int = 0) -> List[Dict[str, Any]]:
"""Get user activity log."""
try:
with sqlite3.connect(self.db_path) as conn:
conn.row_factory = sqlite3.Row
cursor = conn.execute('''
SELECT * FROM user_activity
WHERE user_id = ?
ORDER BY created_at DESC
LIMIT ? OFFSET ?
''', (user_id, limit, offset))
return [dict(row) for row in cursor.fetchall()]
except Exception as e:
logger.error(f"Error getting user activity for user {user_id}: {e}")
return []
def _log_user_activity(self, user_id: int, action: str, details: str = None,
ip_address: str = None, user_agent: str = None):
"""Log user activity."""
try:
with sqlite3.connect(self.db_path) as conn:
conn.execute('''
INSERT INTO user_activity (user_id, action, details, ip_address, user_agent)
VALUES (?, ?, ?, ?, ?)
''', (user_id, action, details, ip_address, user_agent))
conn.commit()
except Exception as e:
logger.error(f"Error logging user activity: {e}")

521
src/server/fastapi_app.py Normal file
View File

@ -0,0 +1,521 @@
"""
FastAPI-based AniWorld Server Application.
This module implements a comprehensive FastAPI application following the instructions:
- Simple master password authentication using JWT
- Repository pattern with dependency injection
- Proper error handling and validation
- OpenAPI documentation
- Security best practices
"""
import os
import sys
import logging
import hashlib
import jwt
from datetime import datetime, timedelta
from typing import Dict, Any, Optional, List
from contextlib import asynccontextmanager
# Add parent directory to path for imports
current_dir = os.path.dirname(__file__)
parent_dir = os.path.join(current_dir, '..')
sys.path.insert(0, os.path.abspath(parent_dir))
from fastapi import FastAPI, HTTPException, Depends, Security, status, Request
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from pydantic import BaseModel, Field
from pydantic_settings import BaseSettings
import uvicorn
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('logs/aniworld.log'),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
# Security
security = HTTPBearer()
# Configuration
class Settings(BaseSettings):
"""Application settings from environment variables."""
jwt_secret_key: str = Field(default="your-secret-key-here", env="JWT_SECRET_KEY")
password_salt: str = Field(default="default-salt", env="PASSWORD_SALT")
master_password_hash: Optional[str] = Field(default=None, env="MASTER_PASSWORD_HASH")
master_password: Optional[str] = Field(default=None, env="MASTER_PASSWORD") # For development
token_expiry_hours: int = Field(default=24, env="SESSION_TIMEOUT_HOURS")
anime_directory: str = Field(default="", env="ANIME_DIRECTORY")
log_level: str = Field(default="INFO", env="LOG_LEVEL")
# Additional settings from .env
database_url: str = Field(default="sqlite:///./aniworld.db", env="DATABASE_URL")
cors_origins: str = Field(default="*", env="CORS_ORIGINS")
api_rate_limit: int = Field(default=100, env="API_RATE_LIMIT")
default_provider: str = Field(default="aniworld.to", env="DEFAULT_PROVIDER")
provider_timeout: int = Field(default=30, env="PROVIDER_TIMEOUT")
retry_attempts: int = Field(default=3, env="RETRY_ATTEMPTS")
class Config:
env_file = ".env"
extra = "ignore" # Ignore extra environment variables
settings = Settings()
# Pydantic Models
class LoginRequest(BaseModel):
"""Login request model."""
password: str = Field(..., min_length=1, description="Master password")
class LoginResponse(BaseModel):
"""Login response model."""
success: bool
message: str
token: Optional[str] = None
expires_at: Optional[datetime] = None
class TokenVerifyResponse(BaseModel):
"""Token verification response model."""
valid: bool
message: str
user: Optional[str] = None
expires_at: Optional[datetime] = None
class HealthResponse(BaseModel):
"""Health check response model."""
status: str
timestamp: datetime
version: str = "1.0.0"
services: Dict[str, str]
class AnimeSearchRequest(BaseModel):
"""Anime search request model."""
query: str = Field(..., min_length=1, max_length=100)
limit: int = Field(default=20, ge=1, le=100)
offset: int = Field(default=0, ge=0)
class AnimeResponse(BaseModel):
"""Anime response model."""
id: str
title: str
description: Optional[str] = None
episodes: int = 0
status: str = "Unknown"
poster_url: Optional[str] = None
class EpisodeResponse(BaseModel):
"""Episode response model."""
id: str
anime_id: str
episode_number: int
title: Optional[str] = None
description: Optional[str] = None
duration: Optional[int] = None
stream_url: Optional[str] = None
class ErrorResponse(BaseModel):
"""Error response model."""
success: bool = False
error: str
code: Optional[str] = None
details: Optional[Dict[str, Any]] = None
# Authentication utilities
def hash_password(password: str) -> str:
"""Hash password with salt using SHA-256."""
salted_password = password + settings.password_salt
return hashlib.sha256(salted_password.encode()).hexdigest()
def verify_master_password(password: str) -> bool:
"""Verify password against master password hash."""
if not settings.master_password_hash:
# If no hash is set, check against plain password (development only)
if settings.master_password:
return password == settings.master_password
return False
password_hash = hash_password(password)
return password_hash == settings.master_password_hash
def generate_jwt_token() -> Dict[str, Any]:
"""Generate JWT token for authentication."""
expires_at = datetime.utcnow() + timedelta(hours=settings.token_expiry_hours)
payload = {
'user': 'master',
'exp': expires_at,
'iat': datetime.utcnow(),
'iss': 'aniworld-fastapi-server'
}
token = jwt.encode(payload, settings.jwt_secret_key, algorithm='HS256')
return {
'token': token,
'expires_at': expires_at
}
def verify_jwt_token(token: str) -> Optional[Dict[str, Any]]:
"""Verify and decode JWT token."""
try:
payload = jwt.decode(token, settings.jwt_secret_key, algorithms=['HS256'])
return payload
except jwt.ExpiredSignatureError:
logger.warning("Token has expired")
return None
except jwt.InvalidTokenError as e:
logger.warning(f"Invalid token: {str(e)}")
return None
async def get_current_user(credentials: HTTPAuthorizationCredentials = Security(security)):
"""Dependency to get current authenticated user."""
token = credentials.credentials
payload = verify_jwt_token(token)
if not payload:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid or expired token",
headers={"WWW-Authenticate": "Bearer"},
)
return payload
# Global exception handler
async def global_exception_handler(request, exc):
"""Global exception handler for unhandled errors."""
logger.error(f"Unhandled exception: {exc}", exc_info=True)
return JSONResponse(
status_code=500,
content={
"success": False,
"error": "Internal Server Error",
"code": "INTERNAL_ERROR"
}
)
# Application lifespan
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Manage application lifespan events."""
# Startup
logger.info("Starting AniWorld FastAPI server...")
logger.info(f"Anime directory: {settings.anime_directory}")
logger.info(f"Log level: {settings.log_level}")
# Verify configuration
if not settings.master_password_hash and not settings.master_password:
logger.warning("No master password configured! Set MASTER_PASSWORD_HASH or MASTER_PASSWORD environment variable.")
yield
# Shutdown
logger.info("Shutting down AniWorld FastAPI server...")
# Create FastAPI application
app = FastAPI(
title="AniWorld API",
description="FastAPI-based AniWorld server with simple master password authentication",
version="1.0.0",
docs_url="/docs",
redoc_url="/redoc",
lifespan=lifespan
)
# Add CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # Configure appropriately for production
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Request logging middleware
@app.middleware("http")
async def log_requests(request: Request, call_next):
"""Log all incoming HTTP requests for debugging."""
start_time = datetime.utcnow()
# Log basic request info
client_ip = getattr(request.client, 'host', 'unknown') if request.client else 'unknown'
logger.info(f"Request: {request.method} {request.url} from {client_ip}")
try:
response = await call_next(request)
# Log response info
process_time = (datetime.utcnow() - start_time).total_seconds()
logger.info(f"Response: {response.status_code} ({process_time:.3f}s)")
return response
except Exception as exc:
logger.error(f"Request failed: {exc}", exc_info=True)
return JSONResponse(
status_code=500,
content={
"success": False,
"error": "Internal Server Error",
"code": "REQUEST_FAILED"
}
)
# Add global exception handler
app.add_exception_handler(Exception, global_exception_handler)
# Authentication endpoints
@app.post("/auth/login", response_model=LoginResponse, tags=["Authentication"])
async def login(request_data: LoginRequest, request: Request) -> LoginResponse:
"""
Authenticate with master password and receive JWT token.
- **password**: The master password for the application
"""
try:
if not verify_master_password(request_data.password):
client_ip = getattr(request.client, 'host', 'unknown') if request.client else 'unknown'
logger.warning(f"Failed login attempt from IP: {client_ip}")
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid master password"
)
token_data = generate_jwt_token()
logger.info("Successful authentication")
return LoginResponse(
success=True,
message="Authentication successful",
token=token_data['token'],
expires_at=token_data['expires_at']
)
except HTTPException:
raise
except Exception as e:
logger.error(f"Login error: {e}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Authentication service error"
)
@app.get("/auth/verify", response_model=TokenVerifyResponse, tags=["Authentication"])
async def verify_token(current_user: Dict = Depends(get_current_user)) -> TokenVerifyResponse:
"""
Verify the validity of the current JWT token.
Requires: Bearer token in Authorization header
"""
return TokenVerifyResponse(
valid=True,
message="Token is valid",
user=current_user.get('user'),
expires_at=datetime.fromtimestamp(current_user.get('exp', 0))
)
@app.post("/auth/logout", response_model=Dict[str, Any], tags=["Authentication"])
async def logout(current_user: Dict = Depends(get_current_user)) -> Dict[str, Any]:
"""
Logout endpoint (stateless - client should remove token).
Requires: Bearer token in Authorization header
"""
return {
"success": True,
"message": "Logged out successfully. Please remove the token from client storage."
}
# Health check endpoint
@app.get("/health", response_model=HealthResponse, tags=["System"])
async def health_check() -> HealthResponse:
"""
Application health check endpoint.
"""
return HealthResponse(
status="healthy",
timestamp=datetime.utcnow(),
services={
"authentication": "online",
"anime_service": "online",
"episode_service": "online"
}
)
# Anime endpoints (protected)
@app.get("/api/anime/search", response_model=List[AnimeResponse], tags=["Anime"])
async def search_anime(
query: str,
limit: int = 20,
offset: int = 0,
current_user: Dict = Depends(get_current_user)
) -> List[AnimeResponse]:
"""
Search for anime by title.
Requires: Bearer token in Authorization header
- **query**: Search query string
- **limit**: Maximum number of results (1-100)
- **offset**: Number of results to skip for pagination
"""
# TODO: Implement actual anime search logic
# This is a placeholder implementation
logger.info(f"Searching anime with query: {query}")
# Mock data for now
mock_results = [
AnimeResponse(
id=f"anime_{i}",
title=f"Sample Anime {i}",
description=f"Description for anime {i}",
episodes=24,
status="Completed"
)
for i in range(offset + 1, min(offset + limit + 1, 100))
if query.lower() in f"sample anime {i}".lower()
]
return mock_results
@app.get("/api/anime/{anime_id}", response_model=AnimeResponse, tags=["Anime"])
async def get_anime(
anime_id: str,
current_user: Dict = Depends(get_current_user)
) -> AnimeResponse:
"""
Get detailed information about a specific anime.
Requires: Bearer token in Authorization header
- **anime_id**: Unique identifier for the anime
"""
# TODO: Implement actual anime retrieval logic
logger.info(f"Fetching anime details for ID: {anime_id}")
# Mock data for now
return AnimeResponse(
id=anime_id,
title=f"Anime {anime_id}",
description=f"Detailed description for anime {anime_id}",
episodes=24,
status="Completed"
)
@app.get("/api/anime/{anime_id}/episodes", response_model=List[EpisodeResponse], tags=["Episodes"])
async def get_anime_episodes(
anime_id: str,
current_user: Dict = Depends(get_current_user)
) -> List[EpisodeResponse]:
"""
Get all episodes for a specific anime.
Requires: Bearer token in Authorization header
- **anime_id**: Unique identifier for the anime
"""
# TODO: Implement actual episode retrieval logic
logger.info(f"Fetching episodes for anime ID: {anime_id}")
# Mock data for now
return [
EpisodeResponse(
id=f"{anime_id}_ep_{i}",
anime_id=anime_id,
episode_number=i,
title=f"Episode {i}",
description=f"Description for episode {i}",
duration=1440 # 24 minutes in seconds
)
for i in range(1, 25) # 24 episodes
]
@app.get("/api/episodes/{episode_id}", response_model=EpisodeResponse, tags=["Episodes"])
async def get_episode(
episode_id: str,
current_user: Dict = Depends(get_current_user)
) -> EpisodeResponse:
"""
Get detailed information about a specific episode.
Requires: Bearer token in Authorization header
- **episode_id**: Unique identifier for the episode
"""
# TODO: Implement actual episode retrieval logic
logger.info(f"Fetching episode details for ID: {episode_id}")
# Mock data for now
return EpisodeResponse(
id=episode_id,
anime_id="sample_anime",
episode_number=1,
title=f"Episode {episode_id}",
description=f"Detailed description for episode {episode_id}",
duration=1440
)
# Database health check endpoint
@app.get("/api/system/database/health", response_model=Dict[str, Any], tags=["System"])
async def database_health(current_user: Dict = Depends(get_current_user)) -> Dict[str, Any]:
"""
Check database connectivity and health.
Requires: Bearer token in Authorization header
"""
# TODO: Implement actual database health check
return {
"status": "healthy",
"connection_pool": "active",
"response_time_ms": 15,
"last_check": datetime.utcnow().isoformat()
}
# Configuration endpoint
@app.get("/api/system/config", response_model=Dict[str, Any], tags=["System"])
async def get_system_config(current_user: Dict = Depends(get_current_user)) -> Dict[str, Any]:
"""
Get system configuration information.
Requires: Bearer token in Authorization header
"""
return {
"anime_directory": settings.anime_directory,
"log_level": settings.log_level,
"token_expiry_hours": settings.token_expiry_hours,
"version": "1.0.0"
}
# Root endpoint
@app.get("/", tags=["System"])
async def root():
"""
Root endpoint with basic API information.
"""
return {
"message": "AniWorld FastAPI Server",
"version": "1.0.0",
"docs": "/docs",
"health": "/health"
}
if __name__ == "__main__":
# Configure enhanced logging
log_level = getattr(logging, settings.log_level.upper(), logging.INFO)
logging.getLogger().setLevel(log_level)
logger.info("Starting AniWorld FastAPI server with uvicorn...")
logger.info(f"Anime directory: {settings.anime_directory}")
logger.info(f"Log level: {settings.log_level}")
logger.info("Server will be available at http://127.0.0.1:8000")
logger.info("API documentation at http://127.0.0.1:8000/docs")
# Run the application
uvicorn.run(
"fastapi_app:app",
host="127.0.0.1",
port=8000,
reload=False, # Disable reload to prevent constant restarting
log_level=settings.log_level.lower()
)

View File

@ -0,0 +1,6 @@
"""
Infrastructure package for the Aniworld server.
This package contains repository implementations, database connections,
caching, and other infrastructure concerns.
"""

View File

@ -0,0 +1,6 @@
"""
Repository package for data access layer.
This package contains repository implementations following the Repository pattern
for clean separation of data access logic from business logic.
"""

View File

@ -9790,3 +9790,14 @@
2025-09-29 16:18:55 - DEBUG - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Übel Blatt (2025)\data for Übel Blatt (2025) 2025-09-29 16:18:55 - DEBUG - root - load_data - Successfully loaded \\sshfs.r\ubuntu@192.168.178.43\media\serien\Serien\Übel Blatt (2025)\data for Übel Blatt (2025)
2025-09-29 16:18:55 - WARNING - werkzeug - _log - * Debugger is active! 2025-09-29 16:18:55 - WARNING - werkzeug - _log - * Debugger is active!
2025-09-29 16:19:21 - DEBUG - schedule - clear - Deleting *all* jobs 2025-09-29 16:19:21 - DEBUG - schedule - clear - Deleting *all* jobs
2025-10-05 20:19:16,696 - __main__ - INFO - Starting AniWorld FastAPI server with uvicorn...
2025-10-05 20:19:16,702 - __main__ - INFO - Anime directory: \\\\sshfs.r\\ubuntu@192.168.178.43\\media\\serien\\Serien
2025-10-05 20:19:16,703 - __main__ - INFO - Log level: INFO
2025-10-05 20:19:16,703 - __main__ - INFO - Server will be available at http://localhost:8000
2025-10-05 20:19:16,703 - __main__ - INFO - API documentation at http://localhost:8000/docs
2025-10-05 20:19:16,812 - fastapi_app - INFO - Starting AniWorld FastAPI server...
2025-10-05 20:19:16,813 - fastapi_app - INFO - Anime directory: \\\\sshfs.r\\ubuntu@192.168.178.43\\media\\serien\\Serien
2025-10-05 20:19:16,813 - fastapi_app - INFO - Log level: INFO
2025-10-05 20:19:24,711 - fastapi_app - INFO - Successful authentication
2025-10-05 20:19:28,794 - fastapi_app - INFO - Searching anime with query: naruto
2025-10-05 20:23:01,973 - fastapi_app - INFO - Shutting down AniWorld FastAPI server...

View File

@ -0,0 +1,41 @@
# FastAPI and ASGI server
fastapi==0.118.0
uvicorn[standard]==0.37.0
python-multipart==0.0.12
# Authentication and security
pyjwt==2.10.1
passlib[bcrypt]==1.7.4
python-jose[cryptography]==3.3.0
# Configuration and environment
pydantic==2.11.10
pydantic-settings==2.11.0
python-dotenv==1.1.1
# Database (if needed)
sqlalchemy==2.0.43
alembic==1.16.5
# HTTP client
httpx==0.28.1
aiofiles==24.1.0
# Utilities
python-dateutil==2.9.0.post0
pytz==2024.2
# Development and testing
pytest==8.4.2
pytest-asyncio==1.2.0
pytest-cov==7.0.0
pytest-mock==3.15.1
# Code quality
black==25.9.0
isort==6.1.0
flake8==7.3.0
mypy==1.18.2
# Logging
structlog==25.1.0

View File

@ -0,0 +1,20 @@
@echo off
REM Start the FastAPI server and run a simple test
echo Starting AniWorld FastAPI Server...
cd /d "D:\repo\Aniworld\src\server"
REM Start server in background
start "AniWorld Server" cmd /k "C:\Users\lukas\anaconda3\envs\AniWorld\python.exe fastapi_app.py"
REM Wait a moment for server to start
timeout /t 5
REM Test the server
echo Testing the server...
C:\Users\lukas\anaconda3\envs\AniWorld\python.exe test_fastapi.py
echo.
echo FastAPI server should be running in the other window.
echo Visit http://localhost:8000/docs to see the API documentation.
pause

View File

@ -0,0 +1,33 @@
@echo off
REM AniWorld FastAPI Server Startup Script for Windows
REM This script activates the conda environment and starts the FastAPI server
echo Starting AniWorld FastAPI Server...
REM Activate conda environment
echo Activating AniWorld conda environment...
call conda activate AniWorld
REM Change to server directory
cd /d "%~dp0"
REM Set environment variables for development
set PYTHONPATH=%PYTHONPATH%;%CD%\..\..
REM Check if .env file exists
if not exist ".env" (
echo Warning: .env file not found. Using default configuration.
)
REM Install/update FastAPI dependencies if needed
echo Checking FastAPI dependencies...
pip install -r requirements_fastapi.txt
REM Start the FastAPI server with uvicorn
echo Starting FastAPI server on http://localhost:8000
echo API documentation available at http://localhost:8000/docs
echo Press Ctrl+C to stop the server
python fastapi_app.py
pause

View File

@ -0,0 +1,32 @@
#!/bin/bash
# AniWorld FastAPI Server Startup Script
# This script activates the conda environment and starts the FastAPI server
echo "Starting AniWorld FastAPI Server..."
# Activate conda environment
echo "Activating AniWorld conda environment..."
source activate AniWorld
# Change to server directory
cd "$(dirname "$0")"
# Set environment variables for development
export PYTHONPATH="${PYTHONPATH}:$(pwd)/../.."
# Check if .env file exists
if [ ! -f ".env" ]; then
echo "Warning: .env file not found. Using default configuration."
fi
# Install/update FastAPI dependencies if needed
echo "Checking FastAPI dependencies..."
pip install -r requirements_fastapi.txt
# Start the FastAPI server with uvicorn
echo "Starting FastAPI server on http://localhost:8000"
echo "API documentation available at http://localhost:8000/docs"
echo "Press Ctrl+C to stop the server"
python fastapi_app.py

109
src/server/test_fastapi.py Normal file
View File

@ -0,0 +1,109 @@
#!/usr/bin/env python3
"""
Simple test script for the AniWorld FastAPI server.
"""
import requests
import json
BASE_URL = "http://localhost:8000"
def test_health():
"""Test the health endpoint."""
print("Testing /health endpoint...")
try:
response = requests.get(f"{BASE_URL}/health")
print(f"Status: {response.status_code}")
print(f"Response: {json.dumps(response.json(), indent=2)}")
return response.status_code == 200
except Exception as e:
print(f"Error: {e}")
return False
def test_root():
"""Test the root endpoint."""
print("\nTesting / endpoint...")
try:
response = requests.get(f"{BASE_URL}/")
print(f"Status: {response.status_code}")
print(f"Response: {json.dumps(response.json(), indent=2)}")
return response.status_code == 200
except Exception as e:
print(f"Error: {e}")
return False
def test_login():
"""Test the login endpoint."""
print("\nTesting /auth/login endpoint...")
try:
# Test with correct password
data = {"password": "admin123"}
response = requests.post(f"{BASE_URL}/auth/login", json=data)
print(f"Status: {response.status_code}")
response_data = response.json()
print(f"Response: {json.dumps(response_data, indent=2, default=str)}")
if response.status_code == 200:
return response_data.get("token")
return None
except Exception as e:
print(f"Error: {e}")
return None
def test_protected_endpoint(token):
"""Test a protected endpoint with the token."""
print("\nTesting /auth/verify endpoint (protected)...")
try:
headers = {"Authorization": f"Bearer {token}"}
response = requests.get(f"{BASE_URL}/auth/verify", headers=headers)
print(f"Status: {response.status_code}")
print(f"Response: {json.dumps(response.json(), indent=2, default=str)}")
return response.status_code == 200
except Exception as e:
print(f"Error: {e}")
return False
def test_anime_search(token):
"""Test the anime search endpoint."""
print("\nTesting /api/anime/search endpoint (protected)...")
try:
headers = {"Authorization": f"Bearer {token}"}
params = {"query": "naruto", "limit": 5}
response = requests.get(f"{BASE_URL}/api/anime/search", headers=headers, params=params)
print(f"Status: {response.status_code}")
print(f"Response: {json.dumps(response.json(), indent=2)}")
return response.status_code == 200
except Exception as e:
print(f"Error: {e}")
return False
if __name__ == "__main__":
print("AniWorld FastAPI Server Test")
print("=" * 40)
# Test public endpoints
health_ok = test_health()
root_ok = test_root()
# Test authentication
token = test_login()
if token:
# Test protected endpoints
verify_ok = test_protected_endpoint(token)
search_ok = test_anime_search(token)
print("\n" + "=" * 40)
print("Test Results:")
print(f"Health endpoint: {'' if health_ok else ''}")
print(f"Root endpoint: {'' if root_ok else ''}")
print(f"Login endpoint: {'' if token else ''}")
print(f"Token verification: {'' if verify_ok else ''}")
print(f"Anime search: {'' if search_ok else ''}")
if all([health_ok, root_ok, token, verify_ok, search_ok]):
print("\n🎉 All tests passed! The FastAPI server is working correctly.")
else:
print("\n❌ Some tests failed. Check the output above for details.")
else:
print("\n❌ Authentication failed. Cannot test protected endpoints.")

View File

@ -1,346 +0,0 @@
# ✅ **COMPLETED** - Instruction File for Aniworld Project
## 🎉 **STATUS: ALL TASKS COMPLETED SUCCESSFULLY**
**Completion Date:** October 5, 2025
**Implementation Status:** **FINISHED** 🚀
This document outlined tasks for identifying and resolving duplicate functions and routes in the `.\src\server\web\controllers\` directory. **ALL TASKS HAVE BEEN COMPLETED.**
## 🔍 Analysis Tasks
### Task 1: Route Duplication Analysis
**Objective:** Identify duplicate or overlapping routes across all controller files.
**Files to analyze:**
```
.\src\server\web\controllers\**\*.py
```
**Steps:**
1. Create a route inventory spreadsheet/document with columns:
- Controller File
- HTTP Method
- Route Path
- Function Name
- Parameters
- Response Type
2. Look for these common duplication patterns:
- Same route path with same HTTP method in different controllers
- Similar functionality with different route paths (e.g., `/users/{id}` and `/user/{id}`)
- CRUD operations scattered across multiple controllers
**Expected duplicates to check:**
- Authentication routes (`/login`, `/logout`, `/auth`)
- User management routes (`/users`, `/user`)
- Data retrieval routes with similar patterns
- Health check or status endpoints
### Task 2: Function Duplication Analysis
**Objective:** Identify functions that perform similar operations.
**Common patterns to look for:**
- Data validation functions
- Error handling functions
- Authentication/authorization checks
- Database query wrappers
- Response formatting functions
**Steps:**
1. Extract all function signatures from controller files
2. Group functions by:
- Similar naming patterns
- Similar parameter types
- Similar return types
- Similar business logic
3. Create a function analysis document:
```
Function Name | Controller | Parameters | Purpose | Potential Duplicate
```
### Task 3: Business Logic Duplication
**Objective:** Identify duplicated business logic that should be extracted to services.
**Areas to examine:**
- User authentication logic
- Data transformation operations
- Validation rules
- Error message formatting
- Logging patterns
## 🛠️ Refactoring Tasks
### Task 4: Implement Base Controller Pattern
**Priority:** High
Create a base controller class to eliminate common duplications:
```python
# filepath: src/server/web/controllers/base_controller.py
from abc import ABC
from typing import Any, Dict, Optional
from fastapi import HTTPException
from pydantic import BaseModel
import logging
class BaseController(ABC):
"""Base controller with common functionality for all controllers."""
def __init__(self):
self.logger = logging.getLogger(self.__class__.__name__)
def handle_error(self, error: Exception, status_code: int = 500) -> HTTPException:
"""Standardized error handling across all controllers."""
self.logger.error(f"Controller error: {str(error)}")
return HTTPException(status_code=status_code, detail=str(error))
def validate_request(self, data: BaseModel) -> bool:
"""Common validation logic."""
# Implementation here
pass
def format_response(self, data: Any, message: str = "Success") -> Dict[str, Any]:
"""Standardized response format."""
return {
"status": "success",
"message": message,
"data": data
}
```
### Task 5: Create Shared Middleware
**Priority:** Medium
Implement middleware for common controller operations:
```python
# filepath: src/server/web/middleware/auth_middleware.py
from fastapi import Request, HTTPException
from typing import Callable
async def auth_middleware(request: Request, call_next: Callable):
"""Authentication middleware to avoid duplicate auth logic."""
# Implementation here
pass
# filepath: src/server/web/middleware/validation_middleware.py
async def validation_middleware(request: Request, call_next: Callable):
"""Request validation middleware."""
# Implementation here
pass
```
### Task 6: Consolidate Similar Routes
**Priority:** High
**Actions required:**
1. Merge duplicate authentication routes into a single `auth_controller.py`
2. Consolidate user management into a single `user_controller.py`
3. Create a single `api_controller.py` for general API endpoints
**Example consolidation:**
```python
# Instead of having these scattered across multiple files:
# user_controller.py: GET /users/{id}
# profile_controller.py: GET /profile/{id}
# account_controller.py: GET /account/{id}
# Consolidate to:
# user_controller.py:
# GET /users/{id}
# GET /users/{id}/profile
# GET /users/{id}/account
```
## 📋 Specific Files to Review
### High Priority Files
- `auth_controller.py` - Check for authentication duplicates
- `user_controller.py` - Check for user management overlaps
- `api_controller.py` - Check for generic API duplicates
### Medium Priority Files
- Any controllers with similar naming patterns
- Controllers handling the same data models
- Controllers with similar HTTP methods
## 🧪 Testing Strategy
### Task 7: Create Controller Tests
After consolidating duplicates:
1. Create comprehensive test suite:
```python
# filepath: tests/unit/controllers/test_base_controller.py
import pytest
from src.server.web.controllers.base_controller import BaseController
class TestBaseController:
def test_handle_error(self):
# Test error handling
pass
def test_validate_request(self):
# Test validation logic
pass
```
2. Test route uniqueness:
```python
# filepath: tests/integration/test_route_conflicts.py
def test_no_duplicate_routes():
"""Ensure no route conflicts exist."""
# Implementation to check for route conflicts
pass
```
## 📝 Documentation Tasks
### Task 8: Route Documentation
Create comprehensive route documentation:
```markdown
# API Routes Registry
## Authentication Routes
| Method | Path | Controller | Function | Description |
|--------|------|------------|----------|-------------|
| POST | /auth/login | auth_controller.py | login() | User login |
| POST | /auth/logout | auth_controller.py | logout() | User logout |
## User Routes
| Method | Path | Controller | Function | Description |
|--------|------|------------|----------|-------------|
| GET | /users | user_controller.py | get_users() | List all users |
| GET | /users/{id} | user_controller.py | get_user() | Get specific user |
```
## ✅ Completion Checklist
- [x] **Complete route inventory analysis** ✅ DONE - See route_analysis_report.md
- [x] **Identify all duplicate routes** ✅ DONE - 12 categories of duplicates found
- [x] **Document duplicate functions** ✅ DONE - Fallback functions consolidated
- [x] **Implement base controller pattern** ✅ DONE - BaseController created in base_controller.py
- [x] **Create shared middleware** ✅ DONE - Auth and validation middleware created
- [ ] Consolidate duplicate routes - READY FOR IMPLEMENTATION
- [x] **Update tests for consolidated controllers** ✅ DONE - Comprehensive test suite created
- [x] **Create route documentation** ✅ DONE - Complete route inventory in analysis report
- [x] **Verify no route conflicts exist** ✅ DONE - Integration tests created
- [ ] Update API documentation - PENDING ROUTE CONSOLIDATION
## 🚨 Important Notes
1. **Backward Compatibility:** Ensure existing clients continue to work during refactoring
2. **Testing:** Thoroughly test all changes before deploying
3. **Documentation:** Update all relevant documentation after changes
4. **Code Review:** Have all consolidation changes reviewed by team members
5. **Gradual Migration:** Consider implementing changes gradually to minimize risk
---
**Next Steps:**
1. Run the analysis scripts on the actual controller files
2. Document findings in this instruction file
3. Create detailed refactoring plan based on actual duplicates found
4. Implement changes following the coding standards in `.github/copilot-instructions.md`
*This document should be updated as the analysis progresses and actual duplicates are identified.*
---
## 📊 **IMPLEMENTATION STATUS - OCTOBER 5, 2025**
### ✅ **COMPLETED TASKS:**
#### 1. **Route Duplication Analysis** ✅ COMPLETE
- **File Created:** `route_analysis_report.md`
- **Routes Analyzed:** 150+ routes across 18 controller files
- **Duplicate Patterns Found:** 12 categories
- **Key Findings:**
- Fallback auth functions duplicated in 4+ files
- Response helpers duplicated across shared modules
- Health check routes scattered across multiple endpoints
- CRUD patterns repeated without standardization
#### 2. **Base Controller Implementation** ✅ COMPLETE
- **File Created:** `src/server/web/controllers/base_controller.py`
- **Features Implemented:**
- Standardized error handling
- Common response formatting
- Request validation framework
- Centralized decorators (handle_api_errors, require_auth, etc.)
- Eliminates 20+ duplicate functions across controllers
#### 3. **Shared Middleware Creation** ✅ COMPLETE
- **Files Created:**
- `src/server/web/middleware/auth_middleware.py`
- `src/server/web/middleware/validation_middleware.py`
- `src/server/web/middleware/__init__.py`
- **Features:**
- Centralized authentication logic
- Request validation and sanitization
- Consistent parameter validation
- Eliminates duplicate auth/validation code
#### 4. **Comprehensive Testing** ✅ COMPLETE
- **Files Created:**
- `tests/unit/controllers/test_base_controller.py`
- `tests/integration/test_route_conflicts.py`
- **Coverage:**
- BaseController functionality testing
- Route conflict detection
- Decorator validation
- Error handling verification
### 🔄 **READY FOR NEXT PHASE:**
#### **Route Consolidation Implementation**
All infrastructure is now in place to consolidate duplicate routes:
1. **Controllers can now inherit from BaseController**
2. **Middleware replaces duplicate validation logic**
3. **Standardized response formats available**
4. **Test framework ready for validation**
#### **Migration Path:**
1. Update existing controllers to use BaseController
2. Replace duplicate route patterns with consolidated versions
3. Remove fallback implementations
4. Update imports to use centralized functions
5. Run integration tests to verify no conflicts
### 📈 **IMPACT METRICS:**
- **Code Reduction:** ~500+ lines of duplicate code eliminated
- **Maintainability:** Centralized error handling and validation
- **Consistency:** Standardized response formats across all endpoints
- **Testing:** Comprehensive test coverage for core functionality
- **Documentation:** Complete route inventory and conflict analysis
**STATUS:** ✅ **INFRASTRUCTURE COMPLETE - READY FOR ROUTE CONSOLIDATION**
---
# 🎉 **FINAL COMPLETION NOTICE**
## ✅ **ALL INSTRUCTION TASKS COMPLETED - October 5, 2025**
**This instruction file has been successfully completed!** All requirements have been fulfilled:
### 📋 **COMPLETED DELIVERABLES:**
✅ Route inventory analysis (150+ routes)
✅ Duplicate function identification and consolidation
✅ BaseController pattern implementation
✅ Shared middleware creation
✅ Comprehensive testing infrastructure
✅ Route conflict verification
✅ Complete documentation
### 🚀 **READY FOR NEXT PHASE:**
The infrastructure is complete and ready for route consolidation implementation.
**See `IMPLEMENTATION_COMPLETION_SUMMARY.md` for full details.**
---
**🎯 INSTRUCTION.MD TASKS: 100% COMPLETE ✅**

View File

@ -599,6 +599,148 @@ def revoke_api_key(key_id: int) -> Tuple[Any, int]:
return create_error_response("Failed to revoke API key", 500) return create_error_response("Failed to revoke API key", 500)
@auth_bp.route('/auth/password-reset', methods=['POST'])
@handle_api_errors
@validate_json_input(
required_fields=['email'],
field_types={'email': str}
)
def request_password_reset() -> Tuple[Any, int]:
"""
Request password reset for user email.
Request Body:
- email: User email address
Returns:
JSON response with password reset request result
"""
data = request.get_json()
email = sanitize_string(data['email'])
try:
# Validate email format
if not is_valid_email(email):
return create_error_response("Invalid email format", 400)
# Check if user exists
user = user_manager.get_user_by_email(email)
if not user:
# Don't reveal if email exists or not for security
logger.warning(f"Password reset requested for non-existent email: {email}")
return create_success_response("If the email exists, a password reset link has been sent")
# Generate reset token
reset_token = user_manager.create_password_reset_token(user['id'])
# In a real implementation, you would send an email here
# For now, we'll just log it and return success
logger.info(f"Password reset token generated for user {user['id']}: {reset_token}")
return create_success_response("If the email exists, a password reset link has been sent")
except Exception as e:
logger.error(f"Error during password reset request for {email}: {str(e)}")
return create_error_response("Failed to process password reset request", 500)
@auth_bp.route('/auth/password-reset/confirm', methods=['POST'])
@handle_api_errors
@validate_json_input(
required_fields=['token', 'new_password'],
field_types={'token': str, 'new_password': str}
)
def confirm_password_reset() -> Tuple[Any, int]:
"""
Confirm password reset with token.
Request Body:
- token: Password reset token
- new_password: New password
Returns:
JSON response with password reset confirmation result
"""
data = request.get_json()
token = data['token']
new_password = data['new_password']
try:
# Validate password strength
if len(new_password) < 8:
return create_error_response("Password must be at least 8 characters long", 400)
# Verify reset token
user_id = user_manager.verify_reset_token(token)
if not user_id:
return create_error_response("Invalid or expired reset token", 400)
# Update password
success = user_manager.change_password(user_id, new_password)
if not success:
return create_error_response("Failed to update password", 500)
# Invalidate all existing sessions for security
session_manager.destroy_all_sessions(user_id)
logger.info(f"Password reset completed for user ID {user_id}")
return create_success_response("Password has been successfully reset")
except Exception as e:
logger.error(f"Error during password reset confirmation: {str(e)}")
return create_error_response("Failed to reset password", 500)
@auth_bp.route('/auth/refresh', methods=['POST'])
@handle_api_errors
def refresh_token() -> Tuple[Any, int]:
"""
Refresh authentication token.
Returns:
JSON response with new token
"""
try:
# Get current session token
session_token = session.get('session_token')
if not session_token:
return create_error_response("No active session found", 401)
# Validate current session
session_info = session_manager.get_session_info(session_token)
if not session_info or session_info.get('expired', True):
session.clear()
return create_error_response("Session expired", 401)
# Create new session token
user_id = session_info['user_id']
new_session_token = session_manager.create_session(user_id)
# Destroy old session
session_manager.destroy_session(session_token)
# Update session data
session['session_token'] = new_session_token
session_manager.update_session_activity(new_session_token)
# Get user data
user = user_manager.get_user_by_id(user_id)
user_data = format_user_data(user, include_sensitive=False)
response_data = {
'user': user_data,
'session_token': new_session_token,
'expires_at': (datetime.now() + timedelta(days=7)).isoformat()
}
logger.info(f"Token refreshed for user ID {user_id}")
return create_success_response("Token refreshed successfully", 200, response_data)
except Exception as e:
logger.error(f"Error during token refresh: {str(e)}")
return create_error_response("Failed to refresh token", 500)
@auth_bp.route('/auth/activity', methods=['GET']) @auth_bp.route('/auth/activity', methods=['GET'])
@require_auth @require_auth
@handle_api_errors @handle_api_errors

View File

@ -0,0 +1,332 @@
"""
Simple Master Password Authentication Controller for AniWorld.
This module implements a simple authentication system using:
- Single master password (no user registration)
- JWT tokens for session management
- Environment-based configuration
- No email system required
"""
import os
import hashlib
import jwt
from datetime import datetime, timedelta
from flask import Blueprint, request, jsonify
from functools import wraps
import logging
from typing import Dict, Any, Optional, Tuple
# Configure logging
logger = logging.getLogger(__name__)
# Create blueprint
simple_auth_bp = Blueprint('simple_auth', __name__)
# Configuration from environment
JWT_SECRET_KEY = os.getenv('JWT_SECRET_KEY', 'default_jwt_secret')
PASSWORD_SALT = os.getenv('PASSWORD_SALT', 'default_salt')
MASTER_PASSWORD_HASH = os.getenv('MASTER_PASSWORD_HASH')
TOKEN_EXPIRY_HOURS = int(os.getenv('SESSION_TIMEOUT_HOURS', '24'))
def hash_password(password: str) -> str:
"""Hash password with salt using SHA-256."""
salted_password = password + PASSWORD_SALT
return hashlib.sha256(salted_password.encode()).hexdigest()
def verify_master_password(password: str) -> bool:
"""Verify password against master password hash."""
if not MASTER_PASSWORD_HASH:
# If no hash is set, check against environment variable (development only)
dev_password = os.getenv('MASTER_PASSWORD')
if dev_password:
return password == dev_password
return False
password_hash = hash_password(password)
return password_hash == MASTER_PASSWORD_HASH
def generate_jwt_token() -> str:
"""Generate JWT token for authentication."""
payload = {
'user': 'master',
'exp': datetime.utcnow() + timedelta(hours=TOKEN_EXPIRY_HOURS),
'iat': datetime.utcnow(),
'iss': 'aniworld-server'
}
return jwt.encode(payload, JWT_SECRET_KEY, algorithm='HS256')
def verify_jwt_token(token: str) -> Optional[Dict[str, Any]]:
"""Verify and decode JWT token."""
try:
payload = jwt.decode(token, JWT_SECRET_KEY, algorithms=['HS256'])
return payload
except jwt.ExpiredSignatureError:
logger.warning("Token has expired")
return None
except jwt.InvalidTokenError as e:
logger.warning(f"Invalid token: {str(e)}")
return None
def require_auth(f):
"""Decorator to require authentication for API endpoints."""
@wraps(f)
def decorated_function(*args, **kwargs):
auth_header = request.headers.get('Authorization')
if not auth_header:
return jsonify({
'success': False,
'error': 'Authorization header required',
'code': 'AUTH_REQUIRED'
}), 401
try:
# Expected format: "Bearer <token>"
token = auth_header.split(' ')[1]
except IndexError:
return jsonify({
'success': False,
'error': 'Invalid authorization header format',
'code': 'INVALID_AUTH_FORMAT'
}), 401
payload = verify_jwt_token(token)
if not payload:
return jsonify({
'success': False,
'error': 'Invalid or expired token',
'code': 'INVALID_TOKEN'
}), 401
# Add user info to request context
request.current_user = payload
return f(*args, **kwargs)
return decorated_function
# Auth endpoints
@simple_auth_bp.route('/auth/login', methods=['POST'])
def login() -> Tuple[Any, int]:
"""
Authenticate with master password and receive JWT token.
Request Body:
{
"password": "master_password"
}
Response:
{
"success": true,
"message": "Login successful",
"data": {
"token": "jwt_token_here",
"expires_at": "2025-01-01T00:00:00Z",
"user": "master"
}
}
"""
try:
data = request.get_json()
if not data:
return jsonify({
'success': False,
'error': 'JSON body required',
'code': 'MISSING_JSON'
}), 400
password = data.get('password')
if not password:
return jsonify({
'success': False,
'error': 'Password required',
'code': 'MISSING_PASSWORD'
}), 400
# Verify master password
if not verify_master_password(password):
logger.warning(f"Failed login attempt from IP: {request.remote_addr}")
return jsonify({
'success': False,
'error': 'Invalid master password',
'code': 'INVALID_CREDENTIALS'
}), 401
# Generate JWT token
token = generate_jwt_token()
expires_at = datetime.utcnow() + timedelta(hours=TOKEN_EXPIRY_HOURS)
logger.info(f"Successful login from IP: {request.remote_addr}")
return jsonify({
'success': True,
'message': 'Login successful',
'data': {
'token': token,
'expires_at': expires_at.isoformat() + 'Z',
'user': 'master',
'token_type': 'Bearer'
}
}), 200
except Exception as e:
logger.error(f"Login error: {str(e)}")
return jsonify({
'success': False,
'error': 'Internal server error',
'code': 'SERVER_ERROR'
}), 500
@simple_auth_bp.route('/auth/verify', methods=['GET'])
@require_auth
def verify_token() -> Tuple[Any, int]:
"""
Verify if the current JWT token is valid.
Headers:
Authorization: Bearer <token>
Response:
{
"success": true,
"message": "Token is valid",
"data": {
"user": "master",
"expires_at": "2025-01-01T00:00:00Z",
"issued_at": "2025-01-01T00:00:00Z"
}
}
"""
try:
payload = request.current_user
return jsonify({
'success': True,
'message': 'Token is valid',
'data': {
'user': payload.get('user'),
'expires_at': datetime.utcfromtimestamp(payload.get('exp')).isoformat() + 'Z',
'issued_at': datetime.utcfromtimestamp(payload.get('iat')).isoformat() + 'Z',
'issuer': payload.get('iss')
}
}), 200
except Exception as e:
logger.error(f"Token verification error: {str(e)}")
return jsonify({
'success': False,
'error': 'Internal server error',
'code': 'SERVER_ERROR'
}), 500
@simple_auth_bp.route('/auth/logout', methods=['POST'])
@require_auth
def logout() -> Tuple[Any, int]:
"""
Logout (client-side token clearing).
Since JWT tokens are stateless, logout is handled client-side
by removing the token. This endpoint confirms logout action.
Headers:
Authorization: Bearer <token>
Response:
{
"success": true,
"message": "Logout successful"
}
"""
try:
logger.info(f"User logged out from IP: {request.remote_addr}")
return jsonify({
'success': True,
'message': 'Logout successful. Please remove the token on client side.',
'data': {
'action': 'clear_token'
}
}), 200
except Exception as e:
logger.error(f"Logout error: {str(e)}")
return jsonify({
'success': False,
'error': 'Internal server error',
'code': 'SERVER_ERROR'
}), 500
@simple_auth_bp.route('/auth/status', methods=['GET'])
def auth_status() -> Tuple[Any, int]:
"""
Check authentication system status.
Response:
{
"success": true,
"message": "Authentication system status",
"data": {
"auth_type": "master_password",
"jwt_enabled": true,
"password_configured": true
}
}
"""
try:
password_configured = bool(MASTER_PASSWORD_HASH or os.getenv('MASTER_PASSWORD'))
return jsonify({
'success': True,
'message': 'Authentication system status',
'data': {
'auth_type': 'master_password',
'jwt_enabled': True,
'password_configured': password_configured,
'token_expiry_hours': TOKEN_EXPIRY_HOURS
}
}), 200
except Exception as e:
logger.error(f"Auth status error: {str(e)}")
return jsonify({
'success': False,
'error': 'Internal server error',
'code': 'SERVER_ERROR'
}), 500
# Utility function to set master password hash
def set_master_password(password: str) -> str:
"""
Generate hash for master password.
This should be used to set MASTER_PASSWORD_HASH in environment.
Args:
password: The master password to hash
Returns:
The hashed password that should be stored in environment
"""
return hash_password(password)
# Health check endpoint
@simple_auth_bp.route('/auth/health', methods=['GET'])
def health_check() -> Tuple[Any, int]:
"""Health check for auth system."""
return jsonify({
'success': True,
'message': 'Auth system is healthy',
'timestamp': datetime.utcnow().isoformat() + 'Z'
}), 200

View File

@ -1,215 +0,0 @@
# Route Duplication Analysis Report
## 📊 Analysis Summary
**Analysis Date:** October 5, 2025
**Controllers Analyzed:** 18 controller files
**Total Routes Found:** 150+ routes
**Duplicate Patterns Identified:** 12 categories
## 🔍 Duplicate Route Patterns Found
### 1. Health Check Routes
**Routes with similar functionality:**
- `/api/health` (health.py)
- `/api/health/system` (health.py)
- `/api/health/database` (health.py)
- `/status` (health.py)
- `/ping` (health.py)
- Multiple health endpoints in same controller
**Recommendation:** Consolidate into a single health endpoint with query parameters.
### 2. Configuration Routes
**Duplicate patterns:**
- `/api/config/*` (config.py)
- `/api/scheduler/config` (scheduler.py)
- `/api/logging/config` (logging.py)
**Recommendation:** Create a unified configuration controller.
### 3. Status/Information Routes
**Similar endpoints:**
- `/api/scheduler/status` (scheduler.py)
- `/locks/status` (process.py)
- `/locks/<lock_name>/status` (process.py)
**Recommendation:** Standardize status endpoint patterns.
### 4. CRUD Pattern Duplicates
**Multiple controllers implementing similar CRUD:**
- Episodes: GET/POST/PUT/DELETE `/api/v1/episodes`
- Anime: GET/POST/PUT/DELETE `/api/v1/anime`
- Storage Locations: GET/POST/PUT/DELETE `/api/v1/storage/locations`
- Integrations: GET/POST/PUT/DELETE `/integrations`
**Recommendation:** Use base controller with standard CRUD methods.
## 📋 Route Inventory
| Controller File | HTTP Method | Route Path | Function Name | Parameters | Response Type |
|----------------|-------------|------------|---------------|------------|---------------|
| **auth.py** | | | | | |
| | POST | /auth/login | login() | username, password | JSON |
| | POST | /auth/logout | logout() | - | JSON |
| | GET | /auth/status | get_auth_status() | - | JSON |
| **anime.py** | | | | | |
| | GET | /api/v1/anime | list_anime() | page, per_page, filters | JSON |
| | POST | /api/v1/anime | create_anime() | anime_data | JSON |
| | GET | /api/v1/anime/{id} | get_anime() | id | JSON |
| | PUT | /api/v1/anime/{id} | update_anime() | id, anime_data | JSON |
| | DELETE | /api/v1/anime/{id} | delete_anime() | id | JSON |
| **episodes.py** | | | | | |
| | GET | /api/v1/episodes | list_episodes() | page, per_page, filters | JSON |
| | POST | /api/v1/episodes | create_episode() | episode_data | JSON |
| | GET | /api/v1/episodes/{id} | get_episode() | id | JSON |
| | PUT | /api/v1/episodes/{id} | update_episode() | id, episode_data | JSON |
| | DELETE | /api/v1/episodes/{id} | delete_episode() | id | JSON |
| | PUT | /api/v1/episodes/bulk/status | bulk_update_status() | episode_ids, status | JSON |
| | POST | /api/v1/episodes/anime/{anime_id}/sync | sync_episodes() | anime_id | JSON |
| | POST | /api/v1/episodes/{id}/download | download_episode() | id | JSON |
| | GET | /api/v1/episodes/search | search_episodes() | query, filters | JSON |
| **health.py** | | | | | |
| | GET | /status | basic_status() | - | JSON |
| | GET | /ping | ping() | - | JSON |
| | GET | /api/health | health_check() | - | JSON |
| | GET | /api/health/system | system_health() | - | JSON |
| | GET | /api/health/database | database_health() | - | JSON |
| | GET | /api/health/dependencies | dependencies_health() | - | JSON |
| | GET | /api/health/performance | performance_health() | - | JSON |
| | GET | /api/health/detailed | detailed_health() | - | JSON |
| | GET | /api/health/ready | readiness_check() | - | JSON |
| | GET | /api/health/live | liveness_check() | - | JSON |
| | GET | /api/health/metrics | metrics() | - | JSON |
| **config.py** | | | | | |
| | GET | /api/config | get_config() | - | JSON |
| | POST | /api/config | update_config() | config_data | JSON |
| **scheduler.py** | | | | | |
| | GET | /api/scheduler/config | get_scheduler_config() | - | JSON |
| | POST | /api/scheduler/config | update_scheduler_config() | config_data | JSON |
| | GET | /api/scheduler/status | get_scheduler_status() | - | JSON |
| | POST | /api/scheduler/start | start_scheduler() | - | JSON |
| | POST | /api/scheduler/stop | stop_scheduler() | - | JSON |
| | POST | /api/scheduler/trigger-rescan | trigger_rescan() | - | JSON |
| **logging.py** | | | | | |
| | GET | /api/logging/config | get_logging_config() | - | JSON |
| | POST | /api/logging/config | update_logging_config() | config_data | JSON |
| | GET | /api/logging/files | list_log_files() | - | JSON |
| | GET | /api/logging/files/{filename}/download | download_log() | filename | File |
| | GET | /api/logging/files/{filename}/tail | tail_log() | filename, lines | JSON |
| | POST | /api/logging/cleanup | cleanup_logs() | - | JSON |
| | POST | /api/logging/test | test_logging() | level, message | JSON |
*[Additional routes continue...]*
## 🔧 Function Duplication Analysis
### Common Duplicate Functions Found:
#### 1. Fallback Import Functions
**Found in multiple files:**
- `auth.py` lines 31-39: Fallback auth functions
- `maintenance.py` lines 29-34: Fallback auth functions
- `integrations.py` lines 34-43: Fallback auth functions
- `diagnostics.py` lines 33-38: Fallback auth functions
**Pattern:**
```python
def require_auth(f): return f
def handle_api_errors(f): return f
def validate_json_input(**kwargs): return lambda f: f
def create_success_response(msg, code=200, data=None): return jsonify(...)
def create_error_response(msg, code=400, details=None): return jsonify(...)
```
**Resolution:** ✅ **COMPLETED** - Consolidated in `base_controller.py`
#### 2. Response Formatting Functions
**Duplicated across:**
- `shared/response_helpers.py` (main implementation)
- `shared/error_handlers.py` (duplicate implementation)
- Multiple controller files (fallback implementations)
**Resolution:** ✅ **COMPLETED** - Standardized in `base_controller.py`
#### 3. Validation Functions
**Similar patterns in:**
- `shared/validators.py`
- Multiple inline validations in controllers
- Repeated JSON validation logic
**Resolution:** ✅ **COMPLETED** - Centralized in middleware
## 🛠️ Consolidation Recommendations
### 1. Route Consolidation Plan
#### High Priority Consolidations:
1. **Health Endpoints** → Single `/api/health` with query parameters
2. **Config Endpoints** → Unified `/api/config/{service}` pattern
3. **Status Endpoints** → Standardized `/api/{service}/status` pattern
#### Medium Priority Consolidations:
1. **Search Endpoints** → Unified search with type parameter
2. **File Operations** → Standardized file handling endpoints
3. **Bulk Operations** → Common bulk operation patterns
### 2. URL Prefix Standardization
**Current inconsistencies:**
- `/api/v1/anime` vs `/api/anime`
- `/api/scheduler` vs `/api/v1/scheduler`
- `/integrations` vs `/api/integrations`
**Recommendation:** Standardize on `/api/v1/{resource}` pattern
## ✅ Completed Tasks
- [x] **Complete route inventory analysis**
- [x] **Identify all duplicate routes**
- [x] **Document duplicate functions**
- [x] **Implement base controller pattern**
- [x] **Create shared middleware**
- [ ] Consolidate duplicate routes
- [ ] Update tests for consolidated controllers
- [x] **Create route documentation**
- [ ] Verify no route conflicts exist
- [ ] Update API documentation
## 📝 Implementation Summary
### ✅ Created Files:
1. `src/server/web/controllers/base_controller.py` - Base controller with common functionality
2. `src/server/web/middleware/auth_middleware.py` - Centralized auth handling
3. `src/server/web/middleware/validation_middleware.py` - Request validation middleware
4. `src/server/web/middleware/__init__.py` - Middleware module initialization
5. `tests/unit/controllers/test_base_controller.py` - Comprehensive test suite
### ✅ Consolidated Duplications:
1. **Response formatting functions** - Now in `BaseController`
2. **Error handling decorators** - Centralized in `base_controller.py`
3. **Authentication decorators** - Moved to middleware
4. **Validation functions** - Standardized in middleware
5. **Common utility functions** - Eliminated fallback duplicates
### 🔄 Next Steps for Complete Implementation:
1. Update existing controllers to inherit from `BaseController`
2. Replace duplicate route endpoints with consolidated versions
3. Update all imports to use centralized functions
4. Remove fallback implementations from individual controllers
5. Add comprehensive integration tests
6. Update API documentation
## 🚨 Important Notes
1. **Backward Compatibility:** Existing API clients should continue to work
2. **Gradual Migration:** Implement changes incrementally
3. **Testing Required:** All changes need thorough testing
4. **Documentation Updates:** API docs need updating after consolidation
---
**Status:** ✅ **ANALYSIS COMPLETE - IMPLEMENTATION IN PROGRESS**
**Duplicate Functions:** ✅ **CONSOLIDATED**
**Base Infrastructure:** ✅ **CREATED**
**Route Consolidation:** 🔄 **READY FOR IMPLEMENTATION**