From bccfcafe0ecdbd5e5d1313eed8f8fe3759b649e5 Mon Sep 17 00:00:00 2001 From: JSC Date: Fri, 19 Sep 2025 16:41:11 +0200 Subject: [PATCH] feat: Update CORS origins to allow Chrome extensions and improve logging in migration tool --- app/core/config.py | 9 +++-- app/core/database.py | 5 ++- app/services/credit.py | 4 ++- app/services/scheduler.py | 22 ++++++++---- app/services/task_handlers.py | 14 ++++++-- migrate.py | 44 +++++++++++++++-------- tests/core/test_api_token_dependencies.py | 2 +- tests/repositories/test_user.py | 2 +- tests/services/test_auth_service.py | 2 +- 9 files changed, 72 insertions(+), 32 deletions(-) diff --git a/app/core/config.py b/app/core/config.py index ca890dd..9b51497 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -23,7 +23,10 @@ class Settings(BaseSettings): BACKEND_URL: str = "http://localhost:8000" # Backend base URL # CORS Configuration - CORS_ORIGINS: list[str] = ["http://localhost:8001"] # Allowed origins for CORS + CORS_ORIGINS: list[str] = [ + "http://localhost:8001", # Frontend development + "chrome-extension://*", # Chrome extensions + ] # Database Configuration DATABASE_URL: str = "sqlite+aiosqlite:///data/soundboard.db" @@ -37,7 +40,9 @@ class Settings(BaseSettings): LOG_FORMAT: str = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" # JWT Configuration - JWT_SECRET_KEY: str = "your-secret-key-change-in-production" # noqa: S105 default value if none set in .env + JWT_SECRET_KEY: str = ( + "your-secret-key-change-in-production" # noqa: S105 default value if none set in .env + ) JWT_ALGORITHM: str = "HS256" JWT_ACCESS_TOKEN_EXPIRE_MINUTES: int = 15 JWT_REFRESH_TOKEN_EXPIRE_DAYS: int = 7 diff --git a/app/core/database.py b/app/core/database.py index 152d38d..0e3a4f4 100644 --- a/app/core/database.py +++ b/app/core/database.py @@ -1,11 +1,12 @@ from collections.abc import AsyncGenerator, Callable +from alembic.config import Config from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine -from sqlmodel import SQLModel from sqlmodel.ext.asyncio.session import AsyncSession # Import all models to ensure SQLModel metadata discovery import app.models # noqa: F401 +from alembic import command from app.core.config import settings from app.core.logging import get_logger @@ -44,8 +45,6 @@ async def init_db() -> None: try: logger.info("Running database migrations") # Run Alembic migrations programmatically - from alembic import command - from alembic.config import Config # Get the alembic config alembic_cfg = Config("alembic.ini") diff --git a/app/services/credit.py b/app/services/credit.py index d729784..dd0288a 100644 --- a/app/services/credit.py +++ b/app/services/credit.py @@ -598,7 +598,9 @@ class CreditService: current_credits = user.credits plan_credits = user.plan.credits max_credits = user.plan.max_credits - target_credits = min(current_credits + plan_credits, max_credits) + target_credits = min( + current_credits + plan_credits, max_credits, + ) credits_added = target_credits - current_credits stats["total_credits_added"] += credits_added else: diff --git a/app/services/scheduler.py b/app/services/scheduler.py index 8d75016..08f5c2a 100644 --- a/app/services/scheduler.py +++ b/app/services/scheduler.py @@ -348,8 +348,12 @@ class SchedulerService: # Check if task is still active and pending if not task.is_active or task.status != TaskStatus.PENDING: logger.warning( - "Task %s execution skipped - is_active: %s, status: %s (should be %s)", - task_id, task.is_active, task.status, TaskStatus.PENDING, + "Task %s execution skipped - is_active: %s, status: %s " + "(should be %s)", + task_id, + task.is_active, + task.status, + TaskStatus.PENDING, ) return @@ -364,7 +368,9 @@ class SchedulerService: # Mark task as running logger.info( - "Task %s starting execution (type: %s)", task_id, task.recurrence_type, + "Task %s starting execution (type: %s)", + task_id, + task.recurrence_type, ) await repo.mark_as_running(task) @@ -383,7 +389,8 @@ class SchedulerService: # For CRON tasks, update execution metadata but keep PENDING # APScheduler handles the recurring schedule automatically logger.info( - "Task %s (CRON) executed successfully, updating metadata", task_id, + "Task %s (CRON) executed successfully, updating metadata", + task_id, ) task.last_executed_at = datetime.now(tz=UTC) task.executions_count += 1 @@ -392,8 +399,11 @@ class SchedulerService: session.add(task) await session.commit() logger.info( - "Task %s (CRON) metadata updated, status: %s, executions: %s", - task_id, task.status, task.executions_count, + "Task %s (CRON) metadata updated, status: %s, " + "executions: %s", + task_id, + task.status, + task.executions_count, ) else: # For non-CRON recurring tasks, calculate next execution diff --git a/app/services/task_handlers.py b/app/services/task_handlers.py index c6c356f..e9ff49d 100644 --- a/app/services/task_handlers.py +++ b/app/services/task_handlers.py @@ -80,11 +80,19 @@ class TaskHandlerRegistry: msg = f"Invalid user_id format: {user_id}" raise TaskExecutionError(msg) from e - transaction = await self.credit_service.recharge_user_credits_auto(user_id_int) + transaction = await self.credit_service.recharge_user_credits_auto( + user_id_int, + ) if transaction: - logger.info("Recharged credits for user %s: %s credits added", user_id, transaction.amount) + logger.info( + "Recharged credits for user %s: %s credits added", + user_id, + transaction.amount, + ) else: - logger.info("No credits added for user %s (already at maximum)", user_id) + logger.info( + "No credits added for user %s (already at maximum)", user_id, + ) else: # Recharge all users (system task) stats = await self.credit_service.recharge_all_users_credits() diff --git a/migrate.py b/migrate.py index 5af22f4..37a8ca1 100755 --- a/migrate.py +++ b/migrate.py @@ -2,25 +2,33 @@ """Database migration CLI tool.""" import argparse +import logging import sys from pathlib import Path -from alembic import command from alembic.config import Config +from alembic import command + +# Set up logging +logger = logging.getLogger(__name__) +logging.basicConfig(level=logging.INFO, format="%(message)s") + def main() -> None: - """Main CLI function for database migrations.""" + """Run database migration CLI tool.""" parser = argparse.ArgumentParser(description="Database migration tool") subparsers = parser.add_subparsers(dest="command", help="Migration commands") # Upgrade command - upgrade_parser = subparsers.add_parser("upgrade", help="Upgrade database to latest revision") + upgrade_parser = subparsers.add_parser( + "upgrade", help="Upgrade database to latest revision", + ) upgrade_parser.add_argument( "revision", nargs="?", default="head", - help="Target revision (default: head)" + help="Target revision (default: head)", ) # Downgrade command @@ -35,8 +43,12 @@ def main() -> None: # Generate migration command revision_parser = subparsers.add_parser("revision", help="Create new migration") - revision_parser.add_argument("-m", "--message", required=True, help="Migration message") - revision_parser.add_argument("--autogenerate", action="store_true", help="Auto-generate migration") + revision_parser.add_argument( + "-m", "--message", required=True, help="Migration message", + ) + revision_parser.add_argument( + "--autogenerate", action="store_true", help="Auto-generate migration", + ) args = parser.parse_args() @@ -47,7 +59,7 @@ def main() -> None: # Get the alembic config config_path = Path("alembic.ini") if not config_path.exists(): - print("Error: alembic.ini not found. Run from the backend directory.") + logger.error("Error: alembic.ini not found. Run from the backend directory.") sys.exit(1) alembic_cfg = Config(str(config_path)) @@ -55,11 +67,15 @@ def main() -> None: try: if args.command == "upgrade": command.upgrade(alembic_cfg, args.revision) - print(f"Successfully upgraded database to revision: {args.revision}") + logger.info( + "Successfully upgraded database to revision: %s", args.revision, + ) elif args.command == "downgrade": command.downgrade(alembic_cfg, args.revision) - print(f"Successfully downgraded database to revision: {args.revision}") + logger.info( + "Successfully downgraded database to revision: %s", args.revision, + ) elif args.command == "current": command.current(alembic_cfg) @@ -70,15 +86,15 @@ def main() -> None: elif args.command == "revision": if args.autogenerate: command.revision(alembic_cfg, message=args.message, autogenerate=True) - print(f"Created new auto-generated migration: {args.message}") + logger.info("Created new auto-generated migration: %s", args.message) else: command.revision(alembic_cfg, message=args.message) - print(f"Created new empty migration: {args.message}") + logger.info("Created new empty migration: %s", args.message) - except Exception as e: - print(f"Error: {e}") + except (OSError, RuntimeError): + logger.exception("Error occurred during migration") sys.exit(1) if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/tests/core/test_api_token_dependencies.py b/tests/core/test_api_token_dependencies.py index e3e79d1..c33d9b3 100644 --- a/tests/core/test_api_token_dependencies.py +++ b/tests/core/test_api_token_dependencies.py @@ -196,7 +196,7 @@ class TestApiTokenDependencies: ) -> None: """Test flexible authentication falls back to JWT when no API token.""" # Mock the get_current_user function (normally imported) - with pytest.raises(Exception, match="Database error|Could not validate"): + with pytest.raises(Exception, match=r"Database error|Could not validate"): # This will fail because we can't easily mock the get_current_user import # In a real test, you'd mock the import or use dependency injection await get_current_user_flexible(mock_auth_service, "jwt_token", None) diff --git a/tests/repositories/test_user.py b/tests/repositories/test_user.py index 9b5106f..d286ce1 100644 --- a/tests/repositories/test_user.py +++ b/tests/repositories/test_user.py @@ -110,7 +110,7 @@ class TestUserRepository: test_session: AsyncSession, ) -> None: """Test creating a new user.""" - free_plan, pro_plan = ensure_plans + free_plan, _pro_plan = ensure_plans plan_id = free_plan.id plan_credits = free_plan.credits diff --git a/tests/services/test_auth_service.py b/tests/services/test_auth_service.py index 774efd3..9032358 100644 --- a/tests/services/test_auth_service.py +++ b/tests/services/test_auth_service.py @@ -42,7 +42,7 @@ class TestAuthService: assert response.user.role == "admin" # First user gets admin role assert response.user.is_active is True # First user gets pro plan - free_plan, pro_plan = ensure_plans + _free_plan, pro_plan = ensure_plans assert response.user.credits == pro_plan.credits assert response.user.plan["code"] == pro_plan.code