# Copyright (C) 2025 Raccoon Survey org
# This file is part of Raccoon Survey.
# Raccoon Survey is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License v3 as published by
# the Free Software Foundation.
# See the LICENSE file distributed with this program for details.
from __future__ import annotations
import os
from pathlib import Path
try:
from dotenv import load_dotenv
except ImportError:
load_dotenv = None
if load_dotenv is not None:
load_dotenv(
dotenv_path=Path(__file__).resolve().parents[2] / ".env", override=False
)
from typing import Any, ClassVar
[docs]
class BaseConfig:
"""Base configuration for the Raccoon Survey backend."""
# General
ENV: ClassVar[str] = os.getenv("FLASK_ENV", "development")
DEBUG: ClassVar[bool] = os.getenv("FLASK_DEBUG", "1") == "1"
TESTING: ClassVar[bool] = False
JSON_SORT_KEYS: ClassVar[bool] = False
# JWT
JWT_TOKEN_LOCATION: ClassVar[tuple[str, ...]] = ("headers",)
JWT_HEADER_NAME: ClassVar[str] = "Authorization"
JWT_HEADER_TYPE: ClassVar[str] = "Bearer"
JWT_SECRET_KEY: ClassVar[str] = os.getenv("JWT_SECRET_KEY", None)
JWT_ACCESS_TOKEN_EXPIRES: ClassVar[int] = int(
os.getenv("JWT_ACCESS_TOKEN_EXPIRES", "900")
)
JWT_REFRESH_TOKEN_EXPIRES: ClassVar[int] = int(
os.getenv("JWT_REFRESH_TOKEN_EXPIRES", "2592000")
)
# CORS
CORS_ORIGINS: ClassVar[tuple[str, ...]] = tuple[str, ...](
os.getenv("CORS_ORIGINS", "*").split(",")
)
# Database
DATABASE_URL: ClassVar[str | None] = os.getenv("DATABASE_URL", None)
DATABASE_ECHO: ClassVar[bool] = os.getenv("DATABASE_ECHO", "0") == "1"
# SQLAlchemy
SQLALCHEMY_DATABASE_URI: ClassVar[str | None] = DATABASE_URL
SQLALCHEMY_TRACK_MODIFICATIONS: ClassVar[bool] = False
SQLALCHEMY_ENGINE_OPTIONS: ClassVar[dict[str, Any]] = {
"pool_pre_ping": True,
"pool_recycle": 1800,
"echo": DATABASE_ECHO,
}
# Cleanup scheduler
CLEANUP_RUN_ON_START: ClassVar[bool] = os.getenv("CLEANUP_RUN_ON_START", "1") == "1"
CLEANUP_CRON_HOUR: ClassVar[int] = int(os.getenv("CLEANUP_CRON_HOUR", "3"))
CLEANUP_CRON_MINUTE: ClassVar[int] = int(os.getenv("CLEANUP_CRON_MINUTE", "0"))
# Default Admin User
DEFAULT_USER_ADMIN_EMAIL: ClassVar[str] = os.getenv(
"DEFAULT_USER_ADMIN_EMAIL", None
)
DEFAULT_USER_ADMIN_PASSWORD: ClassVar[str] = os.getenv(
"DEFAULT_USER_ADMIN_PASSWORD", None
)
DEFAULT_USER_ADMIN_NAME: ClassVar[str] = os.getenv("DEFAULT_USER_ADMIN_NAME", None)
# Environment validations: applied only outside of testing
if ENV.lower() not in ("testing", "test"):
if not JWT_SECRET_KEY:
raise ValueError("JWT_SECRET_KEY must be set")
if not DATABASE_URL:
raise ValueError("DATABASE_URL must be set")
if not DEFAULT_USER_ADMIN_EMAIL:
raise ValueError("DEFAULT_USER_ADMIN_EMAIL must be set")
if not DEFAULT_USER_ADMIN_PASSWORD:
raise ValueError("DEFAULT_USER_ADMIN_PASSWORD must be set")
if not DEFAULT_USER_ADMIN_NAME:
raise ValueError("DEFAULT_USER_ADMIN_NAME must be set")
[docs]
class DevConfig(BaseConfig):
ENV = "development"
DEBUG = True
[docs]
class ProdConfig(BaseConfig):
ENV = "production"
DEBUG = False
[docs]
class TestConfig(BaseConfig):
ENV = "testing"
DEBUG = False
TESTING = True
[docs]
def get_config_class(env: str | None = None) -> type[BaseConfig]:
"""Return the configuration class based on FLASK_ENV or provided env.
Args:
env (str | None, optional): Environment name. Defaults to None.
Returns:
type[BaseConfig]: Configuration class for the specified environment.
"""
env = env or os.getenv("FLASK_ENV", "development")
mapping = {
"development": DevConfig,
"production": ProdConfig,
"testing": TestConfig,
"test": TestConfig,
}
return mapping.get(env.lower(), DevConfig)
__all__ = [
"BaseConfig",
"DevConfig",
"ProdConfig",
"TestConfig",
"get_config_class",
]