From 6c7dc66c5d1854230dd7782852fe93f06e4e9cde Mon Sep 17 00:00:00 2001 From: Lukas Date: Sun, 22 Feb 2026 19:56:49 +0100 Subject: [PATCH] Remove unused scripts and config files --- check_db.py | 35 --- cleanup_patches.py | 95 ------- playwright.config.js | 88 ------- run_tests_capture.py | 28 -- scripts/migrate_loading_status.py | 97 ------- scripts/setup.py | 421 ------------------------------ scripts/start.sh | 225 ---------------- scripts/test_nfo_integration.py | 294 --------------------- scripts/test_nfo_update.py | 183 ------------- start_server.sh | 5 - stop_server.sh | 94 ------- vitest.config.js | 46 ---- 12 files changed, 1611 deletions(-) delete mode 100644 check_db.py delete mode 100644 cleanup_patches.py delete mode 100644 playwright.config.js delete mode 100644 run_tests_capture.py delete mode 100644 scripts/migrate_loading_status.py delete mode 100644 scripts/setup.py delete mode 100644 scripts/start.sh delete mode 100644 scripts/test_nfo_integration.py delete mode 100644 scripts/test_nfo_update.py delete mode 100644 start_server.sh delete mode 100644 stop_server.sh delete mode 100644 vitest.config.js diff --git a/check_db.py b/check_db.py deleted file mode 100644 index e0a4065..0000000 --- a/check_db.py +++ /dev/null @@ -1,35 +0,0 @@ -"""Simple database check script for manual testing.""" -import asyncio - -from sqlalchemy import select - -from src.server.database.connection import get_db_session, init_db -from src.server.database.models import AnimeSeries - - -async def check_series(): - """Check series in database.""" - await init_db() - - async with get_db_session() as session: - result = await session.execute(select(AnimeSeries)) - series_list = result.scalars().all() - - print(f'\n=== Database Series Check ===') - print(f'Total series: {len(series_list)}') - print() - - for s in series_list: - status = getattr(s, 'loading_status', 'no field') - episodes = getattr(s, 'episodes_loaded', 'N/A') - nfo = getattr(s, 'nfo_loaded', 'N/A') - logo = getattr(s, 'logo_loaded', 'N/A') - images = getattr(s, 'images_loaded', 'N/A') - - print(f'{s.name} ({s.key}):') - print(f' Status: {status}') - print(f' Episodes: {episodes}, NFO: {nfo}, Logo: {logo}, Images: {images}') - print() - -if __name__ == '__main__': - asyncio.run(check_series()) diff --git a/cleanup_patches.py b/cleanup_patches.py deleted file mode 100644 index 7b615f6..0000000 --- a/cleanup_patches.py +++ /dev/null @@ -1,95 +0,0 @@ -#!/usr/bin/env python3 -"""Remove patch contexts from NFO test file.""" - -with open('tests/api/test_nfo_endpoints.py', 'r') as f: - lines = f.readlines() - -new_lines = [] -i = 0 -skip_until_indent = None - -while i < len(lines): - line = lines[i] - - # Check if we're starting a patch context for dependencies (not settings) - if 'with patch(' in line and ('get_series_app' in line or 'get_nfo_service' in line): - # Skip this line and continuation lines until we find the closing '):' - indent = len(line) - len(line.lstrip()) - i += 1 - - # Skip continuation lines - while i < len(lines): - current = lines[i] - # Check if it's a continuation - if (current.strip().startswith('patch(') or - current.strip().startswith('), patch(') or - current.strip().startswith('return_value=') or - (current.strip() == '):' and not lines[i-1].strip().startswith('mock_settings'))): - i += 1 - if current.strip() == '):': - break - else: - break - - # If next line is blank, skip it too - if i < len(lines) and not lines[i].strip(): - i += 1 - - # Keep settings patches but remove dependency patches from them - elif 'with patch(' in line and 'settings' in line: - # This is a settings patch - keep it but might need to simplify - # Check if it's multi-line with dependency patches - if '\\' in line: # Multi-line patch - # Keep the settings patch line - new_lines.append(line) - i += 1 - - # Skip dependency patches in the multi-line context - while i < len(lines): - current = lines[i] - if ('get_series_app' in current or 'get_nfo_service' in current or - 'patch(' in current): - i += 1 - if current.strip() == '):': - # Found end of patch context, adjust indentation - i += 1 - break - else: - # Not a patch continuation, this is actual code - break - - # Dedent the code that was inside patch context by 4 spaces - while i < len(lines): - current = lines[i] - current_indent = len(current) - len(current.lstrip()) - - # Blank line - if not current.strip(): - new_lines.append(current) - i += 1 - continue - - # If we hit a new test or class, we're done - if (current.strip().startswith('def test_') or - current.strip().startswith('class ') or - current.strip().startswith('@pytest')): - break - - # Dedent by 4 if indented - if current_indent >= 12: - new_lines.append(' ' * (current_indent - 4) + current.lstrip()) - else: - new_lines.append(current) - i += 1 - else: - # Single line settings patch - should not happen but keep it - new_lines.append(line) - i += 1 - else: - new_lines.append(line) - i += 1 - -with open('tests/api/test_nfo_endpoints.py', 'w') as f: - f.writelines(new_lines) - -print("Cleaned up patch contexts") diff --git a/playwright.config.js b/playwright.config.js deleted file mode 100644 index e19f13e..0000000 --- a/playwright.config.js +++ /dev/null @@ -1,88 +0,0 @@ -import { defineConfig, devices } from '@playwright/test'; - -/** - * Playwright configuration for E2E tests - * @see https://playwright.dev/docs/test-configuration - */ -export default defineConfig({ - // Test directory - testDir: './tests/frontend/e2e', - - // Maximum time one test can run for - timeout: 30 * 1000, - - // Run tests in parallel - fullyParallel: true, - - // Fail the build on CI if you accidentally left test.only in the source code - forbidOnly: !!process.env.CI, - - // Retry on CI only - retries: process.env.CI ? 2 : 0, - - // Opt out of parallel tests on CI - workers: process.env.CI ? 1 : undefined, - - // Reporter to use - reporter: [ - ['html', { outputFolder: 'playwright-report' }], - ['list'] - ], - - // Shared settings for all the projects below - use: { - // Base URL to use in actions like `await page.goto('/')` - baseURL: 'http://127.0.0.1:8000', - - // Collect trace when retrying the failed test - trace: 'on-first-retry', - - // Screenshot on failure - screenshot: 'only-on-failure', - - // Video on failure - video: 'retain-on-failure', - - // Action timeout - actionTimeout: 10 * 1000, - - // Navigation timeout - navigationTimeout: 30 * 1000 - }, - - // Configure projects for major browsers - projects: [ - { - name: 'chromium', - use: { ...devices['Desktop Chrome'] }, - }, - - // Uncomment for cross-browser testing - // { - // name: 'firefox', - // use: { ...devices['Desktop Firefox'] }, - // }, - // { - // name: 'webkit', - // use: { ...devices['Desktop Safari'] }, - // }, - - // Mobile viewports - // { - // name: 'Mobile Chrome', - // use: { ...devices['Pixel 5'] }, - // }, - // { - // name: 'Mobile Safari', - // use: { ...devices['iPhone 12'] }, - // }, - ], - - // Run your local dev server before starting the tests - webServer: { - command: 'conda run -n AniWorld python -m uvicorn src.server.fastapi_app:app --host 127.0.0.1 --port 8000', - url: 'http://127.0.0.1:8000', - reuseExistingServer: !process.env.CI, - timeout: 120 * 1000, - }, -}); diff --git a/run_tests_capture.py b/run_tests_capture.py deleted file mode 100644 index 1301070..0000000 --- a/run_tests_capture.py +++ /dev/null @@ -1,28 +0,0 @@ -"""Script to run pytest and capture failed test names.""" -import subprocess -import sys - -result = subprocess.run( - [sys.executable, "-m", "pytest", "tests/", "--tb=no", "-q", "--no-header"], - capture_output=True, - text=True, - timeout=600, - cwd="/home/lukas/Volume/repo/AniworldMain", -) - -# Extract FAILED lines -lines = result.stdout.strip().split("\n") -failed = [line for line in lines if line.startswith("FAILED")] - -with open("/tmp/failed_tests.txt", "w") as f: - for line in failed: - f.write(line + "\n") - -# Also write summary -summary_lines = [line for line in lines if "passed" in line or "failed" in line or "error" in line] -print(f"Total FAILED: {len(failed)}") -for line in summary_lines[-3:]: - print(line) -print("---") -for line in failed: - print(line) diff --git a/scripts/migrate_loading_status.py b/scripts/migrate_loading_status.py deleted file mode 100644 index 061b584..0000000 --- a/scripts/migrate_loading_status.py +++ /dev/null @@ -1,97 +0,0 @@ -"""Database migration utility for adding loading status fields. - -This script adds the loading status fields to existing anime_series tables -without Alembic. For new databases, these fields are created automatically -via create_all(). - -Run this after updating the models.py file. -""" -import asyncio -import logging -import sys -from pathlib import Path - -# Add project root to Python path -project_root = Path(__file__).parent.parent -sys.path.insert(0, str(project_root)) - -from sqlalchemy import text -from sqlalchemy.exc import OperationalError - -from src.config.settings import settings -from src.server.database.connection import get_engine, init_db - -logger = logging.getLogger(__name__) -logging.basicConfig(level=logging.INFO) - - -async def migrate_add_loading_status_fields(): - """Add loading status fields to anime_series table if they don't exist.""" - - # Initialize database connection - await init_db() - engine = get_engine() - - if not engine: - logger.error("Failed to get database engine") - return - - # Define the migrations - migrations = [ - ("loading_status", "ALTER TABLE anime_series ADD COLUMN loading_status VARCHAR(50) NOT NULL DEFAULT 'completed'"), - ("episodes_loaded", "ALTER TABLE anime_series ADD COLUMN episodes_loaded BOOLEAN NOT NULL DEFAULT 1"), - ("logo_loaded", "ALTER TABLE anime_series ADD COLUMN logo_loaded BOOLEAN NOT NULL DEFAULT 0"), - ("images_loaded", "ALTER TABLE anime_series ADD COLUMN images_loaded BOOLEAN NOT NULL DEFAULT 0"), - ("loading_started_at", "ALTER TABLE anime_series ADD COLUMN loading_started_at TIMESTAMP"), - ("loading_completed_at", "ALTER TABLE anime_series ADD COLUMN loading_completed_at TIMESTAMP"), - ("loading_error", "ALTER TABLE anime_series ADD COLUMN loading_error VARCHAR(1000)"), - ] - - async with engine.begin() as conn: - for column_name, sql in migrations: - try: - logger.info(f"Adding column: {column_name}") - await conn.execute(text(sql)) - logger.info(f"✅ Successfully added column: {column_name}") - except OperationalError as e: - if "duplicate column name" in str(e).lower() or "already exists" in str(e).lower(): - logger.info(f"⏭️ Column {column_name} already exists, skipping") - else: - logger.error(f"❌ Error adding column {column_name}: {e}") - raise - - logger.info("Migration completed successfully!") - logger.info("All loading status fields are now available in anime_series table") - - -async def rollback_loading_status_fields(): - """Remove loading status fields from anime_series table.""" - - await init_db() - engine = get_engine() - - if not engine: - logger.error("Failed to get database engine") - return - - # SQLite doesn't support DROP COLUMN easily, so we'd need to recreate the table - # For now, just log a warning - logger.warning("Rollback not implemented for SQLite") - logger.warning("To rollback, you would need to:") - logger.warning("1. Create a new table without the loading fields") - logger.warning("2. Copy data from old table") - logger.warning("3. Drop old table and rename new table") - - -def main(): - """Run the migration.""" - import sys - - if len(sys.argv) > 1 and sys.argv[1] == "rollback": - asyncio.run(rollback_loading_status_fields()) - else: - asyncio.run(migrate_add_loading_status_fields()) - - -if __name__ == "__main__": - main() diff --git a/scripts/setup.py b/scripts/setup.py deleted file mode 100644 index 26a999d..0000000 --- a/scripts/setup.py +++ /dev/null @@ -1,421 +0,0 @@ -""" -Aniworld Application Setup Script - -This script handles initial setup, dependency installation, database -initialization, and configuration for the Aniworld application. - -Usage: - python setup.py [--environment {development|production}] [--no-deps] - python setup.py --help -""" - -import argparse -import asyncio -import os -import subprocess -import sys -from pathlib import Path - - -class SetupManager: - """Manages application setup and initialization.""" - - def __init__( - self, - environment: str = "development", - skip_deps: bool = False - ): - """ - Initialize setup manager. - - Args: - environment: Environment mode (development or production) - skip_deps: Skip dependency installation - """ - self.environment = environment - self.skip_deps = skip_deps - self.project_root = Path(__file__).parent.parent - self.conda_env = "AniWorld" - - # ============================================================================ - # Logging - # ============================================================================ - - @staticmethod - def log_info(message: str) -> None: - """Log info message.""" - print(f"\033[34m[INFO]\033[0m {message}") - - @staticmethod - def log_success(message: str) -> None: - """Log success message.""" - print(f"\033[32m[SUCCESS]\033[0m {message}") - - @staticmethod - def log_warning(message: str) -> None: - """Log warning message.""" - print(f"\033[33m[WARNING]\033[0m {message}") - - @staticmethod - def log_error(message: str) -> None: - """Log error message.""" - print(f"\033[31m[ERROR]\033[0m {message}") - - # ============================================================================ - # Validation - # ============================================================================ - - def validate_environment(self) -> bool: - """ - Validate environment parameter. - - Returns: - True if valid, False otherwise - """ - valid_envs = {"development", "production", "testing"} - if self.environment not in valid_envs: - self.log_error( - f"Invalid environment: {self.environment}. " - f"Must be one of: {valid_envs}" - ) - return False - self.log_success(f"Environment: {self.environment}") - return True - - def check_conda_env(self) -> bool: - """ - Check if conda environment exists. - - Returns: - True if exists, False otherwise - """ - result = subprocess.run( - ["conda", "env", "list"], - capture_output=True, - text=True - ) - if self.conda_env in result.stdout: - self.log_success(f"Conda environment '{self.conda_env}' found") - return True - self.log_error( - f"Conda environment '{self.conda_env}' not found. " - f"Create with: conda create -n {self.conda_env} python=3.11" - ) - return False - - def check_python_version(self) -> bool: - """ - Check Python version. - - Returns: - True if version >= 3.9, False otherwise - """ - if sys.version_info < (3, 9): - self.log_error( - f"Python 3.9+ required. Current: {sys.version_info.major}." - f"{sys.version_info.minor}" - ) - return False - self.log_success( - f"Python version: {sys.version_info.major}." - f"{sys.version_info.minor}" - ) - return True - - # ============================================================================ - # Directory Setup - # ============================================================================ - - def create_directories(self) -> bool: - """ - Create necessary directories. - - Returns: - True if successful, False otherwise - """ - try: - directories = [ - "logs", - "data", - "data/config_backups", - "Temp", - "tests", - "scripts", - ] - self.log_info("Creating directories...") - for directory in directories: - dir_path = self.project_root / directory - dir_path.mkdir(parents=True, exist_ok=True) - self.log_success("Directories created") - return True - except Exception as e: - self.log_error(f"Failed to create directories: {e}") - return False - - # ============================================================================ - # Dependency Installation - # ============================================================================ - - def install_dependencies(self) -> bool: - """ - Install Python dependencies. - - Returns: - True if successful, False otherwise - """ - if self.skip_deps: - self.log_warning("Skipping dependency installation") - return True - - try: - requirements_file = self.project_root / "requirements.txt" - if not requirements_file.exists(): - self.log_error( - f"requirements.txt not found at {requirements_file}" - ) - return False - - self.log_info("Installing dependencies...") - subprocess.run( - ["conda", "run", "-n", self.conda_env, - "pip", "install", "-q", "-r", str(requirements_file)], - check=True - ) - self.log_success("Dependencies installed") - return True - except subprocess.CalledProcessError as e: - self.log_error(f"Failed to install dependencies: {e}") - return False - - # ============================================================================ - # Environment Configuration - # ============================================================================ - - def create_env_files(self) -> bool: - """ - Create environment configuration files. - - Returns: - True if successful, False otherwise - """ - try: - self.log_info("Creating environment configuration files...") - - env_file = self.project_root / f".env.{self.environment}" - if env_file.exists(): - self.log_warning(f"{env_file.name} already exists") - return True - - # Create environment file with defaults - env_content = self._get_env_template() - env_file.write_text(env_content) - self.log_success(f"Created {env_file.name}") - return True - except Exception as e: - self.log_error(f"Failed to create env files: {e}") - return False - - def _get_env_template(self) -> str: - """ - Get environment file template. - - Returns: - Environment file content - """ - if self.environment == "production": - return """# Aniworld Production Configuration -# IMPORTANT: Set these values before running in production - -# Security (REQUIRED - generate new values) -JWT_SECRET_KEY=change-this-to-a-secure-random-key -PASSWORD_SALT=change-this-to-a-secure-random-salt -MASTER_PASSWORD_HASH=change-this-to-hashed-password - -# Database (REQUIRED - use PostgreSQL or MySQL in production) -DATABASE_URL=postgresql://user:password@localhost/aniworld -DATABASE_POOL_SIZE=20 -DATABASE_MAX_OVERFLOW=10 - -# Application -ENVIRONMENT=production -ANIME_DIRECTORY=/var/lib/aniworld -TEMP_DIRECTORY=/tmp/aniworld - -# Server -HOST=0.0.0.0 -PORT=8000 -WORKERS=4 - -# Security -CORS_ORIGINS=https://yourdomain.com -ALLOWED_HOSTS=yourdomain.com - -# Logging -LOG_LEVEL=WARNING -LOG_FILE=logs/production.log -LOG_ROTATION_SIZE=10485760 -LOG_RETENTION_DAYS=30 - -# Performance -API_RATE_LIMIT=60 -SESSION_TIMEOUT_HOURS=24 -MAX_CONCURRENT_DOWNLOADS=3 -""" - else: # development - return """# Aniworld Development Configuration - -# Security (Development defaults - NOT for production) -JWT_SECRET_KEY=dev-secret-key-change-in-production -PASSWORD_SALT=dev-salt-change-in-production -MASTER_PASSWORD_HASH=$2b$12$wP0KBVbJKVAb8CdSSXw0NeGTKCkbw4fSAFXIqR2/wDqPSEBn9w7lS -MASTER_PASSWORD=password - -# Database -DATABASE_URL=sqlite:///./data/aniworld_dev.db - -# Application -ENVIRONMENT=development -ANIME_DIRECTORY=/tmp/aniworld_dev -TEMP_DIRECTORY=/tmp/aniworld_dev/temp - -# Server -HOST=127.0.0.1 -PORT=8000 -WORKERS=1 - -# Security -CORS_ORIGINS=* - -# Logging -LOG_LEVEL=DEBUG -LOG_FILE=logs/development.log - -# Performance -API_RATE_LIMIT=1000 -SESSION_TIMEOUT_HOURS=168 -MAX_CONCURRENT_DOWNLOADS=1 -""" - - # ============================================================================ - # Database Initialization - # ============================================================================ - - async def init_database(self) -> bool: - """ - Initialize database. - - Returns: - True if successful, False otherwise - """ - try: - self.log_info("Initializing database...") - # Import and run database initialization - os.chdir(self.project_root) - from src.server.database import init_db - await init_db() - self.log_success("Database initialized") - return True - except Exception as e: - self.log_error(f"Failed to initialize database: {e}") - return False - - # ============================================================================ - # Summary - # ============================================================================ - - def print_summary(self) -> None: - """Print setup summary.""" - self.log_info("=" * 50) - self.log_info("Setup Summary") - self.log_info("=" * 50) - self.log_info(f"Environment: {self.environment}") - self.log_info(f"Conda Environment: {self.conda_env}") - self.log_info(f"Project Root: {self.project_root}") - self.log_info("") - self.log_success("Setup complete!") - self.log_info("") - self.log_info("Next steps:") - self.log_info("1. Configure .env files with your settings") - if self.environment == "production": - self.log_info("2. Set up database (PostgreSQL/MySQL)") - self.log_info("3. Configure security settings") - self.log_info("4. Run: ./scripts/start.sh production") - else: - self.log_info("2. Run: ./scripts/start.sh development") - self.log_info("") - - # ============================================================================ - # Main Setup - # ============================================================================ - - async def run(self) -> int: - """ - Run setup process. - - Returns: - 0 if successful, 1 otherwise - """ - print("\033[34m" + "=" * 50 + "\033[0m") - print("\033[34mAniworld Application Setup\033[0m") - print("\033[34m" + "=" * 50 + "\033[0m") - print() - - # Validation - if not self.validate_environment(): - return 1 - if not self.check_python_version(): - return 1 - if not self.check_conda_env(): - return 1 - - # Setup - if not self.create_directories(): - return 1 - if not self.create_env_files(): - return 1 - if not self.install_dependencies(): - return 1 - - # Initialize database - if not await self.init_database(): - return 1 - - # Summary - self.print_summary() - return 0 - - -async def main() -> int: - """ - Main entry point. - - Returns: - Exit code - """ - parser = argparse.ArgumentParser( - description="Aniworld Application Setup" - ) - parser.add_argument( - "--environment", - choices=["development", "production", "testing"], - default="development", - help="Environment to set up (default: development)" - ) - parser.add_argument( - "--no-deps", - action="store_true", - help="Skip dependency installation" - ) - - args = parser.parse_args() - - setup = SetupManager( - environment=args.environment, - skip_deps=args.no_deps - ) - return await setup.run() - - -if __name__ == "__main__": - exit_code = asyncio.run(main()) - sys.exit(exit_code) diff --git a/scripts/start.sh b/scripts/start.sh deleted file mode 100644 index 498a94f..0000000 --- a/scripts/start.sh +++ /dev/null @@ -1,225 +0,0 @@ -#!/bin/bash - -################################################################################ -# Aniworld Application Startup Script -# -# This script initializes the development or production environment, -# installs dependencies, sets up the database, and starts the application. -# -# Usage: -# ./start.sh [development|production] [--no-install] -# -# Environment Variables: -# ENVIRONMENT: 'development' or 'production' (default: development) -# CONDA_ENV: Conda environment name (default: AniWorld) -# PORT: Server port (default: 8000) -# HOST: Server host (default: 127.0.0.1) -# -################################################################################ - -set -euo pipefail - -# ============================================================================ -# Configuration -# ============================================================================ - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" -CONDA_ENV="${CONDA_ENV:-AniWorld}" -ENVIRONMENT="${1:-development}" -INSTALL_DEPS="${INSTALL_DEPS:-true}" -PORT="${PORT:-8000}" -HOST="${HOST:-127.0.0.1}" - -# ============================================================================ -# Color Output -# ============================================================================ - -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -# ============================================================================ -# Functions -# ============================================================================ - -log_info() { - echo -e "${BLUE}[INFO]${NC} $1" -} - -log_success() { - echo -e "${GREEN}[SUCCESS]${NC} $1" -} - -log_warning() { - echo -e "${YELLOW}[WARNING]${NC} $1" -} - -log_error() { - echo -e "${RED}[ERROR]${NC} $1" -} - -# Check if conda environment exists -check_conda_env() { - if ! conda env list | grep -q "^$CONDA_ENV "; then - log_error "Conda environment '$CONDA_ENV' not found." - log_info "Create it with: conda create -n $CONDA_ENV python=3.11" - exit 1 - fi - log_success "Conda environment '$CONDA_ENV' found." -} - -# Validate environment parameter -validate_environment() { - if [[ ! "$ENVIRONMENT" =~ ^(development|production|testing)$ ]]; then - log_error "Invalid environment: $ENVIRONMENT" - log_info "Valid options: development, production, testing" - exit 1 - fi - log_success "Environment set to: $ENVIRONMENT" -} - -# Create necessary directories -create_directories() { - log_info "Creating necessary directories..." - mkdir -p "$PROJECT_ROOT/logs" - mkdir -p "$PROJECT_ROOT/data" - mkdir -p "$PROJECT_ROOT/data/config_backups" - mkdir -p "$PROJECT_ROOT/Temp" - log_success "Directories created." -} - -# Install dependencies -install_dependencies() { - if [[ "$INSTALL_DEPS" != "true" ]]; then - log_warning "Skipping dependency installation." - return - fi - - log_info "Installing dependencies..." - conda run -n "$CONDA_ENV" pip install -q -r "$PROJECT_ROOT/requirements.txt" - log_success "Dependencies installed." -} - -# Initialize database -init_database() { - log_info "Initializing database..." - cd "$PROJECT_ROOT" - conda run -n "$CONDA_ENV" \ - python -c "from src.server.database import init_db; import asyncio; asyncio.run(init_db())" - log_success "Database initialized." -} - -# Create environment file if it doesn't exist -create_env_file() { - ENV_FILE="$PROJECT_ROOT/.env.$ENVIRONMENT" - if [[ ! -f "$ENV_FILE" ]]; then - log_warning "Creating $ENV_FILE with defaults..." - cat > "$ENV_FILE" << EOF -# Aniworld Configuration for $ENVIRONMENT - -# Security Settings -JWT_SECRET_KEY=your-secret-key-here -PASSWORD_SALT=your-salt-here -MASTER_PASSWORD_HASH=\$2b\$12\$wP0KBVbJKVAb8CdSSXw0NeGTKCkbw4fSAFXIqR2/wDqPSEBn9w7lS - -# Database -DATABASE_URL=sqlite:///./data/aniworld_${ENVIRONMENT}.db - -# Application -ENVIRONMENT=${ENVIRONMENT} -ANIME_DIRECTORY=/path/to/anime - -# Server -PORT=${PORT} -HOST=${HOST} - -# Logging -LOG_LEVEL=$([ "$ENVIRONMENT" = "production" ] && echo "WARNING" || echo "DEBUG") - -# Features (development only) -$([ "$ENVIRONMENT" = "development" ] && echo "DEBUG=true" || echo "DEBUG=false") -EOF - log_success "Created $ENV_FILE - please configure with your settings" - fi -} - -# Start the application -start_application() { - log_info "Starting Aniworld application..." - log_info "Environment: $ENVIRONMENT" - log_info "Conda Environment: $CONDA_ENV" - log_info "Server: http://$HOST:$PORT" - - cd "$PROJECT_ROOT" - - case "$ENVIRONMENT" in - development) - log_info "Starting in development mode with auto-reload..." - conda run -n "$CONDA_ENV" \ - python -m uvicorn \ - src.server.fastapi_app:app \ - --host "$HOST" \ - --port "$PORT" \ - --reload - ;; - production) - WORKERS="${WORKERS:-4}" - log_info "Starting in production mode with $WORKERS workers..." - conda run -n "$CONDA_ENV" \ - python -m uvicorn \ - src.server.fastapi_app:app \ - --host "$HOST" \ - --port "$PORT" \ - --workers "$WORKERS" \ - --worker-class "uvicorn.workers.UvicornWorker" - ;; - testing) - log_warning "Starting in testing mode..." - # Testing mode typically runs tests instead of starting server - conda run -n "$CONDA_ENV" \ - python -m pytest tests/ -v --tb=short - ;; - *) - log_error "Unknown environment: $ENVIRONMENT" - exit 1 - ;; - esac -} - -# ============================================================================ -# Main Script -# ============================================================================ - -main() { - log_info "==========================================" - log_info "Aniworld Application Startup" - log_info "==========================================" - - # Parse command-line options - while [[ $# -gt 0 ]]; do - case "$1" in - --no-install) - INSTALL_DEPS="false" - shift - ;; - *) - ENVIRONMENT="$1" - shift - ;; - esac - done - - validate_environment - check_conda_env - create_directories - create_env_file - install_dependencies - init_database - start_application -} - -# Run main function -main "$@" diff --git a/scripts/test_nfo_integration.py b/scripts/test_nfo_integration.py deleted file mode 100644 index 6c1bae2..0000000 --- a/scripts/test_nfo_integration.py +++ /dev/null @@ -1,294 +0,0 @@ -"""Manual integration test for NFO functionality. - -This script tests the complete NFO generation workflow with real TMDB API calls. -It's intended for manual verification, not automated testing. - -Usage: - 1. Set TMDB_API_KEY environment variable - 2. Run: python scripts/test_nfo_integration.py - 3. Check output in test_output/ directory - -Requirements: - - Valid TMDB API key (get from https://www.themoviedb.org/settings/api) - - Internet connection - - Write permissions for test_output/ directory -""" - -import asyncio -import os -import sys -from pathlib import Path - -# Add src to path -sys.path.insert(0, str(Path(__file__).parent.parent)) - -from src.core.entities.nfo_models import TVShowNFO -from src.core.services.nfo_service import NFOService -from src.core.services.tmdb_client import TMDBAPIError, TMDBClient -from src.core.utils.nfo_generator import generate_tvshow_nfo, validate_nfo_xml - - -async def test_tmdb_client(): - """Test TMDB client basic functionality.""" - print("\n=== Testing TMDB Client ===") - - api_key = os.getenv("TMDB_API_KEY") - if not api_key: - print("❌ TMDB_API_KEY environment variable not set") - print(" Get your API key from: https://www.themoviedb.org/settings/api") - return False - - try: - async with TMDBClient(api_key=api_key) as client: - # Test 1: Search for a show - print("\n1. Searching for 'Attack on Titan'...") - results = await client.search_tv_show("Attack on Titan") - - if results and results.get("results"): - show = results["results"][0] - print(f" ✅ Found: {show['name']} (ID: {show['id']})") - show_id = show["id"] - else: - print(" ❌ No results found") - return False - - # Test 2: Get show details - print(f"\n2. Getting details for show ID {show_id}...") - details = await client.get_tv_show_details( - show_id, - append_to_response="credits,external_ids,images" - ) - print(f" ✅ Title: {details['name']}") - print(f" ✅ First Air Date: {details.get('first_air_date', 'N/A')}") - print(f" ✅ Rating: {details.get('vote_average', 'N/A')}/10") - - # Test 3: Get external IDs - if "external_ids" in details: - ext_ids = details["external_ids"] - print(f" ✅ IMDB ID: {ext_ids.get('imdb_id', 'N/A')}") - print(f" ✅ TVDB ID: {ext_ids.get('tvdb_id', 'N/A')}") - - # Test 4: Get images - if "images" in details: - images = details["images"] - print(f" ✅ Posters: {len(images.get('posters', []))}") - print(f" ✅ Backdrops: {len(images.get('backdrops', []))}") - print(f" ✅ Logos: {len(images.get('logos', []))}") - - # Test 5: Get image URL - if details.get("poster_path"): - url = client.get_image_url(details["poster_path"], "w500") - print(f" ✅ Poster URL: {url[:60]}...") - - return True - - except TMDBAPIError as e: - print(f" ❌ TMDB API Error: {e}") - return False - except Exception as e: - print(f" ❌ Unexpected Error: {e}") - import traceback - traceback.print_exc() - return False - - -async def test_nfo_generation(): - """Test NFO XML generation.""" - print("\n=== Testing NFO Generation ===") - - try: - # Create a sample NFO model - print("\n1. Creating sample TVShowNFO model...") - from src.core.entities.nfo_models import ( - ActorInfo, - ImageInfo, - RatingInfo, - UniqueID, - ) - - nfo = TVShowNFO( - title="Test Show", - originaltitle="Test Show Original", - year=2020, - plot="This is a test show for NFO generation validation.", - runtime=45, - premiered="2020-01-15", - status="Continuing", - genre=["Action", "Drama", "Animation"], - studio=["Test Studio"], - country=["Japan"], - ratings=[RatingInfo( - name="themoviedb", - value=8.5, - votes=1000, - max_rating=10, - default=True - )], - actors=[ - ActorInfo(name="Test Actor 1", role="Main Character"), - ActorInfo(name="Test Actor 2", role="Villain") - ], - thumb=[ImageInfo(url="https://image.tmdb.org/t/p/w500/poster.jpg")], - fanart=[ImageInfo(url="https://image.tmdb.org/t/p/original/fanart.jpg")], - uniqueid=[ - UniqueID(type="tmdb", value="12345", default=False), - UniqueID(type="tvdb", value="67890", default=True) - ], - tmdbid=12345, - tvdbid=67890, - imdbid="tt1234567" - ) - print(" ✅ TVShowNFO model created") - - # Test 2: Generate XML - print("\n2. Generating XML...") - xml_string = generate_tvshow_nfo(nfo) - print(f" ✅ Generated {len(xml_string)} characters") - - # Test 3: Validate XML - print("\n3. Validating XML...") - validate_nfo_xml(xml_string) - print(" ✅ XML is valid") - - # Test 4: Save to file - output_dir = Path("test_output") - output_dir.mkdir(exist_ok=True) - nfo_path = output_dir / "test_tvshow.nfo" - nfo_path.write_text(xml_string, encoding="utf-8") - print(f" ✅ Saved to: {nfo_path}") - - # Test 5: Show sample - print("\n4. Sample XML (first 500 chars):") - print(" " + xml_string[:500].replace("\n", "\n ")) - - return True - - except Exception as e: - print(f" ❌ Error: {e}") - import traceback - traceback.print_exc() - return False - - -async def test_nfo_service(): - """Test complete NFO service workflow.""" - print("\n=== Testing NFO Service ===") - - api_key = os.getenv("TMDB_API_KEY") - if not api_key: - print("❌ TMDB_API_KEY environment variable not set") - return False - - try: - # Create test output directory - output_dir = Path("test_output") - output_dir.mkdir(exist_ok=True) - - # Create a test series folder - test_series = output_dir / "Attack_on_Titan" - test_series.mkdir(exist_ok=True) - - print(f"\n1. Creating NFO for 'Attack on Titan'...") - print(f" Output directory: {test_series}") - - # Initialize NFO service - nfo_service = NFOService( - tmdb_api_key=api_key, - anime_directory=str(output_dir), - image_size="w500" - ) - - # Create NFO - nfo_path = await nfo_service.create_tvshow_nfo( - serie_name="Attack on Titan", - serie_folder="Attack_on_Titan", - year=2013, - download_poster=True, - download_logo=True, - download_fanart=True - ) - - print(f" ✅ NFO created: {nfo_path}") - - # Check if files were created - print("\n2. Checking created files...") - files_created = { - "tvshow.nfo": (test_series / "tvshow.nfo").exists(), - "poster.jpg": (test_series / "poster.jpg").exists(), - "logo.png": (test_series / "logo.png").exists(), - "fanart.jpg": (test_series / "fanart.jpg").exists(), - } - - for filename, exists in files_created.items(): - status = "✅" if exists else "❌" - size = "" - if exists: - file_path = test_series / filename - size = f" ({file_path.stat().st_size:,} bytes)" - print(f" {status} {filename}{size}") - - # Read and validate NFO - if files_created["tvshow.nfo"]: - print("\n3. Validating generated NFO...") - nfo_content = nfo_path.read_text(encoding="utf-8") - validate_nfo_xml(nfo_content) - print(" ✅ NFO is valid XML") - - # Show sample - print("\n4. NFO Content (first 800 chars):") - print(" " + nfo_content[:800].replace("\n", "\n ")) - - return all(files_created.values()) - - except Exception as e: - print(f" ❌ Error: {e}") - import traceback - traceback.print_exc() - return False - - -async def main(): - """Run all integration tests.""" - print("=" * 70) - print("NFO Functionality Integration Tests") - print("=" * 70) - print("\nNOTE: This requires a valid TMDB API key set as environment variable.") - print("Get your API key from: https://www.themoviedb.org/settings/api") - print("Set it with: export TMDB_API_KEY='your_api_key_here'") - - results = [] - - # Test 1: TMDB Client - results.append(("TMDB Client", await test_tmdb_client())) - - # Test 2: NFO Generation - results.append(("NFO Generation", await test_nfo_generation())) - - # Test 3: NFO Service (full workflow) - results.append(("NFO Service", await test_nfo_service())) - - # Summary - print("\n" + "=" * 70) - print("SUMMARY") - print("=" * 70) - - for test_name, passed in results: - status = "✅ PASSED" if passed else "❌ FAILED" - print(f"{test_name:.<50} {status}") - - all_passed = all(result for _, result in results) - - if all_passed: - print("\n🎉 All tests passed!") - print("\nGenerated files are in the 'test_output/' directory.") - print("You can import tvshow.nfo into Kodi/Plex/Jellyfin to verify compatibility.") - else: - print("\n⚠️ Some tests failed. Check the output above for details.") - return 1 - - return 0 - - -if __name__ == "__main__": - exit_code = asyncio.run(main()) - sys.exit(exit_code) diff --git a/scripts/test_nfo_update.py b/scripts/test_nfo_update.py deleted file mode 100644 index af798db..0000000 --- a/scripts/test_nfo_update.py +++ /dev/null @@ -1,183 +0,0 @@ -"""Test script for NFOService.update_tvshow_nfo() functionality. - -This script tests the update functionality by: -1. Creating a test NFO file with TMDB ID -2. Updating it with fresh data from TMDB -3. Verifying the update worked correctly -""" - -import asyncio -import logging -import sys -from pathlib import Path - -# Add src to path -sys.path.insert(0, str(Path(__file__).parent.parent)) - -from src.config.settings import settings -from src.core.services.nfo_service import NFOService -from src.core.services.tmdb_client import TMDBAPIError - -# Configure logging -logging.basicConfig( - level=logging.INFO, - format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" -) -logger = logging.getLogger(__name__) - - -async def test_update_nfo(): - """Test NFO update functionality.""" - - # Check if TMDB API key is configured - if not settings.tmdb_api_key: - logger.error("TMDB_API_KEY not configured in environment") - logger.error("Set TMDB_API_KEY in .env file or environment variables") - return False - - # Test series: Attack on Titan (TMDB ID: 1429) - test_serie_name = "Attack on Titan" - test_serie_folder = "test_update_nfo" - test_tmdb_id = 1429 - - # Create test folder - test_folder = Path(settings.anime_directory) / test_serie_folder - test_folder.mkdir(parents=True, exist_ok=True) - logger.info(f"Created test folder: {test_folder}") - - # Initialize NFO service - nfo_service = NFOService( - tmdb_api_key=settings.tmdb_api_key, - anime_directory=settings.anime_directory, - image_size=settings.nfo_image_size - ) - - try: - # Step 1: Create initial NFO - logger.info("=" * 60) - logger.info("STEP 1: Creating initial NFO") - logger.info("=" * 60) - - nfo_path = await nfo_service.create_tvshow_nfo( - serie_name=test_serie_name, - serie_folder=test_serie_folder, - year=2013, - download_poster=False, # Skip downloads for faster testing - download_logo=False, - download_fanart=False - ) - - logger.info(f"✓ Initial NFO created: {nfo_path}") - - # Read initial NFO content - initial_content = nfo_path.read_text(encoding="utf-8") - logger.info(f"Initial NFO size: {len(initial_content)} bytes") - - # Verify TMDB ID is in the file - if str(test_tmdb_id) not in initial_content: - logger.error(f"TMDB ID {test_tmdb_id} not found in NFO!") - return False - logger.info(f"✓ TMDB ID {test_tmdb_id} found in NFO") - - # Step 2: Update the NFO - logger.info("") - logger.info("=" * 60) - logger.info("STEP 2: Updating NFO with fresh data") - logger.info("=" * 60) - - updated_path = await nfo_service.update_tvshow_nfo( - serie_folder=test_serie_folder, - download_media=False # Skip downloads - ) - - logger.info(f"✓ NFO updated: {updated_path}") - - # Read updated content - updated_content = updated_path.read_text(encoding="utf-8") - logger.info(f"Updated NFO size: {len(updated_content)} bytes") - - # Verify TMDB ID is still in the file - if str(test_tmdb_id) not in updated_content: - logger.error(f"TMDB ID {test_tmdb_id} not found after update!") - return False - logger.info(f"✓ TMDB ID {test_tmdb_id} still present after update") - - # Step 3: Test update on non-existent NFO (should fail) - logger.info("") - logger.info("=" * 60) - logger.info("STEP 3: Testing error handling") - logger.info("=" * 60) - - try: - await nfo_service.update_tvshow_nfo( - serie_folder="non_existent_folder", - download_media=False - ) - logger.error("✗ Should have raised FileNotFoundError!") - return False - except FileNotFoundError as e: - logger.info(f"✓ Correctly raised FileNotFoundError: {e}") - - # Step 4: Test update on NFO without TMDB ID - logger.info("") - logger.info("STEP 4: Testing NFO without TMDB ID") - - # Create a minimal NFO without TMDB ID - no_id_folder = test_folder.parent / "test_no_tmdb_id" - no_id_folder.mkdir(parents=True, exist_ok=True) - no_id_nfo = no_id_folder / "tvshow.nfo" - no_id_nfo.write_text( - '\n' - '\n' - ' Test Show\n' - '', - encoding="utf-8" - ) - - try: - await nfo_service.update_tvshow_nfo( - serie_folder="test_no_tmdb_id", - download_media=False - ) - logger.error("✗ Should have raised TMDBAPIError!") - return False - except TMDBAPIError as e: - logger.info(f"✓ Correctly raised TMDBAPIError: {e}") - - logger.info("") - logger.info("=" * 60) - logger.info("✓ ALL TESTS PASSED") - logger.info("=" * 60) - return True - - except Exception as e: - logger.error(f"✗ Test failed with error: {e}", exc_info=True) - return False - - finally: - await nfo_service.close() - - # Cleanup test folders - logger.info("\nCleaning up test folders...") - import shutil - try: - if test_folder.exists(): - shutil.rmtree(test_folder) - logger.info(f"Removed: {test_folder}") - - no_id_folder = test_folder.parent / "test_no_tmdb_id" - if no_id_folder.exists(): - shutil.rmtree(no_id_folder) - logger.info(f"Removed: {no_id_folder}") - except Exception as e: - logger.warning(f"Cleanup error: {e}") - - -async def main(): - """Main entry point.""" - success = await test_update_nfo() - sys.exit(0 if success else 1) - - -if __name__ == "__main__": - asyncio.run(main()) diff --git a/start_server.sh b/start_server.sh deleted file mode 100644 index 3fd80d4..0000000 --- a/start_server.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash -# Startup script for Aniworld FastAPI server with proper logging - -# Activate conda environment and run the server -conda run -n AniWorld python run_server.py diff --git a/stop_server.sh b/stop_server.sh deleted file mode 100644 index 7e89368..0000000 --- a/stop_server.sh +++ /dev/null @@ -1,94 +0,0 @@ -#!/bin/bash -# Stop Aniworld FastAPI Server (Graceful Shutdown) -# -# This script performs a graceful shutdown by sending SIGTERM first, -# allowing the application to clean up resources properly before -# falling back to SIGKILL if needed. - -GRACEFUL_TIMEOUT=30 # seconds to wait for graceful shutdown - -echo "Stopping Aniworld server (graceful shutdown)..." - -# Function to wait for a process to terminate -wait_for_process() { - local pid=$1 - local timeout=$2 - local count=0 - - while [ $count -lt $timeout ]; do - if ! kill -0 "$pid" 2>/dev/null; then - return 0 # Process terminated - fi - sleep 1 - count=$((count + 1)) - echo -ne "\r Waiting for graceful shutdown... ${count}/${timeout}s" - done - echo "" - return 1 # Timeout -} - -# Method 1: Gracefully stop uvicorn processes -UVICORN_PIDS=$(pgrep -f "uvicorn.*fastapi_app:app") -if [ -n "$UVICORN_PIDS" ]; then - echo "Sending SIGTERM to uvicorn processes..." - for pid in $UVICORN_PIDS; do - kill -TERM "$pid" 2>/dev/null - done - - # Wait for graceful shutdown - all_terminated=true - for pid in $UVICORN_PIDS; do - if ! wait_for_process "$pid" "$GRACEFUL_TIMEOUT"; then - all_terminated=false - echo " Process $pid did not terminate gracefully, forcing..." - kill -9 "$pid" 2>/dev/null - fi - done - - if $all_terminated; then - echo "✓ Uvicorn processes stopped gracefully" - else - echo "✓ Uvicorn processes stopped (forced)" - fi -else - echo "✓ No uvicorn processes running" -fi - -# Method 2: Gracefully stop any process using port 8000 -PORT_PID=$(lsof -ti:8000) -if [ -n "$PORT_PID" ]; then - echo "Found process on port 8000 (PID: $PORT_PID)" - - # Send SIGTERM first - kill -TERM "$PORT_PID" 2>/dev/null - - if wait_for_process "$PORT_PID" "$GRACEFUL_TIMEOUT"; then - echo "✓ Process on port 8000 stopped gracefully" - else - echo " Graceful shutdown timed out, forcing..." - kill -9 "$PORT_PID" 2>/dev/null - echo "✓ Process on port 8000 stopped (forced)" - fi -else - echo "✓ Port 8000 is already free" -fi - -# Method 3: Gracefully stop run_server.py processes -SERVER_PIDS=$(pgrep -f "run_server.py") -if [ -n "$SERVER_PIDS" ]; then - echo "Sending SIGTERM to run_server.py processes..." - for pid in $SERVER_PIDS; do - kill -TERM "$pid" 2>/dev/null - done - - for pid in $SERVER_PIDS; do - if ! wait_for_process "$pid" 10; then - kill -9 "$pid" 2>/dev/null - fi - done - echo "✓ Stopped run_server.py processes" -fi - -echo "" -echo "Server stopped successfully!" -echo "You can restart it with: ./start_server.sh" diff --git a/vitest.config.js b/vitest.config.js deleted file mode 100644 index 1ced211..0000000 --- a/vitest.config.js +++ /dev/null @@ -1,46 +0,0 @@ -import path from 'path'; -import { defineConfig } from 'vitest/config'; - -export default defineConfig({ - test: { - // Use happy-dom for faster DOM testing - environment: 'happy-dom', - - // Include test files - include: ['tests/frontend/unit/**/*.{test,spec}.{js,mjs,cjs}'], - - // Global test utilities - globals: true, - - // Coverage configuration - coverage: { - provider: 'v8', - reporter: ['text', 'html', 'json'], - reportsDirectory: './htmlcov_frontend', - include: ['src/server/web/static/js/**/*.js'], - exclude: [ - 'node_modules/', - 'tests/', - '**/*.test.js', - '**/*.spec.js' - ], - all: true, - lines: 80, - functions: 80, - branches: 80, - statements: 80 - }, - - // Test timeout (30 seconds) - testTimeout: 30000, - - // Hook timeout (10 seconds) - hookTimeout: 10000 - }, - - resolve: { - alias: { - '@': path.resolve(__dirname, './src/server/web/static/js') - } - } -});