import os from dotenv import load_dotenv # Use an absolute path so .env is found regardless of the process working # directory — under systemd the CWD may not be the project root. _here = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) load_dotenv(os.path.join(_here, '.env')) class Config: SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev-secret-key-change-in-production' SQLALCHEMY_DATABASE_URI = ( f"mysql+pymysql://{os.environ.get('DB_USER', 'root')}:" f"{os.environ.get('DB_PASSWORD', '')}@" f"{os.environ.get('DB_HOST', 'localhost')}:" f"{os.environ.get('DB_PORT', '3306')}/" f"{os.environ.get('DB_NAME', 'it_tickets')}" ) SQLALCHEMY_TRACK_MODIFICATIONS = False SQLALCHEMY_ENGINE_OPTIONS = { 'pool_recycle': 300, 'pool_pre_ping': True, } # Mail MAIL_SERVER = os.environ.get('MAIL_SERVER', 'localhost') MAIL_PORT = int(os.environ.get('MAIL_PORT', 587)) MAIL_USE_TLS = os.environ.get('MAIL_USE_TLS', 'True').lower() == 'true' MAIL_USE_SSL = os.environ.get('MAIL_USE_SSL', 'False').lower() == 'true' MAIL_USERNAME = os.environ.get('MAIL_USERNAME') MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD') MAIL_DEFAULT_SENDER = os.environ.get('MAIL_DEFAULT_SENDER', 'IT Helpdesk ') # IT Department IT_DEPT_EMAIL = os.environ.get('IT_DEPT_EMAIL', 'it@yourdomain.com') APP_BASE_URL = os.environ.get('APP_BASE_URL', 'http://localhost:5000') # Uploads # UPLOAD_FOLDER must be an absolute path so that save (os.path.join) and # serve (send_from_directory) always resolve to the same directory regardless # of the process CWD. The default anchors to the project root via __file__. _project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) _raw_upload = os.environ.get('UPLOAD_FOLDER', '') UPLOAD_FOLDER = ( _raw_upload if (_raw_upload and os.path.isabs(_raw_upload)) else os.path.join(_project_root, 'app', 'static', 'uploads') ) MAX_CONTENT_LENGTH = int(os.environ.get('MAX_CONTENT_LENGTH', 16 * 1024 * 1024)) ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif', 'pdf', 'doc', 'docx', 'txt', 'zip', 'log'} # AI Chatbot — Groq (free tier: 14,400 req/day, no credit card, no region restrictions) # Get a key at: https://console.groq.com → API Keys GROQ_API_KEY = os.environ.get('GROQ_API_KEY', '') GROQ_MODEL = os.environ.get('GROQ_MODEL', 'llama-3.3-70b-versatile') # Rate limiting storage. # The `limits` library (used by Flask-Limiter) does not support MySQL as a # storage backend — its only supported schemes are memory://, redis://, and # memcached://. Since gunicorn.conf.py enforces a single worker process # (workers = 1, worker_class = eventlet), in-process memory is both correct # and sufficient — there is no other worker to share counters with, and a # counter reset on restart is an acceptable trade-off versus adding Redis. # To switch to Redis when it becomes available: set RATELIMIT_STORAGE_URI=redis://... RATELIMIT_STORAGE_URI = os.environ.get('RATELIMIT_STORAGE_URI', 'memory://') # SLA — hours until a ticket is considered overdue, by priority. # Override via environment variables if needed (e.g. SLA_CRITICAL_HOURS=2). # These are also seeded into SystemSetting so admins can tune them in-app. SLA_CRITICAL_HOURS = int(os.environ.get('SLA_CRITICAL_HOURS', 4)) SLA_HIGH_HOURS = int(os.environ.get('SLA_HIGH_HOURS', 8)) SLA_MEDIUM_HOURS = int(os.environ.get('SLA_MEDIUM_HOURS', 48)) SLA_LOW_HOURS = int(os.environ.get('SLA_LOW_HOURS', 120)) # Admin ADMIN_EMAIL = os.environ.get('ADMIN_EMAIL', 'admin@yourdomain.com') ADMIN_PASSWORD = os.environ.get('ADMIN_PASSWORD', 'Admin@123!') class DevelopmentConfig(Config): DEBUG = True SQLALCHEMY_ECHO = False class ProductionConfig(Config): DEBUG = False # Session cookie hardening — enforce HTTPS-only, no JS access, strict same-site. SESSION_COOKIE_SECURE = True # only sent over HTTPS SESSION_COOKIE_HTTPONLY = True # not accessible via document.cookie SESSION_COOKIE_SAMESITE = 'Lax' # blocks cross-site POST forgery; 'Strict' would break OAuth flows config = { 'development': DevelopmentConfig, 'production': ProductionConfig, 'default': ProductionConfig }