chore: add changelog and documentation updates
All checks were successful
CI / lint-and-test (push) Successful in 17s
All checks were successful
CI / lint-and-test (push) Successful in 17s
- Created a new `CHANGELOG.md` file to document all notable changes to the project, adhering to the Keep a Changelog format. - Updated `CONTRIBUTING.md` to include instructions for building and previewing documentation using MkDocs. - Added `mkdocs.yml` configuration for documentation generation, including navigation structure and theme settings. - Enhanced various documentation files, including API reference, architecture overview, configuration reference, and runbook, to provide comprehensive guidance for users and developers. - Included new sections in the README for changelog and documentation links, improving accessibility to project information.
This commit is contained in:
@@ -1,4 +1,8 @@
|
||||
"""Load configuration from environment. BOT_TOKEN is not validated on import; check in main/entry point."""
|
||||
"""Load configuration from environment (e.g. .env via python-dotenv).
|
||||
|
||||
BOT_TOKEN is not validated on import; call require_bot_token() in the entry point
|
||||
when running the bot.
|
||||
"""
|
||||
|
||||
import re
|
||||
import os
|
||||
@@ -17,7 +21,14 @@ _PHONE_DIGITS_RE = re.compile(r"\D")
|
||||
|
||||
|
||||
def normalize_phone(phone: str | None) -> str:
|
||||
"""Return phone as digits only (spaces, +, parentheses, dashes removed). Empty string if None/empty."""
|
||||
"""Return phone as digits only (spaces, +, parentheses, dashes removed).
|
||||
|
||||
Args:
|
||||
phone: Raw phone string or None.
|
||||
|
||||
Returns:
|
||||
Digits-only string, or empty string if None or empty.
|
||||
"""
|
||||
if not phone or not isinstance(phone, str):
|
||||
return ""
|
||||
return _PHONE_DIGITS_RE.sub("", phone.strip())
|
||||
@@ -43,7 +54,7 @@ def _parse_phone_list(raw: str) -> set[str]:
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Settings:
|
||||
"""Optional injectable settings built from env. Tests can override or build from env."""
|
||||
"""Injectable settings built from environment. Used in tests or when env is overridden."""
|
||||
|
||||
bot_token: str
|
||||
database_url: str
|
||||
@@ -62,7 +73,11 @@ class Settings:
|
||||
|
||||
@classmethod
|
||||
def from_env(cls) -> "Settings":
|
||||
"""Build Settings from current environment (same logic as module-level vars)."""
|
||||
"""Build Settings from current environment (same logic as module-level variables).
|
||||
|
||||
Returns:
|
||||
Settings instance with all fields populated from env.
|
||||
"""
|
||||
bot_token = os.getenv("BOT_TOKEN") or ""
|
||||
raw_allowed = os.getenv("ALLOWED_USERNAMES", "").strip()
|
||||
allowed = {
|
||||
@@ -143,18 +158,39 @@ DEFAULT_LANGUAGE = _normalize_default_language(
|
||||
|
||||
|
||||
def is_admin(username: str) -> bool:
|
||||
"""True if the given Telegram username (no @, any case) is in ADMIN_USERNAMES."""
|
||||
"""Check if Telegram username is in ADMIN_USERNAMES.
|
||||
|
||||
Args:
|
||||
username: Telegram username (with or without @; case-insensitive).
|
||||
|
||||
Returns:
|
||||
True if in ADMIN_USERNAMES.
|
||||
"""
|
||||
return (username or "").strip().lower() in ADMIN_USERNAMES
|
||||
|
||||
|
||||
def can_access_miniapp(username: str) -> bool:
|
||||
"""True if username is in ALLOWED_USERNAMES or ADMIN_USERNAMES."""
|
||||
"""Check if username is allowed to open the calendar Miniapp.
|
||||
|
||||
Args:
|
||||
username: Telegram username (with or without @; case-insensitive).
|
||||
|
||||
Returns:
|
||||
True if in ALLOWED_USERNAMES or ADMIN_USERNAMES.
|
||||
"""
|
||||
u = (username or "").strip().lower()
|
||||
return u in ALLOWED_USERNAMES or u in ADMIN_USERNAMES
|
||||
|
||||
|
||||
def can_access_miniapp_by_phone(phone: str | None) -> bool:
|
||||
"""True if normalized phone is in ALLOWED_PHONES or ADMIN_PHONES."""
|
||||
"""Check if phone (set via /set_phone) is allowed to open the Miniapp.
|
||||
|
||||
Args:
|
||||
phone: Raw phone string or None.
|
||||
|
||||
Returns:
|
||||
True if normalized phone is in ALLOWED_PHONES or ADMIN_PHONES.
|
||||
"""
|
||||
normalized = normalize_phone(phone)
|
||||
if not normalized:
|
||||
return False
|
||||
@@ -162,13 +198,27 @@ def can_access_miniapp_by_phone(phone: str | None) -> bool:
|
||||
|
||||
|
||||
def is_admin_by_phone(phone: str | None) -> bool:
|
||||
"""True if normalized phone is in ADMIN_PHONES."""
|
||||
"""Check if phone is in ADMIN_PHONES.
|
||||
|
||||
Args:
|
||||
phone: Raw phone string or None.
|
||||
|
||||
Returns:
|
||||
True if normalized phone is in ADMIN_PHONES.
|
||||
"""
|
||||
normalized = normalize_phone(phone)
|
||||
return bool(normalized and normalized in ADMIN_PHONES)
|
||||
|
||||
|
||||
def require_bot_token() -> None:
|
||||
"""Raise SystemExit with a clear message if BOT_TOKEN is not set. Call from entry point."""
|
||||
"""Raise SystemExit with a clear message if BOT_TOKEN is not set.
|
||||
|
||||
Call from the application entry point (e.g. main.py or duty_teller.run) so the
|
||||
process exits with a helpful message instead of failing later.
|
||||
|
||||
Raises:
|
||||
SystemExit: If BOT_TOKEN is empty.
|
||||
"""
|
||||
if not BOT_TOKEN:
|
||||
raise SystemExit(
|
||||
"BOT_TOKEN is not set. Copy .env.example to .env and set your token from @BotFather."
|
||||
|
||||
Reference in New Issue
Block a user