chore: add changelog and documentation updates
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:
2026-02-20 15:32:10 +03:00
parent b61e1ca8a5
commit 86f6d66865
88 changed files with 28912 additions and 118 deletions

View File

@@ -2,6 +2,8 @@
A minimal Telegram bot boilerplate using [python-telegram-bot](https://github.com/python-telegram-bot/python-telegram-bot) v22 with the `Application` API. The bot and web UI support **Russian and English** (language from Telegram or `DEFAULT_LANGUAGE`).
**History of changes:** [CHANGELOG.md](CHANGELOG.md).
## Get a bot token
1. Open Telegram and search for [@BotFather](https://t.me/BotFather).
@@ -34,22 +36,13 @@ A minimal Telegram bot boilerplate using [python-telegram-bot](https://github.co
Edit `.env` and set `BOT_TOKEN` to the token from BotFather.
5. **Miniapp access (calendar)**
To allow access to the calendar miniapp, set `ALLOWED_USERNAMES` to a comma-separated list of Telegram usernames (without `@`). Users in `ADMIN_USERNAMES` also have access; the admin role is reserved for future bot commands and API features. If both are empty, no one can open the calendar.
Set `ALLOWED_USERNAMES` (and optionally `ADMIN_USERNAMES`) to allow access to the calendar miniapp; if both are empty, no one can open it. Users can also be allowed by phone via `ALLOWED_PHONES` / `ADMIN_PHONES` after setting a phone with `/set_phone`.
**Mini App URL:** When configuring the bot's menu button or Web App URL (e.g. in @BotFather or via `setChatMenuButton`), use the URL **with a trailing slash**, e.g. `https://your-domain.com/app/`. A redirect from `/app` to `/app/` can cause the browser to drop the fragment that Telegram sends, which breaks authorization.
**How to open:** Users must open the calendar **via the bot's menu button** (⋮ → «Календарь» or the configured label) or a **Web App inline button**. If they use «Open in browser» or a direct link, Telegram may not send user data (`tgWebAppData`), and access will be denied.
**BOT_TOKEN:** The server that serves `/api/duties` (e.g. your production host) must have in `.env` the **same** bot token as the bot from which users open the Mini App. If the token differs (e.g. test vs production bot), validation returns "hash_mismatch" and access is denied.
6. **Optional env**
- `DATABASE_URL` DB connection (default: `sqlite:///data/duty_teller.db`).
- `HTTP_PORT` HTTP server port (default: `8080`).
- `MINI_APP_BASE_URL` Base URL of the miniapp (for documentation / CORS).
- `MINI_APP_SKIP_AUTH` Set to `1` to allow `/api/duties` without Telegram initData (dev only; insecure).
- `INIT_DATA_MAX_AGE_SECONDS` Reject Telegram initData older than this (e.g. `86400` = 24h). `0` = disabled (default).
- `CORS_ORIGINS` Comma-separated allowed origins for CORS; leave unset for `*`.
- `ALLOWED_PHONES` / `ADMIN_PHONES` Access by phone (user sets via `/set_phone`); comma-separated; comparison uses digits only.
- `DUTY_DISPLAY_TZ` Timezone for the pinned duty message in groups (e.g. `Europe/Moscow`).
- `DEFAULT_LANGUAGE` Default language when user language is unknown: `en` or `ru` (default in code: `en`).
- `EXTERNAL_CALENDAR_ICS_URL` URL of a public ICS calendar (e.g. holidays). If set, those days are highlighted on the duty grid; users can tap «i» on a cell to see the event summary. Empty = no external calendar.
6. **Other options**
Full list of environment variables (types, defaults, examples): **[docs/configuration.md](docs/configuration.md)**.
## Run
@@ -97,13 +90,18 @@ The image is built from `Dockerfile`; on start, `entrypoint.sh` runs Alembic mig
The HTTP server is FastAPI; the miniapp is served at `/app`.
**Interactive API documentation (Swagger UI)** is available at **`/docs`**, e.g. `http://localhost:8080/docs` when running locally.
- **`GET /api/duties`** — List of duties (date params; auth via Telegram initData or, in dev, `MINI_APP_SKIP_AUTH` / private IP).
- **`GET /api/calendar-events`** — Calendar events (including external ICS when `EXTERNAL_CALENDAR_ICS_URL` is set).
- **`GET /api/calendar/ical/{token}.ics`** — Personal ICS calendar (by secret token; no Telegram auth).
For production, initData validation is required; see the reverse-proxy paragraph above for proxy/headers.
## Project layout
High-level architecture (components, data flow, package relationships) is described in [docs/architecture.md](docs/architecture.md).
- `main.py` Entry point: calls `duty_teller.run:main`. Alternatively, after `pip install -e .`, run the console command **`duty-teller`** (see `pyproject.toml` and `duty_teller/run.py`). The runner builds the `Application`, registers handlers, runs polling and FastAPI in a thread, and calls `duty_teller.config.require_bot_token()` so the app exits with a clear message if `BOT_TOKEN` is missing.
- `duty_teller/` Main package (install with `pip install -e .`). Contains:
- `config.py` Loads `BOT_TOKEN`, `DATABASE_URL`, `ALLOWED_USERNAMES`, etc. from env; no exit on import; use `require_bot_token()` in the entry point when running the bot. Optional `Settings` dataclass for tests. `PROJECT_ROOT` for webapp path.
@@ -119,22 +117,20 @@ For production, initData validation is required; see the reverse-proxy paragraph
- `tests/` Tests; `helpers.py` provides `make_init_data` for auth tests.
- `pyproject.toml` Installable package (`pip install -e .`).
**Documentation:** The `docs/` folder contains configuration reference, architecture, import format, and runbook. API reference is generated from the code. Build: `mkdocs build` (requires `pip install -e ".[docs]"`). Preview: `mkdocs serve`.
To add commands, define async handlers in `duty_teller/handlers/commands.py` (or a new module) and register them in `duty_teller/handlers/__init__.py`.
## Импорт расписания дежурств (duty-schedule)
Команда **`/import_duty_schedule`** доступна только пользователям из `ADMIN_USERNAMES`. Импорт выполняется в два шага:
Команда **`/import_duty_schedule`** доступна только пользователям из `ADMIN_USERNAMES` или `ADMIN_PHONES`. Импорт выполняется в два шага:
1. **Время пересменки** — бот просит указать время и при необходимости часовой пояс (например `09:00 Europe/Moscow` или `06:00 UTC`). Время приводится к UTC и используется для границ смен при создании записей.
2. **Файл JSON** — отправьте файл в формате duty-schedule (см. ниже).
2. **Файл JSON** — отправьте файл в формате duty-schedule.
Формат **duty-schedule**:
- **meta**: обязательное поле `start_date` (YYYY-MM-DD), опционально `weeks`; количество дней определяется по длине строки `duty`.
- **schedule**: массив объектов с полями:
- `name` — ФИО (строка);
- `duty` — строка с разделителем `;`: каждый элемент соответствует дню с `start_date` по порядку. Пусто или пробелы — нет события; **в**, **В**, **б**, **Б** — дежурство; **Н** — недоступен; **О** — отпуск.
Формат: в корне JSON — объект **meta** с полем `start_date` (YYYY-MM-DD) и массив **schedule** с объектами `name` (ФИО) и `duty` (строка с разделителем `;`, символы **в/В/б/Б** — дежурство, **Н** — недоступен, **О** — отпуск). Количество дней задаётся длиной строки `duty`. При повторном импорте дежурства в том же диапазоне дат для каждого пользователя заменяются новыми.
При повторном импорте дежурства в том же диапазоне дат для каждого пользователя заменяются новыми.
**Полное описание формата и пример JSON:** [docs/import-format.md](docs/import-format.md).
## Tests