# 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`.