refactor: streamline configuration loading and enhance admin checks
All checks were successful
CI / lint-and-test (push) Successful in 20s

- Refactored the configuration loading in `config.py` to utilize a single source of truth through the `Settings` class, improving maintainability and clarity.
- Introduced the `is_admin_for_telegram_user` function in `repository.py` to centralize admin checks based on usernames and phone numbers.
- Updated command handlers to use the new admin check function, ensuring consistent access control across the application.
- Enhanced error handling in the `error_handler` to log exceptions when sending error replies to users, improving debugging capabilities.
- Improved the handling of user phone updates in `repository.py` to ensure proper normalization and validation of phone numbers.
This commit is contained in:
2026-02-20 16:42:41 +03:00
parent 9486f7004d
commit ae21883e1e
9 changed files with 79 additions and 50 deletions

View File

@@ -119,42 +119,23 @@ class Settings:
)
# Module-level vars: no validation on import; entry point must check BOT_TOKEN when needed.
BOT_TOKEN = os.getenv("BOT_TOKEN") or ""
DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///data/duty_teller.db")
MINI_APP_BASE_URL = os.getenv("MINI_APP_BASE_URL", "").rstrip("/")
HTTP_PORT = int(os.getenv("HTTP_PORT", "8080"))
# Single source of truth: load once at import; entry point must check BOT_TOKEN when needed.
_settings = Settings.from_env()
_raw_allowed = os.getenv("ALLOWED_USERNAMES", "").strip()
ALLOWED_USERNAMES = {
s.strip().lstrip("@").lower() for s in _raw_allowed.split(",") if s.strip()
}
_raw_admin = os.getenv("ADMIN_USERNAMES", "").strip()
ADMIN_USERNAMES = {
s.strip().lstrip("@").lower() for s in _raw_admin.split(",") if s.strip()
}
ALLOWED_PHONES = _parse_phone_list(os.getenv("ALLOWED_PHONES", ""))
ADMIN_PHONES = _parse_phone_list(os.getenv("ADMIN_PHONES", ""))
MINI_APP_SKIP_AUTH = os.getenv("MINI_APP_SKIP_AUTH", "").strip() in ("1", "true", "yes")
INIT_DATA_MAX_AGE_SECONDS = int(os.getenv("INIT_DATA_MAX_AGE_SECONDS", "0"))
_raw_cors = os.getenv("CORS_ORIGINS", "").strip()
CORS_ORIGINS = (
[_o.strip() for _o in _raw_cors.split(",") if _o.strip()]
if _raw_cors and _raw_cors != "*"
else ["*"]
)
EXTERNAL_CALENDAR_ICS_URL = os.getenv("EXTERNAL_CALENDAR_ICS_URL", "").strip()
DUTY_DISPLAY_TZ = (
os.getenv("DUTY_DISPLAY_TZ", "Europe/Moscow").strip() or "Europe/Moscow"
)
DEFAULT_LANGUAGE = _normalize_default_language(
os.getenv("DEFAULT_LANGUAGE", "en").strip()
)
BOT_TOKEN = _settings.bot_token
DATABASE_URL = _settings.database_url
MINI_APP_BASE_URL = _settings.mini_app_base_url
HTTP_PORT = _settings.http_port
ALLOWED_USERNAMES = _settings.allowed_usernames
ADMIN_USERNAMES = _settings.admin_usernames
ALLOWED_PHONES = _settings.allowed_phones
ADMIN_PHONES = _settings.admin_phones
MINI_APP_SKIP_AUTH = _settings.mini_app_skip_auth
INIT_DATA_MAX_AGE_SECONDS = _settings.init_data_max_age_seconds
CORS_ORIGINS = _settings.cors_origins
EXTERNAL_CALENDAR_ICS_URL = _settings.external_calendar_ics_url
DUTY_DISPLAY_TZ = _settings.duty_display_tz
DEFAULT_LANGUAGE = _settings.default_language
def is_admin(username: str) -> bool: