feat: Update CORS origins to allow Chrome extensions and improve logging in migration tool
Some checks failed
Backend CI / lint (push) Failing after 10s
Backend CI / test (push) Failing after 1m37s

This commit is contained in:
JSC
2025-09-19 16:41:11 +02:00
parent 1bef694f38
commit bccfcafe0e
9 changed files with 72 additions and 32 deletions

View File

@@ -23,7 +23,10 @@ class Settings(BaseSettings):
BACKEND_URL: str = "http://localhost:8000" # Backend base URL BACKEND_URL: str = "http://localhost:8000" # Backend base URL
# CORS Configuration # 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 Configuration
DATABASE_URL: str = "sqlite+aiosqlite:///data/soundboard.db" 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" LOG_FORMAT: str = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
# JWT Configuration # 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_ALGORITHM: str = "HS256"
JWT_ACCESS_TOKEN_EXPIRE_MINUTES: int = 15 JWT_ACCESS_TOKEN_EXPIRE_MINUTES: int = 15
JWT_REFRESH_TOKEN_EXPIRE_DAYS: int = 7 JWT_REFRESH_TOKEN_EXPIRE_DAYS: int = 7

View File

@@ -1,11 +1,12 @@
from collections.abc import AsyncGenerator, Callable from collections.abc import AsyncGenerator, Callable
from alembic.config import Config
from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine
from sqlmodel import SQLModel
from sqlmodel.ext.asyncio.session import AsyncSession from sqlmodel.ext.asyncio.session import AsyncSession
# Import all models to ensure SQLModel metadata discovery # Import all models to ensure SQLModel metadata discovery
import app.models # noqa: F401 import app.models # noqa: F401
from alembic import command
from app.core.config import settings from app.core.config import settings
from app.core.logging import get_logger from app.core.logging import get_logger
@@ -44,8 +45,6 @@ async def init_db() -> None:
try: try:
logger.info("Running database migrations") logger.info("Running database migrations")
# Run Alembic migrations programmatically # Run Alembic migrations programmatically
from alembic import command
from alembic.config import Config
# Get the alembic config # Get the alembic config
alembic_cfg = Config("alembic.ini") alembic_cfg = Config("alembic.ini")

View File

@@ -598,7 +598,9 @@ class CreditService:
current_credits = user.credits current_credits = user.credits
plan_credits = user.plan.credits plan_credits = user.plan.credits
max_credits = user.plan.max_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 credits_added = target_credits - current_credits
stats["total_credits_added"] += credits_added stats["total_credits_added"] += credits_added
else: else:

View File

