Файли
2026-11-08 03:46:11 +00:00

122 рядки
5.1 KiB
Markdown

# Architecture
## Context
```text
Telegram user
|
| updates, long polling
v
+-------------+ +------------------+
| bot process| <----> | Telegram Bot API|
+------+------+ +------------------+
|
| GORM
v
+-------------+
| PostgreSQL |
+-------------+
```
One bot process. Telegram is an untrusted input source and an output channel,
never a store. PostgreSQL is the only stateful dependency.
## Packages
```text
cmd/bot/ composition root: config, wiring, run loop, shutdown
internal/config/ environment parsing and validation
internal/domain/ entities, enums, invariants, domain errors
internal/service/ use cases: users, lists, items, access, sessions
internal/repository/ GORM repository implementations
internal/database/ connection setup, pool tuning, migrations
internal/telegram/ telego routes, feature handlers, keyboards, rendering
internal/events/ publisher interface and no-op implementation
docs/ architecture, infrastructure, plan, notes
```
| Package | Responsibility |
| ------------ | ----------------------------------------------------------------------------------------- |
| `cmd/bot` | Reads config, constructs concrete types, injects them, starts polling, handles `SIGTERM`. |
| `config` | Loads env and `.env`, validates required values, fails fast. |
| `domain` | Entities and rules: list ownership, item status, urgency, reservation. No I/O. |
| `service` | Use cases and authorization. Owns transactions. Declares repository interfaces. |
| `repository` | GORM models and queries. Implements service interfaces. |
| `database` | Opens the pool, sets limits, exposes migration entry point. |
| `telegram` | Routes and middleware; feature subpackages own commands, callbacks, and views. |
| `events` | `Publisher` seam for future notifications and activity feeds. |
## Telegram layout
```text
telegram/
bot.go, routes.go, middleware.go lifecycle, routing, identity, bot-access gate, ordering
shared/ sender, reply keyboard, callback codec, user context, pending text router
menu/ /start, /help, /cancel
access/ owner /allow and /kick commands
list/ list index, create, rename, delete
item/ list menu, paginated item list, item screens, prompts
share/ owner share management, pending shares
```
Features register their own `telegohandler` routes. All callback data is built
and validated in `shared`; feature packages only wrap the typed builders.
Free-text prompts are dispatched through
`shared.PendingRouter`, so the root text handler does not grow with each
feature. The `item` package owns the list detail screen because it renders item
rows and pagination; the `list` package delegates to it after create and
rename. The `share` package owns share management; the item list menu links to
it for owners.
The root middleware gate checks bot access before any feature runs. The owner
comes from `BOT_OWNER_ID`; everyone else must have a row in `bot_access`.
Usernames are only stored on access grants so pending grants can be resolved,
and are refreshed when the user contacts the bot.
## Layering
```text
cmd/bot
|
telegram
|
service
|
domain
```
Rules:
- `domain` imports only the standard library.
- `service` imports `domain` and declares the repository interfaces it needs.
- `repository` imports `domain` and GORM, and implements those interfaces.
- `telegram` imports `service` and `domain`. It never imports `repository`.
- `cmd/bot` is the only place that knows concrete implementations.
- Dependencies point inward. Nothing calls from `service` back into
`telegram`.
This is manual dependency injection. Constructors return concrete types;
interfaces are declared at the consumer. A DI library is unnecessary at this
size.
## Domain summary
- `bot_access`: grants bot access by numeric Telegram ID, or by username until
the matching user contacts the bot.
- `users`: one row per allowed Telegram user, keyed by `telegram_id`.
- `lists`: owned by exactly one user.
- `list_shares`: grants a user `reader` or `editor` access to a list.
- `items`: belong to a list, have `status` (`open`/`done`), `urgency`
(`low`/`normal`/`high`), and at most one reserver.
- `pending_shares`: shares granted by username before that user contacts the
bot.
Bot access is checked once per update before a user row is ensured. The owner
is always allowed. After the user row is ensured, pending shares that match the
user's Telegram ID or username are activated. One `Authorize(ctx, userID,
listID, action)` call remains the source of truth for list access. Revoking bot
access also removes the user's shares; owned lists are never touched.
Schema, access matrix, callback format, and use-case details live in
`docs/temp-notes.md`.