122 рядки
5.1 KiB
Markdown
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`.
|