@@ -348,8 +348,12 @@ class SchedulerService:
# Check if task is still active and pending # Check if task is still active and pending
if not task.is_active or task.status != TaskStatus.PENDING: if not task.is_active or task.status != TaskStatus.PENDING:
logger.warning( logger.warning(
"Task %s execution skipped - is_active: %s, status: %s (should be %s)", "Task %s execution skipped - is_active: %s, status: %s "
task_id, task.is_active, task.status, TaskStatus.PENDING, "(should be %s)",
task_id,
task.is_active,
task.status,
TaskStatus.PENDING,
) )
return return
@@ -364,7 +368,9 @@ class SchedulerService:
# Mark task as running # Mark task as running
logger.info( 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) await repo.mark_as_running(task)
@@ -383,7 +389,8 @@ class SchedulerService:
# For CRON tasks, update execution metadata but keep PENDING # For CRON tasks, update execution metadata but keep PENDING
# APScheduler handles the recurring schedule automatically # APScheduler handles the recurring schedule automatically
logger.info( 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.last_executed_at = datetime.now(tz=UTC)
task.executions_count += 1 task.executions_count += 1
@@ -392,8 +399,11 @@ class SchedulerService:
session.add(task) session.add(task)
await session.commit() await session.commit()
logger.info( logger.info(
"Task %s (CRON) metadata updated, status: %s, executions: %s", "Task %s (CRON) metadata updated, status: %s, "
task_id, task.status, task.executions_count, "executions: %s",
task_id,
task.status,
task.executions_count,
) )
else: else:
# For non-CRON recurring tasks, calculate next execution # For non-CRON recurring tasks, calculate next execution

View File

@@ -80,11 +80,19 @@ class TaskHandlerRegistry:
msg = f"Invalid user_id format: {user_id}" msg = f"Invalid user_id format: {user_id}"
raise TaskExecutionError(msg) from e 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: 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: 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: else:
# Recharge all users (system task) # Recharge all users (system task)
stats = await self.credit_service.recharge_all_users_credits() stats = await self.credit_service.recharge_all_users_credits()

View File

@@ -2,25 +2,33 @@
"""Database migration CLI tool.""" """Database migration CLI tool."""
import argparse import argparse
import logging
import sys import sys
from pathlib import Path from pathlib import Path
from alembic import command
from alembic.config import Config 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: def main() -> None:
"""Main CLI function for database migrations.""" """Run database migration CLI tool."""
parser = argparse.ArgumentParser(description="Database migration tool") parser = argparse.ArgumentParser(description="Database migration tool")
subparsers = parser.add_subparsers(dest="command", help="Migration commands") subparsers = parser.add_subparsers(dest="command", help="Migration commands")
# Upgrade command # 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( upgrade_parser.add_argument(
"revision", "revision",
nargs="?", nargs="?",
default="head", default="head",
help="Target revision (default: head)" help="Target revision (default: head)",
) )
# Downgrade command # Downgrade command
@@ -35,8 +43,12 @@ def main() -> None:
# Generate migration command # Generate migration command
revision_parser = subparsers.add_parser("revision", help="Create new migration") 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(
revision_parser.add_argument("--autogenerate", action="store_true", help="Auto-generate migration") "-m", "--message", required=True, help="Migration message",
)
revision_parser.add_argument(
"--autogenerate", action="store_true", help="Auto-generate migration",
)
args = parser.parse_args() args = parser.parse_args()
@@ -47,7 +59,7 @@ def main() -> None:
# Get the alembic config # Get the alembic config
config_path = Path("alembic.ini") config_path = Path("alembic.ini")
if not config_path.exists(): 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) sys.exit(1)
alembic_cfg = Config(str(config_path)) alembic_cfg = Config(str(config_path))
@@ -55,11 +67,15 @@ def main() -> None:
try: try:
if args.command == "upgrade": if args.command == "upgrade":
command.upgrade(alembic_cfg, args.revision) 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": elif args.command == "downgrade":
command.downgrade(alembic_cfg, args.revision) 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": elif args.command == "current":
command.current(alembic_cfg) command.current(alembic_cfg)
@@ -70,13 +86,13 @@ def main() -> None:
elif args.command == "revision": elif args.command == "revision":
if args.autogenerate: if args.autogenerate:
command.revision(alembic_cfg, message=args.message, autogenerate=True) 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: else:
command.revision(alembic_cfg, message=args.message) 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: except (OSError, RuntimeError):
print(f"Error: {e}") logger.exception("Error occurred during migration")
sys.exit(1) sys.exit(1)

View File

@@ -196,7 +196,7 @@ class TestApiTokenDependencies:
) -> None: ) -> None:
"""Test flexible authentication falls back to JWT when no API token.""" """Test flexible authentication falls back to JWT when no API token."""
# Mock the get_current_user function (normally imported) # 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 # 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 # In a real test, you'd mock the import or use dependency injection
await get_current_user_flexible(mock_auth_service, "jwt_token", None) await get_current_user_flexible(mock_auth_service, "jwt_token", None)

View File

@@ -110,7 +110,7 @@ class TestUserRepository:
test_session: AsyncSession, test_session: AsyncSession,
) -> None: ) -> None:
"""Test creating a new user.""" """Test creating a new user."""
free_plan, pro_plan = ensure_plans free_plan, _pro_plan = ensure_plans
plan_id = free_plan.id plan_id = free_plan.id
plan_credits = free_plan.credits plan_credits = free_plan.credits

View File

@@ -42,7 +42,7 @@ class TestAuthService:
assert response.user.role == "admin" # First user gets admin role assert response.user.role == "admin" # First user gets admin role
assert response.user.is_active is True assert response.user.is_active is True
# First user gets pro plan # 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.credits == pro_plan.credits
assert response.user.plan["code"] == pro_plan.code assert response.user.plan["code"] == pro_plan.code