- Removed 11 duplicate router files (auth.py, users.py, decks.py, card_router.py, interactions.py, user_data.py, rooms.py, games/router.py, refresh.py, card_import.py, ws.py) - Removed duplicate core files (logging_config.py, settings.py) - Removed unused services (card_mirror_service.py, mtgjson_loader.py, mtgjson_uploader.py) - Removed unused schemas (proto_messages.py, protocol_constants.py) - Created run.py startup script for simple execution - Created app/core/config.py for unified configuration management - Created app/core/logging.py for logging configuration - Consolidated routers into 6 focused files (auth_router.py, users_router.py, decks_router.py, cards_router.py, user_data_router.py, games_router.py) - Updated all imports to use consolidated model paths - Added backward compatibility aliases in database.py - Created SIMPLIFICATION_SUMMARY.md documentation Architecture improvements: - Single unified database engine with auto-detection (SQLite/PostgreSQL) - Consolidated models into 2 files (models.py, mtg_models.py) - Clean router structure with one file per domain - Simplified configuration with sensible defaults - SQLite by default for development, PostgreSQL for production Testing: - All imports verified working - App loads successfully - Ready for development and testing
217 lines
7.0 KiB
Python
217 lines
7.0 KiB
Python
"""
|
|
Application configuration management.
|
|
|
|
Simple configuration with sensible defaults. SQLite by default for development,
|
|
PostgreSQL supported via DATABASE_URL environment variable.
|
|
|
|
## Usage
|
|
|
|
from app.core.config import get_config
|
|
|
|
config = get_config()
|
|
print(config.DATABASE_URL)
|
|
|
|
## Environment Variables
|
|
|
|
All configuration values can be overridden via environment variables.
|
|
The .env file in the project root is also loaded automatically.
|
|
|
|
| Variable | Default | Description |
|
|
|---|---|---|
|
|
| APP_NAME | MTG Online | Application display name |
|
|
| APP_VERSION | 0.2.0 | Application version |
|
|
| DEBUG | false | Enable debug mode |
|
|
| SECRET_KEY | change-me-in-production | Application secret key |
|
|
| JWT_SECRET_KEY | (same as SECRET_KEY) | JWT signing key |
|
|
| DATABASE_URL | sqlite:///./mtgonline.db | Database connection string |
|
|
| JWT_ALGORITHM | HS256 | JWT signing algorithm |
|
|
| JWT_ACCESS_TOKEN_EXPIRE_MINUTES | 60 | Access token lifetime |
|
|
| JWT_REFRESH_TOKEN_EXPIRE_DAYS | 7 | Refresh token lifetime |
|
|
| CORS_ORIGINS | ["http://localhost:3000", "http://localhost:8000"] | Allowed CORS origins |
|
|
| BCRYPT_ROUNDS | 12 | Bcrypt hashing rounds |
|
|
| LOG_LEVEL | INFO | Logging level |
|
|
| LOG_FORMAT | text | Log output format (text or json) |
|
|
| DATA_DIR | /app/data | Data directory for MTGJSON files |
|
|
| UPLOAD_DIR | /app/uploads | Upload directory |
|
|
"""
|
|
|
|
import os
|
|
from typing import Optional
|
|
|
|
|
|
def _load_env_file(path: str) -> None:
|
|
"""Load environment variables from a .env file if it exists.
|
|
|
|
Args:
|
|
path: Absolute or relative path to the .env file.
|
|
"""
|
|
if os.path.isfile(path):
|
|
with open(path, "r", encoding="utf-8") as f:
|
|
for line in f:
|
|
line = line.strip()
|
|
if not line or line.startswith("#"):
|
|
continue
|
|
if "=" not in line:
|
|
continue
|
|
key, _, value = line.partition("=")
|
|
key = key.strip()
|
|
value = value.strip().strip("\"'")
|
|
if key and value and key not in os.environ:
|
|
os.environ[key] = value
|
|
|
|
|
|
# Load .env from project root (one level up from backend/app/core/)
|
|
_project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../.."))
|
|
_env_path = os.path.join(_project_root, ".env")
|
|
_load_env_file(_env_path)
|
|
|
|
# Also try backend/.env
|
|
_backend_env = os.path.join(_project_root, "..", ".env")
|
|
_load_env_file(os.path.abspath(_backend_env))
|
|
|
|
|
|
class Config:
|
|
"""Application configuration with sensible defaults.
|
|
|
|
All values can be overridden via environment variables.
|
|
This is a simple class (not pydantic-settings) for minimal dependencies.
|
|
|
|
Attributes:
|
|
APP_NAME: Application display name.
|
|
APP_VERSION: Application version string.
|
|
DEBUG: Enable debug mode (enables SQL echo, etc.).
|
|
SECRET_KEY: Application secret key for signing.
|
|
JWT_SECRET_KEY: JWT signing key (falls back to SECRET_KEY).
|
|
DATABASE_URL: SQLAlchemy database connection string.
|
|
Defaults to SQLite for development.
|
|
JWT_ALGORITHM: JWT signing algorithm (default HS256).
|
|
JWT_ACCESS_TOKEN_EXPIRE_MINUTES: Access token lifetime in minutes.
|
|
JWT_REFRESH_TOKEN_EXPIRE_DAYS: Refresh token lifetime in days.
|
|
CORS_ORIGINS: List of allowed CORS origin URLs.
|
|
BCRYPT_ROUNDS: Number of bcrypt hashing rounds.
|
|
LOG_LEVEL: Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL).
|
|
LOG_FORMAT: Log output format (text or json).
|
|
DATA_DIR: Directory for MTGJSON data files.
|
|
UPLOAD_DIR: Directory for user uploads.
|
|
"""
|
|
|
|
# Application
|
|
APP_NAME: str = "MTG Online"
|
|
APP_VERSION: str = "0.2.0"
|
|
DEBUG: bool = False
|
|
SECRET_KEY: str = "change-me-in-production"
|
|
JWT_SECRET_KEY: str = "" # Falls back to SECRET_KEY if empty
|
|
|
|
# Database - single unified connection string
|
|
DATABASE_URL: str = "sqlite+aiosqlite:///./mtgonline.db"
|
|
|
|
# JWT Configuration
|
|
JWT_ALGORITHM: str = "HS256"
|
|
JWT_ACCESS_TOKEN_EXPIRE_MINUTES: int = 60
|
|
JWT_REFRESH_TOKEN_EXPIRE_DAYS: int = 7
|
|
|
|
# CORS
|
|
CORS_ORIGINS: list[str] = ["http://localhost:3000", "http://localhost:8000"]
|
|
|
|
# Security
|
|
BCRYPT_ROUNDS: int = 12
|
|
|
|
# Logging
|
|
LOG_LEVEL: str = "INFO"
|
|
LOG_FORMAT: str = "text"
|
|
|
|
# Directories
|
|
DATA_DIR: str = "/app/data"
|
|
UPLOAD_DIR: str = "/app/uploads"
|
|
|
|
def __init__(self) -> None:
|
|
"""Initialize configuration from environment variables."""
|
|
self._apply_env_overrides()
|
|
|
|
def _apply_env_overrides(self) -> None:
|
|
"""Override configuration values from environment variables."""
|
|
env_map: dict[str, type] = {
|
|
"APP_NAME": str,
|
|
"APP_VERSION": str,
|
|
"DEBUG": bool,
|
|
"SECRET_KEY": str,
|
|
"JWT_SECRET_KEY": str,
|
|
"DATABASE_URL": str,
|
|
"JWT_ALGORITHM": str,
|
|
"JWT_ACCESS_TOKEN_EXPIRE_MINUTES": int,
|
|
"JWT_REFRESH_TOKEN_EXPIRE_DAYS": int,
|
|
"BCRYPT_ROUNDS": int,
|
|
"LOG_LEVEL": str,
|
|
"LOG_FORMAT": str,
|
|
"DATA_DIR": str,
|
|
"UPLOAD_DIR": str,
|
|
}
|
|
|
|
for attr, type_cast in env_map.items():
|
|
value = os.environ.get(attr)
|
|
if value is not None:
|
|
if type_cast == bool:
|
|
value = value.lower() in ("true", "1", "yes", "on")
|
|
else:
|
|
value = type_cast(value)
|
|
setattr(self, attr, value)
|
|
|
|
# CORS_ORIGINS: parse comma-separated from env or keep default list
|
|
cors_env = os.environ.get("CORS_ORIGINS")
|
|
if cors_env:
|
|
self.CORS_ORIGINS = [
|
|
origin.strip() for origin in cors_env.split(",") if origin.strip()
|
|
]
|
|
|
|
@property
|
|
def jwt_secret(self) -> str:
|
|
"""Get the effective JWT secret key.
|
|
|
|
Returns JWT_SECRET_KEY if set, otherwise falls back to SECRET_KEY.
|
|
|
|
Returns:
|
|
The JWT secret key string.
|
|
"""
|
|
return self.JWT_SECRET_KEY or self.SECRET_KEY
|
|
|
|
@property
|
|
def is_sqlite(self) -> bool:
|
|
"""Check if the database is SQLite.
|
|
|
|
Returns:
|
|
True if using SQLite, False otherwise.
|
|
"""
|
|
return self.DATABASE_URL.startswith("sqlite")
|
|
|
|
@property
|
|
def is_postgresql(self) -> bool:
|
|
"""Check if the database is PostgreSQL.
|
|
|
|
Returns:
|
|
True if using PostgreSQL, False otherwise.
|
|
"""
|
|
return "postgresql" in self.DATABASE_URL
|
|
|
|
|
|
# Singleton instance
|
|
_config_instance: Optional[Config] = None
|
|
|
|
|
|
def get_config() -> Config:
|
|
"""Get the application configuration singleton.
|
|
|
|
Returns a cached Config instance. Subsequent calls return the same object.
|
|
|
|
Returns:
|
|
The application configuration instance.
|
|
|
|
Example:
|
|
>>> config = get_config()
|
|
>>> print(config.DATABASE_URL)
|
|
sqlite:///./mtgonline.db
|
|
"""
|
|
global _config_instance
|
|
if _config_instance is None:
|
|
_config_instance = Config()
|
|
return _config_instance
|