Reviewed-on: #1 Co-authored-by: Danya H <[email protected]>
5.1 KiB
Architecture
Context
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
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
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
cmd/bot
|
telegram
|
service
|
domain
Rules:
domainimports only the standard library.serviceimportsdomainand declares the repository interfaces it needs.repositoryimportsdomainand GORM, and implements those interfaces.telegramimportsserviceanddomain. It never importsrepository.cmd/botis the only place that knows concrete implementations.- Dependencies point inward. Nothing calls from
serviceback intotelegram.
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 bytelegram_id.lists: owned by exactly one user.list_shares: grants a userreaderoreditoraccess to a list.items: belong to a list, havestatus(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.