120 lines
6.2 KiB
MySQL
120 lines
6.2 KiB
MySQL
|
|
-- Phone surface → multi-provider messaging surface. Sendblue rows are
|
||
|
|
-- preserved under provider 'sendblue'; conversation addressing moves to
|
||
|
|
-- opaque provider thread ids, so per-vendor columns collapse.
|
||
|
|
|
||
|
|
-- Identities: one (provider, address) per person, DM thread learned on inbound.
|
||
|
|
ALTER TABLE "phone_identities" RENAME TO "messaging_identities";
|
||
|
|
ALTER TABLE "messaging_identities" RENAME COLUMN "phoneE164" TO "address";
|
||
|
|
ALTER TABLE "messaging_identities" ADD COLUMN "provider" TEXT NOT NULL DEFAULT 'sendblue';
|
||
|
|
-- The default only backfills the pre-existing sendblue rows; application code
|
||
|
|
-- always writes provider explicitly.
|
||
|
|
ALTER TABLE "messaging_identities" ALTER COLUMN "provider" DROP DEFAULT;
|
||
|
|
ALTER TABLE "messaging_identities" ADD COLUMN "dmThreadId" TEXT;
|
||
|
|
ALTER INDEX "phone_identities_pkey" RENAME TO "messaging_identities_pkey";
|
||
|
|
ALTER INDEX "phone_identities_botId_key" RENAME TO "messaging_identities_botId_key";
|
||
|
|
ALTER INDEX "phone_identities_userId_idx" RENAME TO "messaging_identities_userId_idx";
|
||
|
|
DROP INDEX "phone_identities_phoneE164_key";
|
||
|
|
CREATE UNIQUE INDEX "messaging_identities_provider_address_key"
|
||
|
|
ON "messaging_identities"("provider", "address");
|
||
|
|
ALTER TABLE "messaging_identities"
|
||
|
|
RENAME CONSTRAINT "phone_identities_spaceId_fkey" TO "messaging_identities_spaceId_fkey";
|
||
|
|
|
||
|
|
-- Channels: keyed by provider thread id. Legacy sendblue group ids cannot be
|
||
|
|
-- re-encoded into thread ids in SQL; prefix them so the next inbound group
|
||
|
|
-- message recreates the channel and restarts its invite cycle cleanly.
|
||
|
|
ALTER TABLE "phone_channels" RENAME TO "messaging_channels";
|
||
|
|
ALTER TABLE "messaging_channels" RENAME COLUMN "providerGroupId" TO "threadId";
|
||
|
|
ALTER TABLE "messaging_channels" ADD COLUMN "provider" TEXT NOT NULL DEFAULT 'sendblue';
|
||
|
|
ALTER TABLE "messaging_channels" ALTER COLUMN "provider" DROP DEFAULT;
|
||
|
|
UPDATE "messaging_channels" SET "threadId" = 'legacy:' || "threadId";
|
||
|
|
ALTER INDEX "phone_channels_pkey" RENAME TO "messaging_channels_pkey";
|
||
|
|
ALTER INDEX "phone_channels_providerGroupId_key" RENAME TO "messaging_channels_threadId_key";
|
||
|
|
|
||
|
|
ALTER TABLE "phone_channel_members" RENAME TO "messaging_channel_members";
|
||
|
|
ALTER TABLE "messaging_channel_members" RENAME COLUMN "phoneE164" TO "address";
|
||
|
|
ALTER INDEX "phone_channel_members_pkey" RENAME TO "messaging_channel_members_pkey";
|
||
|
|
ALTER INDEX "phone_channel_members_channelId_phoneE164_key"
|
||
|
|
RENAME TO "messaging_channel_members_channelId_address_key";
|
||
|
|
ALTER INDEX "phone_channel_members_identityId_idx"
|
||
|
|
RENAME TO "messaging_channel_members_identityId_idx";
|
||
|
|
ALTER TABLE "messaging_channel_members"
|
||
|
|
RENAME CONSTRAINT "phone_channel_members_channelId_fkey"
|
||
|
|
TO "messaging_channel_members_channelId_fkey";
|
||
|
|
-- Close out the retired channels' memberships too. A prefixed channel can
|
||
|
|
-- never receive traffic again, so a lingering 'approved' row would answer the
|
||
|
|
-- owner's next LEAVE command instead of their real group, and a lingering
|
||
|
|
-- 'invited' row would sit unanswerable in the web list. The next inbound group
|
||
|
|
-- message rebuilds membership under the real thread id.
|
||
|
|
UPDATE "messaging_channel_members" m
|
||
|
|
SET "status" = 'left'
|
||
|
|
FROM "messaging_channels" c
|
||
|
|
WHERE m."channelId" = c."id"
|
||
|
|
AND c."threadId" LIKE 'legacy:%'
|
||
|
|
AND m."status" IN ('invited', 'approved');
|
||
|
|
|
||
|
|
-- Outbox: DM rows resolve threads through the identity; group rows carry the
|
||
|
|
-- provider thread id. Map pending DMs onto identities, then fail only rows
|
||
|
|
-- that still cannot be addressed (no identityId and no remappable group
|
||
|
|
-- threadId). Identity-backed DMs stay pending so in-flight YES/NO replies
|
||
|
|
-- and invites drain after deploy.
|
||
|
|
ALTER TABLE "phone_outbound" RENAME TO "messaging_outbound";
|
||
|
|
ALTER TABLE "messaging_outbound" ADD COLUMN "identityId" TEXT;
|
||
|
|
ALTER TABLE "messaging_outbound" ADD COLUMN "threadId" TEXT;
|
||
|
|
UPDATE "messaging_outbound" o
|
||
|
|
SET "identityId" = i."id"
|
||
|
|
FROM "messaging_identities" i
|
||
|
|
WHERE o."toNumber" IS NOT NULL AND i."provider" = 'sendblue' AND i."address" = o."toNumber";
|
||
|
|
-- Legacy sendblue group ids cannot be re-encoded into Chat SDK thread ids in
|
||
|
|
-- SQL, so providerGroupId never becomes a remappable threadId here.
|
||
|
|
UPDATE "messaging_outbound"
|
||
|
|
SET "status" = 'failed'
|
||
|
|
WHERE "status" = 'pending'
|
||
|
|
AND "identityId" IS NULL
|
||
|
|
AND "threadId" IS NULL;
|
||
|
|
ALTER TABLE "messaging_outbound" DROP COLUMN "toNumber";
|
||
|
|
ALTER TABLE "messaging_outbound" DROP COLUMN "providerGroupId";
|
||
|
|
ALTER INDEX "phone_outbound_pkey" RENAME TO "messaging_outbound_pkey";
|
||
|
|
ALTER INDEX "phone_outbound_idempotencyKey_key" RENAME TO "messaging_outbound_idempotencyKey_key";
|
||
|
|
ALTER INDEX "phone_outbound_status_nextAttemptAt_idx"
|
||
|
|
RENAME TO "messaging_outbound_status_nextAttemptAt_idx";
|
||
|
|
|
||
|
|
-- Runs and stored message blocks move to the neutral vocabulary. Replayed
|
||
|
|
-- run.started event payloads carry the trigger too, so they migrate as well.
|
||
|
|
UPDATE "runs" SET "trigger" = 'messaging' WHERE "trigger" = 'phone';
|
||
|
|
UPDATE "events"
|
||
|
|
SET "payload" = jsonb_set("payload", '{trigger}', '"messaging"')
|
||
|
|
WHERE "payload" ->> 'trigger' = 'phone';
|
||
|
|
UPDATE "messages"
|
||
|
|
SET "blocks" = (
|
||
|
|
SELECT jsonb_agg(
|
||
|
|
CASE
|
||
|
|
WHEN block ->> 'kind' = 'phone_channel_message' THEN
|
||
|
|
(block - 'fromNumber')
|
||
|
|
|| jsonb_build_object(
|
||
|
|
'kind', 'channel_message',
|
||
|
|
'provider', 'sendblue',
|
||
|
|
'fromAddress', block -> 'fromNumber'
|
||
|
|
)
|
||
|
|
ELSE block
|
||
|
|
END
|
||
|
|
ORDER BY position
|
||
|
|
)
|
||
|
|
FROM jsonb_array_elements("blocks") WITH ORDINALITY AS entry(block, position)
|
||
|
|
)
|
||
|
|
WHERE "blocks"::text LIKE '%phone_channel_message%';
|
||
|
|
|
||
|
|
-- Web-issued linking codes: bind a chat address to an existing user + bot.
|
||
|
|
CREATE TABLE "messaging_link_codes" (
|
||
|
|
"id" TEXT NOT NULL,
|
||
|
|
"code" TEXT NOT NULL,
|
||
|
|
"userId" TEXT NOT NULL,
|
||
|
|
"spaceId" TEXT NOT NULL,
|
||
|
|
"botId" TEXT NOT NULL,
|
||
|
|
"expiresAt" TIMESTAMP(3) NOT NULL,
|
||
|
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
|
|
|
||
|
|
CONSTRAINT "messaging_link_codes_pkey" PRIMARY KEY ("id")
|
||
|
|
);
|
||
|
|
CREATE UNIQUE INDEX "messaging_link_codes_code_key" ON "messaging_link_codes"("code");
|
||
|
|
CREATE INDEX "messaging_link_codes_userId_idx" ON "messaging_link_codes"("userId");
|