From 734521c5c3e624d863218626b4e6a3d02bbbb7e5 Mon Sep 17 00:00:00 2001 From: JSC Date: Sat, 9 Aug 2025 14:43:20 +0200 Subject: [PATCH] feat: Add environment configuration files and update settings for production and development --- .env.development.template | 55 ++++++++++++++++++++++++++++++++ .env.production.template | 50 +++++++++++++++++++++++++++++ app/api/v1/auth.py | 6 ++-- app/api/v1/main.py | 67 +++++++++++++++++++++++++++++++++++++++ app/core/config.py | 8 +++++ app/main.py | 14 ++++++-- app/services/oauth.py | 6 ++-- app/services/socket.py | 3 +- app/utils/cookies.py | 4 +-- 9 files changed, 202 insertions(+), 11 deletions(-) create mode 100644 .env.development.template create mode 100644 .env.production.template diff --git a/.env.development.template b/.env.development.template new file mode 100644 index 0000000..d76faef --- /dev/null +++ b/.env.development.template @@ -0,0 +1,55 @@ +# Development Environment Configuration +# Copy this file to .env for development setup + +# Application Configuration +HOST=localhost +PORT=8000 +RELOAD=true + +# Development URLs (for local development) +FRONTEND_URL=http://localhost:8001 +BACKEND_URL=http://localhost:8000 +CORS_ORIGINS=["http://localhost:8001"] + +# Database Configuration +DATABASE_URL=sqlite+aiosqlite:///data/soundboard.db +DATABASE_ECHO=false + +# Logging Configuration +LOG_LEVEL=debug +LOG_FILE=logs/app.log +LOG_MAX_SIZE=10485760 +LOG_BACKUP_COUNT=5 + +# JWT Configuration (Use a secure key even in development) +JWT_SECRET_KEY=development-secret-key-change-for-production +JWT_ACCESS_TOKEN_EXPIRE_MINUTES=15 +JWT_REFRESH_TOKEN_EXPIRE_DAYS=7 + +# Cookie Configuration (Development settings) +COOKIE_SECURE=false +COOKIE_SAMESITE=lax +COOKIE_DOMAIN=localhost + +# OAuth2 Configuration (Get these from OAuth providers) +# Google: https://console.developers.google.com/ +# Redirect URI: http://localhost:8000/api/v1/auth/google/callback +GOOGLE_CLIENT_ID=your-google-client-id +GOOGLE_CLIENT_SECRET=your-google-client-secret + +# GitHub: https://github.com/settings/developers +# Redirect URI: http://localhost:8000/api/v1/auth/github/callback +GITHUB_CLIENT_ID=your-github-client-id +GITHUB_CLIENT_SECRET=your-github-client-secret + +# Audio Normalization Configuration +NORMALIZED_AUDIO_FORMAT=mp3 +NORMALIZED_AUDIO_BITRATE=256k +NORMALIZED_AUDIO_PASSES=2 + +# Audio Extraction Configuration +EXTRACTION_AUDIO_FORMAT=mp3 +EXTRACTION_AUDIO_BITRATE=256k +EXTRACTION_TEMP_DIR=sounds/temp +EXTRACTION_THUMBNAILS_DIR=sounds/originals/extracted/thumbnails +EXTRACTION_MAX_CONCURRENT=2 \ No newline at end of file diff --git a/.env.production.template b/.env.production.template new file mode 100644 index 0000000..5004cc7 --- /dev/null +++ b/.env.production.template @@ -0,0 +1,50 @@ +# Production Environment Configuration +# Copy this file to .env and configure for your production environment + +# Application Configuration +HOST=0.0.0.0 +PORT=8000 +RELOAD=false + +# Production URLs (configure for your domain) +FRONTEND_URL=https://yourdomain.com +BACKEND_URL=https://yourdomain.com +CORS_ORIGINS=["https://yourdomain.com"] + +# Database Configuration (consider using PostgreSQL in production) +DATABASE_URL=sqlite+aiosqlite:///data/soundboard.db +DATABASE_ECHO=false + +# Logging Configuration +LOG_LEVEL=info +LOG_FILE=logs/app.log +LOG_MAX_SIZE=10485760 +LOG_BACKUP_COUNT=5 + +# JWT Configuration (IMPORTANT: Generate secure keys for production) +JWT_SECRET_KEY=your-super-secure-secret-key-change-this-in-production +JWT_ACCESS_TOKEN_EXPIRE_MINUTES=15 +JWT_REFRESH_TOKEN_EXPIRE_DAYS=7 + +# Cookie Configuration (Production settings) +COOKIE_SECURE=true +COOKIE_SAMESITE=lax +COOKIE_DOMAIN= # Leave empty for same-origin cookies in production with reverse proxy + +# OAuth2 Configuration (Configure with your OAuth providers) +GOOGLE_CLIENT_ID=your-google-client-id +GOOGLE_CLIENT_SECRET=your-google-client-secret +GITHUB_CLIENT_ID=your-github-client-id +GITHUB_CLIENT_SECRET=your-github-client-secret + +# Audio Normalization Configuration +NORMALIZED_AUDIO_FORMAT=mp3 +NORMALIZED_AUDIO_BITRATE=256k +NORMALIZED_AUDIO_PASSES=2 + +# Audio Extraction Configuration +EXTRACTION_AUDIO_FORMAT=mp3 +EXTRACTION_AUDIO_BITRATE=256k +EXTRACTION_TEMP_DIR=sounds/temp +EXTRACTION_THUMBNAILS_DIR=sounds/originals/extracted/thumbnails +EXTRACTION_MAX_CONCURRENT=2 \ No newline at end of file diff --git a/app/api/v1/auth.py b/app/api/v1/auth.py index ff4a078..24d2918 100644 --- a/app/api/v1/auth.py +++ b/app/api/v1/auth.py @@ -207,14 +207,14 @@ async def logout( httponly=True, secure=settings.COOKIE_SECURE, samesite=settings.COOKIE_SAMESITE, - domain="localhost", # Match the domain used when setting cookies + domain=settings.COOKIE_DOMAIN, # Match the domain used when setting cookies ) response.delete_cookie( key="refresh_token", httponly=True, secure=settings.COOKIE_SECURE, samesite=settings.COOKIE_SAMESITE, - domain="localhost", # Match the domain used when setting cookies + domain=settings.COOKIE_DOMAIN, # Match the domain used when setting cookies ) return {"message": "Successfully logged out"} @@ -303,7 +303,7 @@ async def oauth_callback( "created_at": time.time(), } - redirect_url = f"http://localhost:8001/auth/callback?code={temp_code}" + redirect_url = f"{settings.FRONTEND_URL}/auth/callback?code={temp_code}" logger.info("Redirecting to: %s", redirect_url) return RedirectResponse( diff --git a/app/api/v1/main.py b/app/api/v1/main.py index 785bd29..fd28bd5 100644 --- a/app/api/v1/main.py +++ b/app/api/v1/main.py @@ -1,6 +1,7 @@ """Main router for v1 endpoints.""" from fastapi import APIRouter +from fastapi.responses import HTMLResponse from app.core.logging import get_logger from app.schemas.common import HealthResponse @@ -15,3 +16,69 @@ def health() -> HealthResponse: """Health check endpoint.""" logger.info("Health check endpoint accessed") return HealthResponse(status="healthy") + + +@router.get("/scalar-docs", response_class=HTMLResponse) +def scalar_docs() -> HTMLResponse: + """Serve the API documentation using Scalar.""" + return """ + + + + API Documentation - Scalar + + + + + + + + """ + + +@router.get("/rapidoc-docs", response_class=HTMLResponse) +async def rapidoc() -> HTMLResponse: + """Serve the API documentation using Rapidoc.""" + return """ + + + + API Documentation - Rapidoc + + + + + + + + + """ + + +@router.get("/elements-docs", response_class=HTMLResponse) +async def elements_docs() -> HTMLResponse: + """Serve the API documentation using Stoplight Elements.""" + return """ + + + + + + API Documentation - elements + + + + + + + + """ diff --git a/app/core/config.py b/app/core/config.py index 9d050eb..fc11bf8 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -18,6 +18,13 @@ class Settings(BaseSettings): PORT: int = 8000 RELOAD: bool = True + # Production URLs (for reverse proxy deployment) + FRONTEND_URL: str = "http://localhost:8001" # Frontend URL in production + BACKEND_URL: str = "http://localhost:8000" # Backend base URL + + # CORS Configuration + CORS_ORIGINS: list[str] = ["http://localhost:8001"] # Allowed origins for CORS + # Database Configuration DATABASE_URL: str = "sqlite+aiosqlite:///data/soundboard.db" DATABASE_ECHO: bool = False @@ -38,6 +45,7 @@ class Settings(BaseSettings): # Cookie Configuration COOKIE_SECURE: bool = True COOKIE_SAMESITE: Literal["strict", "lax", "none"] = "lax" + COOKIE_DOMAIN: str | None = "localhost" # Cookie domain (None for production) # OAuth2 Configuration GOOGLE_CLIENT_ID: str = "" diff --git a/app/main.py b/app/main.py index 0d44dfd..66d28e0 100644 --- a/app/main.py +++ b/app/main.py @@ -6,6 +6,7 @@ from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from app.api import api_router +from app.core.config import settings from app.core.database import get_session_factory, init_db from app.core.logging import get_logger, setup_logging from app.middleware.logging import LoggingMiddleware @@ -47,12 +48,21 @@ async def lifespan(_app: FastAPI) -> AsyncGenerator[None, None]: def create_app() -> FastAPI: """Create and configure the FastAPI application.""" - app = FastAPI(lifespan=lifespan) + app = FastAPI( + title="Soundboard API", + description="API for the Soundboard application with authentication, sound management, and real-time features", + version="1.0.0", + lifespan=lifespan, + # Configure docs URLs for reverse proxy setup + docs_url="/api/docs", # Swagger UI at /api/docs + redoc_url="/api/redoc", # ReDoc at /api/redoc + openapi_url="/api/openapi.json" # OpenAPI schema at /api/openapi.json + ) # Add CORS middleware app.add_middleware( CORSMiddleware, - allow_origins=["http://localhost:8001"], + allow_origins=settings.CORS_ORIGINS, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], diff --git a/app/services/oauth.py b/app/services/oauth.py index 174933c..9286d41 100644 --- a/app/services/oauth.py +++ b/app/services/oauth.py @@ -70,7 +70,7 @@ class OAuthProvider(ABC): """Generate authorization URL with state parameter.""" # Construct provider-specific redirect URI redirect_uri = ( - f"http://localhost:8000/api/v1/auth/{self.provider_name}/callback" + f"{settings.BACKEND_URL}/api/v1/auth/{self.provider_name}/callback" ) params = { @@ -86,7 +86,7 @@ class OAuthProvider(ABC): """Exchange authorization code for access token.""" # Construct provider-specific redirect URI (must match authorization request) redirect_uri = ( - f"http://localhost:8000/api/v1/auth/{self.provider_name}/callback" + f"{settings.BACKEND_URL}/api/v1/auth/{self.provider_name}/callback" ) data = { @@ -150,7 +150,7 @@ class GoogleOAuthProvider(OAuthProvider): """Exchange authorization code for access token.""" # Construct provider-specific redirect URI (must match authorization request) redirect_uri = ( - f"http://localhost:8000/api/v1/auth/{self.provider_name}/callback" + f"{settings.BACKEND_URL}/api/v1/auth/{self.provider_name}/callback" ) data = { diff --git a/app/services/socket.py b/app/services/socket.py index 3e18125..78930c3 100644 --- a/app/services/socket.py +++ b/app/services/socket.py @@ -4,6 +4,7 @@ import logging import socketio +from app.core.config import settings from app.utils.auth import JWTUtils from app.utils.cookies import extract_access_token_from_cookies @@ -16,7 +17,7 @@ class SocketManager: def __init__(self) -> None: """Initialize the SocketManager with a Socket.IO server.""" self.sio = socketio.AsyncServer( - cors_allowed_origins=["http://localhost:8001"], + cors_allowed_origins=settings.CORS_ORIGINS, logger=True, engineio_logger=True, async_mode="asgi", diff --git a/app/utils/cookies.py b/app/utils/cookies.py index 77cbb84..da4ba36 100644 --- a/app/utils/cookies.py +++ b/app/utils/cookies.py @@ -40,7 +40,7 @@ def set_access_token_cookie( httponly=True, secure=settings.COOKIE_SECURE, samesite=settings.COOKIE_SAMESITE, - domain="localhost", # Allow cookie across localhost ports + domain=settings.COOKIE_DOMAIN, path=path, ) @@ -58,7 +58,7 @@ def set_refresh_token_cookie( httponly=True, secure=settings.COOKIE_SECURE, samesite=settings.COOKIE_SAMESITE, - domain="localhost", # Allow cookie across localhost ports + domain=settings.COOKIE_DOMAIN, path=path, )