feat: Add environment configuration files and update settings for production and development
Some checks failed
Backend CI / lint (push) Failing after 5m0s
Backend CI / test (push) Successful in 3m39s

This commit is contained in:
JSC
2025-08-09 14:43:20 +02:00
parent 69544b6bb8
commit 734521c5c3
9 changed files with 202 additions and 11 deletions

55
.env.development.template Normal file
View File

@@ -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

50
.env.production.template Normal file
View File

@@ -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

View File

@@ -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(

View File

@@ -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 """
<!doctype html>
<html>
<head>
<title>API Documentation - Scalar</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<script
id="api-reference"
data-url="http://localhost:8000/api/openapi.json"
src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script>
</body>
</html>
"""
@router.get("/rapidoc-docs", response_class=HTMLResponse)
async def rapidoc() -> HTMLResponse:
"""Serve the API documentation using Rapidoc."""
return """
<!doctype html>
<html>
<head>
<title>API Documentation - Rapidoc</title>
<meta charset="utf-8">
<script type="module" src="https://unpkg.com/rapidoc/dist/rapidoc-min.js"></script>
</head>
<body>
<rapi-doc
spec-url="http://localhost:8000/api/openapi.json"
theme="dark"
render-style="read">
</rapi-doc>
</body>
</html>
"""
@router.get("/elements-docs", response_class=HTMLResponse)
async def elements_docs() -> HTMLResponse:
"""Serve the API documentation using Stoplight Elements."""
return """
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>API Documentation - elements</title>
<script src="https://unpkg.com/@stoplight/elements/web-components.min.js"></script>
<link rel="stylesheet" href="https://unpkg.com/@stoplight/elements/styles.min.css">
</head>
<body>
<elements-api
apiDescriptionUrl="http://localhost:8000/api/openapi.json"
router="hash"
/>
</body>
</html>
"""

View File

@@ -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 = ""

View File

@@ -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=["*"],

View File

@@ -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 = {

View File

@@ -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",

View File

@@ -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,
)