012_telegram_bot_fields.up.sql 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. -- 012_telegram_bot_fields.up.sql
  2. --
  3. -- M13b W3: extend public.telegram_bots so the admin UI can
  4. -- configure a per-bot welcome message and default source. Also
  5. -- adds a bcrypt hash of the bot_token so the UI can render a
  6. -- "configured / not configured" indicator without ever exposing
  7. -- the plaintext.
  8. --
  9. -- Why both columns?
  10. -- - bot_token — TEXT, plaintext. telegramd reads
  11. -- this directly to authenticate with
  12. -- api.telegram.org. Keep it for now
  13. -- so telegramd continues to work.
  14. -- - bot_token_hash — TEXT, bcrypt. Added in W3 so the UI
  15. -- can determine "configured" status
  16. -- without exposing the plaintext. The
  17. -- M11 security milestone will replace
  18. -- bot_token entirely with an
  19. -- AES-256-GCM-encrypted column and
  20. -- add a sidecar to decrypt for
  21. -- telegramd.
  22. --
  23. -- Why nullable bot_token_hash?
  24. -- Rows seeded by 004/seed_m3.sql have a plaintext bot_token
  25. -- but no hash yet. The next W3 rotation (or operator save via
  26. -- UI) will populate the hash. NULL = "unrotated since W3";
  27. -- the UI surfaces this as "Configured (legacy token)" with
  28. -- a one-click "Rotate to set hash" hint.
  29. --
  30. -- Other columns:
  31. -- welcome_message — sent in response to /start (M13c wires
  32. -- telegramd to use this; W3 just stores)
  33. -- default_source_id — optional pointer to public.sources.id.
  34. -- W3 sets it on the bot row; W4
  35. -- (Smoke) verifies the FK shape. We do
  36. -- NOT add a hard FK in this migration
  37. -- because sources can be deleted out
  38. -- from under the bot (admin flow); a
  39. -- soft reference + ON DELETE SET NULL
  40. -- would be correct, but we keep the
  41. -- bot row even if the source goes away
  42. -- (operator may want to point it at a
  43. -- new source). v1.1 adds the FK +
  44. -- a reconciliation job.
  45. -- description — free-text label. Same shape as
  46. -- sources.description.
  47. -- last_rotated_at — set every time the token is written
  48. -- (create OR rotate). UI shows it.
  49. -- updated_at — bumped on any PATCH. Audit-friendly.
  50. --
  51. -- The down migration drops the new columns. The original
  52. -- bot_token plaintext column is preserved.
  53. ALTER TABLE telegram_bots
  54. ADD COLUMN IF NOT EXISTS bot_token_hash TEXT,
  55. ADD COLUMN IF NOT EXISTS welcome_message TEXT,
  56. ADD COLUMN IF NOT EXISTS default_source_id TEXT,
  57. ADD COLUMN IF NOT EXISTS description TEXT,
  58. ADD COLUMN IF NOT EXISTS last_rotated_at TIMESTAMPTZ,
  59. ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ NOT NULL DEFAULT now();
  60. -- updated_at trigger so any UPDATE bumps it without the app
  61. -- having to remember. Mirrors the auth.tenants.updated_at
  62. -- pattern from migration 010.
  63. CREATE OR REPLACE FUNCTION telegram_bots_touch_updated_at() RETURNS TRIGGER AS $$
  64. BEGIN
  65. NEW.updated_at = now();
  66. RETURN NEW;
  67. END;
  68. $$ LANGUAGE plpgsql;
  69. DROP TRIGGER IF EXISTS trg_telegram_bots_touch_updated_at ON telegram_bots;
  70. CREATE TRIGGER trg_telegram_bots_touch_updated_at
  71. BEFORE UPDATE ON telegram_bots
  72. FOR EACH ROW
  73. EXECUTE FUNCTION telegram_bots_touch_updated_at();
  74. -- Index on default_source_id so M13c routing lookups
  75. -- ("give me the bots that use this source") are fast even when
  76. -- the table is large. Partial index — most rows will have NULL
  77. -- default_source_id in v1.
  78. CREATE INDEX IF NOT EXISTS idx_telegram_bots_default_source
  79. ON telegram_bots(default_source_id)
  80. WHERE default_source_id IS NOT NULL;