''' Copyright 2025-2026 AIIrondev Licensed under the Inventarsystem EULA (Endbenutzer-Lizenzvertrag). See Legal/LICENSE for the full license text. Unauthorized commercial use, SaaS hosting, or removal of branding is prohibited. For commercial licensing inquiries: https://github.com/AIIrondev ''' """ Centralized settings module to load configuration from config.json and provide defaults for the web application and helper modules. """ import os import json from pymongo import MongoClient as _PyMongoClient # Base directory of this Web package BASE_DIR = os.path.dirname(os.path.abspath(__file__)) # Default values DEFAULTS = { 'version': '2.6.5', 'debug': False, 'secret_key': 'Hsse783942h2342f342342i34hwebf8', 'host': '0.0.0.0', 'port': 443, 'mongodb': { 'host': 'localhost', 'port': 27017, 'db': 'Inventarsystem', }, 'scheduler': { 'interval_minutes': 1, 'backup_interval_hours': 24, 'enabled': True, }, 'ssl': { 'enabled': False, 'cert': 'ssl_certs/cert.pem', 'key': 'ssl_certs/key.pem', }, 'images': { 'thumbnail_size': [150, 150], 'preview_size': [400, 400], }, 'upload': { 'folder': os.path.join(BASE_DIR, 'uploads'), 'thumbnail_folder': os.path.join(BASE_DIR, 'thumbnails'), 'preview_folder': os.path.join(BASE_DIR, 'previews'), 'qrcode_folder': os.path.join(BASE_DIR, 'QRCodes'), 'max_size_mb': 10, 'image_max_size_mb': 15, 'video_max_size_mb': 100, 'allowed_extensions': ['png', 'jpg', 'jpeg', 'gif'] }, 'paths': { 'backups': os.path.join(os.path.dirname(os.path.dirname(BASE_DIR)), 'backups'), 'logs': os.path.join(os.path.dirname(os.path.dirname(BASE_DIR)), 'logs'), }, 'schoolPeriods': { "1": {"start": "08:00", "end": "08:45", "label": "1. Stunde (08:00 - 08:45)"}, "2": {"start": "08:45", "end": "09:30", "label": "2. Stunde (08:45 - 09:30)"}, "3": {"start": "09:45", "end": "10:30", "label": "3. Stunde (09:45 - 10:30)"}, "4": {"start": "10:30", "end": "11:15", "label": "4. Stunde (10:30 - 11:15)"}, "5": {"start": "11:30", "end": "12:15", "label": "5. Stunde (11:30 - 12:15)"}, "6": {"start": "12:15", "end": "13:00", "label": "6. Stunde (12:15 - 13:00)"}, "7": {"start": "13:30", "end": "14:15", "label": "7. Stunde (13:30 - 14:15)"}, "8": {"start": "14:15", "end": "15:00", "label": "8. Stunde (14:15 - 15:00)"}, "9": {"start": "15:15", "end": "16:00", "label": "9. Stunde (15:15 - 16:00)"}, "10": {"start": "16:00", "end": "16:45", "label": "10. Stunde (16:00 - 16:45)"} }, 'modules': { 'library': { 'enabled': False }, 'student_cards': { 'enabled': False, 'default_borrow_days': 14, 'max_borrow_days': 365 } } } # Load configuration file CONFIG_PATH = os.path.join(BASE_DIR, '..', 'config.json') try: with open(CONFIG_PATH, 'r') as f: _conf = json.load(f) except Exception: _conf = {} # Helper to get nested values with defaults def _get(conf, path, default): cur = conf for p in path: if isinstance(cur, dict) and p in cur: cur = cur[p] else: return default return cur # Expose settings APP_VERSION = _get(_conf, ['ver'], DEFAULTS['version']) DEBUG = _get(_conf, ['dbg'], DEFAULTS['debug']) SECRET_KEY = str(_get(_conf, ['key'], DEFAULTS['secret_key'])) HOST = _get(_conf, ['host'], DEFAULTS['host']) PORT = _get(_conf, ['port'], DEFAULTS['port']) # Database MONGODB_HOST = _get(_conf, ['mongodb', 'host'], DEFAULTS['mongodb']['host']) MONGODB_PORT = _get(_conf, ['mongodb', 'port'], DEFAULTS['mongodb']['port']) MONGODB_DB = _get(_conf, ['mongodb', 'db'], DEFAULTS['mongodb']['db']) # Optional environment overrides for containerized/runtime deployments. MONGODB_HOST = os.getenv('INVENTAR_MONGODB_HOST', MONGODB_HOST) MONGODB_PORT = int(os.getenv('INVENTAR_MONGODB_PORT', str(MONGODB_PORT))) MONGODB_DB = os.getenv('INVENTAR_MONGODB_DB', MONGODB_DB) # Scheduler SCHEDULER_INTERVAL_MIN = _get(_conf, ['scheduler', 'interval_minutes'], DEFAULTS['scheduler']['interval_minutes']) BACKUP_INTERVAL_HOURS = _get(_conf, ['scheduler', 'backup_interval_hours'], DEFAULTS['scheduler']['backup_interval_hours']) SCHEDULER_ENABLED = _get(_conf, ['scheduler', 'enabled'], DEFAULTS['scheduler']['enabled']) # SSL SSL_ENABLED = _get(_conf, ['ssl', 'enabled'], DEFAULTS['ssl']['enabled']) SSL_CERT = _get(_conf, ['ssl', 'cert'], DEFAULTS['ssl']['cert']) SSL_KEY = _get(_conf, ['ssl', 'key'], DEFAULTS['ssl']['key']) # School periods SCHOOL_PERIODS = _get(_conf, ['schoolPeriods'], DEFAULTS['schoolPeriods']) # Optional feature modules LIBRARY_MODULE_ENABLED = bool(_get(_conf, ['modules', 'library', 'enabled'], DEFAULTS['modules']['library']['enabled'])) STUDENT_CARDS_MODULE_ENABLED = bool(_get(_conf, ['modules', 'student_cards', 'enabled'], DEFAULTS['modules']['student_cards']['enabled'])) STUDENT_DEFAULT_BORROW_DAYS = int(_get(_conf, ['modules', 'student_cards', 'default_borrow_days'], DEFAULTS['modules']['student_cards']['default_borrow_days'])) STUDENT_MAX_BORROW_DAYS = int(_get(_conf, ['modules', 'student_cards', 'max_borrow_days'], DEFAULTS['modules']['student_cards']['max_borrow_days'])) # Upload/paths ALLOWED_EXTENSIONS = set(_get(_conf, ['allowed_extensions'], DEFAULTS['upload']['allowed_extensions'])) UPLOAD_FOLDER = _get(_conf, ['upload', 'folder'], DEFAULTS['upload']['folder']) THUMBNAIL_FOLDER = _get(_conf, ['upload', 'thumbnail_folder'], DEFAULTS['upload']['thumbnail_folder']) PREVIEW_FOLDER = _get(_conf, ['upload', 'preview_folder'], DEFAULTS['upload']['preview_folder']) QR_CODE_FOLDER = _get(_conf, ['upload', 'qrcode_folder'], DEFAULTS['upload']['qrcode_folder']) # Normalize to absolute paths to avoid cwd issues if not os.path.isabs(UPLOAD_FOLDER): UPLOAD_FOLDER = os.path.join(BASE_DIR, os.path.relpath(UPLOAD_FOLDER, BASE_DIR)) if not os.path.isabs(THUMBNAIL_FOLDER): THUMBNAIL_FOLDER = os.path.join(BASE_DIR, os.path.relpath(THUMBNAIL_FOLDER, BASE_DIR)) if not os.path.isabs(PREVIEW_FOLDER): PREVIEW_FOLDER = os.path.join(BASE_DIR, os.path.relpath(PREVIEW_FOLDER, BASE_DIR)) if not os.path.isabs(QR_CODE_FOLDER): QR_CODE_FOLDER = os.path.join(BASE_DIR, os.path.relpath(QR_CODE_FOLDER, BASE_DIR)) MAX_UPLOAD_MB = _get(_conf, ['upload', 'max_size_mb'], DEFAULTS['upload']['max_size_mb']) IMAGE_MAX_UPLOAD_MB = _get(_conf, ['upload', 'image_max_size_mb'], DEFAULTS['upload']['image_max_size_mb']) VIDEO_MAX_UPLOAD_MB = _get(_conf, ['upload', 'video_max_size_mb'], DEFAULTS['upload']['video_max_size_mb']) THUMBNAIL_SIZE_LIST = _get(_conf, ['images', 'thumbnail_size'], DEFAULTS['images']['thumbnail_size']) PREVIEW_SIZE_LIST = _get(_conf, ['images', 'preview_size'], DEFAULTS['images']['preview_size']) THUMBNAIL_SIZE = (int(THUMBNAIL_SIZE_LIST[0]), int(THUMBNAIL_SIZE_LIST[1])) if isinstance(THUMBNAIL_SIZE_LIST, (list, tuple)) else (150, 150) PREVIEW_SIZE = (int(PREVIEW_SIZE_LIST[0]), int(PREVIEW_SIZE_LIST[1])) if isinstance(PREVIEW_SIZE_LIST, (list, tuple)) else (400, 400) BACKUP_FOLDER = _get(_conf, ['paths', 'backups'], DEFAULTS['paths']['backups']) LOGS_FOLDER = _get(_conf, ['paths', 'logs'], DEFAULTS['paths']['logs']) # Optional environment overrides for writable storage mounts. BACKUP_FOLDER = os.getenv('INVENTAR_BACKUP_FOLDER', BACKUP_FOLDER) LOGS_FOLDER = os.getenv('INVENTAR_LOGS_FOLDER', LOGS_FOLDER) # Normalize backup and logs paths to absolute paths (similar to upload folders) to avoid # permission issues caused by relative paths resolving to unintended working dirs. PROJECT_ROOT = os.path.dirname(os.path.dirname(BASE_DIR)) if not os.path.isabs(BACKUP_FOLDER): BACKUP_FOLDER = os.path.join(PROJECT_ROOT, BACKUP_FOLDER) if not os.path.isabs(LOGS_FOLDER): LOGS_FOLDER = os.path.join(PROJECT_ROOT, LOGS_FOLDER) def MongoClient(*args, **kwargs): """Return a lightweight MongoDB client configured from this settings module.""" client_kwargs = { 'maxPoolSize': 10, 'minPoolSize': 0, 'connectTimeoutMS': 5000, 'serverSelectionTimeoutMS': 5000, 'retryWrites': True, } client_kwargs.update(kwargs) # Preserve caller-provided positional host/port arguments. # If none are passed, use configured defaults. if args: return _PyMongoClient(*args, **client_kwargs) return _PyMongoClient(MONGODB_HOST, MONGODB_PORT, **client_kwargs)