| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061 |
- -- 004_telegram.up.sql
- -- M3 Telegram delivery + bot commands. See SPEC §8.
- --
- -- What lands in M3:
- -- telegram_bots — one row per (company, bot). For M3 we
- -- support one bot per company; the table
- -- keys on (company_id, bot_id) so adding
- -- more later is a one-line change.
- -- individuals: — adds telegram_chat_id, telegram_user_id,
- -- telegram_invite_code (set on individual
- -- creation by the admin; admin hands the
- -- code to the user out-of-band), and
- -- mute_until (per-individual global mute
- -- for the /mute command).
- --
- -- What stays out of M3:
- -- Bot token encryption at rest. M3 stores the token in
- -- plaintext in telegram_bots.bot_token with a dev-only
- -- annotation. AES-256-GCM is a security milestone (§11).
- -- Real Telegram API. M3 ships faketgmd and a long-poll
- -- bot loop. Webhook mode is M5/M9.
- -- Retry + DLQ for failed sends. The single-attempt pattern
- -- from M1's fcm path is repeated. M9 adds the retry chain.
- --
- -- Naming: snake_case to match the rest of the schema.
- -- ── telegram_bots ────────────────────────────────────────────────
- CREATE TABLE IF NOT EXISTS telegram_bots (
- bot_id TEXT NOT NULL, -- short id; e.g. "primary"
- company_id TEXT NOT NULL REFERENCES companies(id) ON DELETE CASCADE,
- name TEXT NOT NULL, -- human label; e.g. "Acme Ops"
- bot_token TEXT NOT NULL, -- DEV ONLY; encrypt in M11 (security milestone)
- status TEXT NOT NULL DEFAULT 'active', -- active | paused
- last_seen_at TIMESTAMPTZ, -- last getUpdates / webhook hit
- created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
- PRIMARY KEY (company_id, bot_id)
- );
- CREATE INDEX IF NOT EXISTS idx_telegram_bots_company ON telegram_bots(company_id) WHERE status = 'active';
- -- ── extend individuals ───────────────────────────────────────────
- -- All four columns are nullable: an individual that has not yet
- -- linked their Telegram account has no chat_id / user_id. The
- -- invite_code is set when the admin creates the individual.
- ALTER TABLE individuals
- ADD COLUMN IF NOT EXISTS telegram_chat_id TEXT,
- ADD COLUMN IF NOT EXISTS telegram_user_id BIGINT,
- ADD COLUMN IF NOT EXISTS telegram_invite_code TEXT,
- ADD COLUMN IF NOT EXISTS mute_until TIMESTAMPTZ;
- -- Unique index for fast "/start <code>" lookup. Partial index
- -- so historical / legacy individuals without a code don't bloat
- -- the index.
- CREATE UNIQUE INDEX IF NOT EXISTS idx_individuals_invite_code
- ON individuals(telegram_invite_code)
- WHERE telegram_invite_code IS NOT NULL;
- -- Unique index for fast "is this telegram_user_id already linked?"
- -- check. Partial: most individuals won't be linked.
- CREATE UNIQUE INDEX IF NOT EXISTS idx_individuals_telegram_user
- ON individuals(company_id, telegram_user_id)
- WHERE telegram_user_id IS NOT NULL;
|