73 lines
4.0 KiB
SQL
73 lines
4.0 KiB
SQL
-- +goose Up
|
|
|
|
-- Client-facing notification channel — Telegram (client subscribes via a
|
|
-- one-time deep-link token) with SMS as a fallback when no chat is linked.
|
|
-- Separate from internal/notify (staff-only chat, single fixed chat_id):
|
|
-- here every client is a distinct recipient, so delivery needs an outbox
|
|
-- (client_notifications) rather than a fire-and-forget Send() call, plus a
|
|
-- linking flow (client_notification_links) since a client's Telegram
|
|
-- identity isn't known until they message the bot.
|
|
|
|
ALTER TABLE clients ADD COLUMN phone_normalized TEXT;
|
|
ALTER TABLE clients ADD COLUMN tg_chat_id TEXT;
|
|
ALTER TABLE clients ADD COLUMN tg_subscribed_at TIMESTAMPTZ;
|
|
ALTER TABLE clients ADD COLUMN notify_telegram BOOLEAN NOT NULL DEFAULT TRUE;
|
|
-- SMS costs money per message — opt-in, unlike Telegram.
|
|
ALTER TABLE clients ADD COLUMN notify_sms BOOLEAN NOT NULL DEFAULT FALSE;
|
|
-- 152-ФЗ (RU personal data law) consent timestamp, set when a client
|
|
-- explicitly opts in via the lead form or the bot's /start flow.
|
|
ALTER TABLE clients ADD COLUMN consent_at TIMESTAMPTZ;
|
|
|
|
CREATE UNIQUE INDEX clients_tg_chat_id_idx ON clients (tg_chat_id) WHERE tg_chat_id IS NOT NULL;
|
|
CREATE INDEX clients_phone_normalized_idx ON clients (phone_normalized) WHERE phone_normalized IS NOT NULL;
|
|
|
|
-- One-time deep-link tokens (t.me/<bot>?start=<token>) binding a Telegram
|
|
-- chat to a specific client once they press Start. Short-lived by design —
|
|
-- expires_at enforced in Go, not a CHECK, so "expired" can mean "past
|
|
-- expires_at OR already used" without two constraints.
|
|
CREATE TABLE client_notification_links (
|
|
token TEXT PRIMARY KEY,
|
|
client_id UUID NOT NULL REFERENCES clients(id) ON DELETE CASCADE,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
expires_at TIMESTAMPTZ NOT NULL,
|
|
used_at TIMESTAMPTZ
|
|
);
|
|
CREATE INDEX client_notification_links_client_id_idx ON client_notification_links (client_id);
|
|
|
|
-- Outbox + audit trail, append-only like every other ledger in this schema
|
|
-- (order_events, cash_transactions). dedupe_key is built from the event
|
|
-- that triggered the send (e.g. an order_events.id), not from the
|
|
-- client/trigger pair alone, so re-entering the same status twice after
|
|
-- leaving it produces a new event and a new notification, while retries of
|
|
-- the SAME event never double-send.
|
|
CREATE TABLE client_notifications (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
client_id UUID NOT NULL REFERENCES clients(id) ON DELETE CASCADE,
|
|
order_id UUID REFERENCES orders(id) ON DELETE SET NULL,
|
|
cartridge_batch_id UUID REFERENCES cartridge_batches(id) ON DELETE SET NULL,
|
|
trigger TEXT NOT NULL CHECK (trigger IN
|
|
('created', 'status_changed', 'ready', 'warranty_expiring',
|
|
'loyalty_accrued', 'manual')),
|
|
channel TEXT NOT NULL CHECK (channel IN ('telegram', 'sms')),
|
|
status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN
|
|
('pending', 'sent', 'failed', 'skipped')),
|
|
attempts INT NOT NULL DEFAULT 0,
|
|
body TEXT NOT NULL,
|
|
dedupe_key TEXT NOT NULL,
|
|
provider_message_id TEXT,
|
|
error TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
sent_at TIMESTAMPTZ
|
|
);
|
|
CREATE UNIQUE INDEX client_notifications_dedupe_idx ON client_notifications (dedupe_key);
|
|
CREATE INDEX client_notifications_client_id_idx ON client_notifications (client_id, created_at DESC);
|
|
|
|
ALTER TABLE settings ADD COLUMN sms_provider TEXT;
|
|
ALTER TABLE settings ADD COLUMN sms_api_id TEXT;
|
|
ALTER TABLE settings ADD COLUMN sms_from TEXT;
|
|
ALTER TABLE settings ADD COLUMN sms_enabled BOOLEAN NOT NULL DEFAULT FALSE;
|
|
ALTER TABLE settings ADD COLUMN client_tg_bot_username TEXT;
|
|
ALTER TABLE settings ADD COLUMN tg_webhook_secret TEXT;
|
|
ALTER TABLE settings ADD COLUMN client_notify_enabled BOOLEAN NOT NULL DEFAULT FALSE;
|
|
ALTER TABLE settings ADD COLUMN public_tracking_url TEXT;
|