feat(bot): точка входа, конфиг, главное меню, доступ для ADMIN_IDS, healthcheck
- config: pydantic-settings по подсистемам, ADMIN_IDS через запятую - texts.yaml + валидация при старте (лишний/пропущенный ключ — ошибка сразу) - меню: reply-клавиатура разделов (Участники — только админам), инлайн «Назад»/«Отмена» с редактированием сообщения на месте, /start, /menu, /help - AccessMiddleware (outer) на сообщениях и кнопках: до #25 пускает только ADMIN_IDS, постороннему — одна фраза в сутки, устаревшие кнопки молча гасятся - heartbeat-файл + HEALTHCHECK в Dockerfile; JSON-логи - FSM пока в памяти (фабрика), хранилище в БД — в #5 - тесты на фейковой сессии aiogram через настоящий Dispatcher Refs #4 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
"""Typed configuration from the environment / `.env` — one settings class per subsystem (ARCHITECTURE §2.11)."""
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Annotated
|
||||
|
||||
from pydantic import Field, SecretStr, field_validator
|
||||
from pydantic_settings import BaseSettings, NoDecode, SettingsConfigDict
|
||||
|
||||
_ENV = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8", extra="ignore")
|
||||
|
||||
|
||||
class TelegramSettings(BaseSettings):
|
||||
model_config = SettingsConfigDict(**_ENV, env_prefix="TELEGRAM_")
|
||||
|
||||
bot_token: SecretStr
|
||||
|
||||
|
||||
class AccessSettings(BaseSettings):
|
||||
model_config = _ENV
|
||||
|
||||
# Bootstrap admins (SPEC A-8). Comma-separated in the env: ADMIN_IDS=1,2
|
||||
admin_ids: Annotated[frozenset[int], NoDecode] = frozenset()
|
||||
|
||||
@field_validator("admin_ids", mode="before")
|
||||
@classmethod
|
||||
def _split_ids(cls, value: object) -> object:
|
||||
if isinstance(value, str):
|
||||
return frozenset(int(part) for part in value.replace(" ", "").split(",") if part)
|
||||
return value
|
||||
|
||||
|
||||
class RuntimeSettings(BaseSettings):
|
||||
model_config = _ENV
|
||||
|
||||
log_level: str = "INFO"
|
||||
# Non-secret configs (texts, thresholds, prompts). Relative to the working dir: /srv/tgit in Docker.
|
||||
config_dir: Path = Path("config")
|
||||
# The heartbeat file is touched while the event loop is alive; the Docker healthcheck reads its age.
|
||||
heartbeat_file: Path = Path("/tmp/tgit-heartbeat")
|
||||
heartbeat_interval: float = Field(default=10.0, gt=0)
|
||||
heartbeat_max_age: float = Field(default=30.0, gt=0)
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
model_config = _ENV
|
||||
|
||||
telegram: TelegramSettings = Field(default_factory=TelegramSettings)
|
||||
access: AccessSettings = Field(default_factory=AccessSettings)
|
||||
runtime: RuntimeSettings = Field(default_factory=RuntimeSettings)
|
||||
Reference in New Issue
Block a user