@amalgm/channels 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PURPOSE.md +48 -13
- package/README.md +64 -6
- package/dist/host/auth.js +7 -3
- package/dist/host/auth.js.map +1 -1
- package/dist/host/config.d.ts +3 -1
- package/dist/host/config.js +3 -1
- package/dist/host/config.js.map +1 -1
- package/dist/host/inbox-repository.d.ts +10 -0
- package/dist/host/inbox-repository.js +57 -0
- package/dist/host/inbox-repository.js.map +1 -0
- package/dist/host/index.d.ts +4 -0
- package/dist/host/index.js +4 -0
- package/dist/host/index.js.map +1 -1
- package/dist/host/ingress-repository.d.ts +10 -0
- package/dist/host/ingress-repository.js +61 -0
- package/dist/host/ingress-repository.js.map +1 -0
- package/dist/host/main.js +32 -3
- package/dist/host/main.js.map +1 -1
- package/dist/host/repository.d.ts +4 -3
- package/dist/host/repository.js +23 -3
- package/dist/host/repository.js.map +1 -1
- package/dist/host/resend-webhook.d.ts +14 -0
- package/dist/host/resend-webhook.js +172 -0
- package/dist/host/resend-webhook.js.map +1 -0
- package/dist/host/resend.js +4 -0
- package/dist/host/resend.js.map +1 -1
- package/dist/host/server.d.ts +2 -1
- package/dist/host/server.js +85 -17
- package/dist/host/server.js.map +1 -1
- package/dist/host/wakeups.d.ts +22 -0
- package/dist/host/wakeups.js +30 -0
- package/dist/host/wakeups.js.map +1 -0
- package/dist/src/api-error.d.ts +2 -0
- package/dist/src/api-error.js +13 -0
- package/dist/src/api-error.js.map +1 -0
- package/dist/src/cli/arguments.d.ts +20 -0
- package/dist/src/cli/arguments.js +46 -0
- package/dist/src/cli/arguments.js.map +1 -0
- package/dist/src/cli/input.d.ts +6 -0
- package/dist/src/cli/input.js +38 -0
- package/dist/src/cli/input.js.map +1 -0
- package/dist/src/cli/main.d.ts +7 -0
- package/dist/src/cli/main.js +27 -0
- package/dist/src/cli/main.js.map +1 -0
- package/dist/src/cli/run.d.ts +13 -0
- package/dist/src/cli/run.js +47 -0
- package/dist/src/cli/run.js.map +1 -0
- package/dist/src/cli/shell-runtime.d.ts +9 -0
- package/dist/src/cli/shell-runtime.js +106 -0
- package/dist/src/cli/shell-runtime.js.map +1 -0
- package/dist/src/cli-main.d.ts +2 -0
- package/dist/src/cli-main.js +4 -0
- package/dist/src/cli-main.js.map +1 -0
- package/dist/src/cli.d.ts +3 -0
- package/dist/src/cli.js +4 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/client.js +5 -4
- package/dist/src/client.js.map +1 -1
- package/dist/src/command-surface.d.ts +23 -0
- package/dist/src/command-surface.js +53 -0
- package/dist/src/command-surface.js.map +1 -0
- package/dist/src/contract.d.ts +33 -7
- package/dist/src/email/blocks.d.ts +7 -0
- package/dist/src/email/blocks.js +43 -0
- package/dist/src/email/blocks.js.map +1 -0
- package/dist/src/email/escape.d.ts +3 -0
- package/dist/src/email/escape.js +19 -0
- package/dist/src/email/escape.js.map +1 -0
- package/dist/src/email/inbound.d.ts +83 -0
- package/dist/src/email/inbound.js +162 -0
- package/dist/src/email/inbound.js.map +1 -0
- package/dist/src/email/inline.d.ts +1 -0
- package/dist/src/email/inline.js +31 -0
- package/dist/src/email/inline.js.map +1 -0
- package/dist/src/email/markdown.d.ts +1 -0
- package/dist/src/email/markdown.js +117 -0
- package/dist/src/email/markdown.js.map +1 -0
- package/dist/src/email/notification.d.ts +2 -0
- package/dist/src/email/notification.js +18 -0
- package/dist/src/email/notification.js.map +1 -0
- package/dist/src/email/styles.d.ts +33 -0
- package/dist/src/email/styles.js +34 -0
- package/dist/src/email/styles.js.map +1 -0
- package/dist/src/email/system.d.ts +13 -0
- package/dist/src/email/system.js +31 -0
- package/dist/src/email/system.js.map +1 -0
- package/dist/src/email/thread-token.d.ts +8 -0
- package/dist/src/email/thread-token.js +70 -0
- package/dist/src/email/thread-token.js.map +1 -0
- package/dist/src/errors.d.ts +3 -2
- package/dist/src/errors.js +3 -1
- package/dist/src/errors.js.map +1 -1
- package/dist/src/http.d.ts +2 -0
- package/dist/src/http.js +84 -3
- package/dist/src/http.js.map +1 -1
- package/dist/src/inbox/client.d.ts +7 -0
- package/dist/src/inbox/client.js +79 -0
- package/dist/src/inbox/client.js.map +1 -0
- package/dist/src/inbox/contract.d.ts +37 -0
- package/dist/src/inbox/contract.js +2 -0
- package/dist/src/inbox/contract.js.map +1 -0
- package/dist/src/inbox/events.d.ts +7 -0
- package/dist/src/inbox/events.js +46 -0
- package/dist/src/inbox/events.js.map +1 -0
- package/dist/src/inbox/runner.d.ts +14 -0
- package/dist/src/inbox/runner.js +93 -0
- package/dist/src/inbox/runner.js.map +1 -0
- package/dist/src/inbox/service.d.ts +9 -0
- package/dist/src/inbox/service.js +56 -0
- package/dist/src/inbox/service.js.map +1 -0
- package/dist/src/index.d.ts +11 -0
- package/dist/src/index.js +11 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/mcp.js +20 -52
- package/dist/src/mcp.js.map +1 -1
- package/dist/src/service.js +7 -3
- package/dist/src/service.js.map +1 -1
- package/dist/src/validation.d.ts +2 -2
- package/dist/src/validation.js +2 -2
- package/dist/src/validation.js.map +1 -1
- package/dist/src/worker.d.ts +4 -1
- package/dist/src/worker.js +27 -5
- package/dist/src/worker.js.map +1 -1
- package/package.json +12 -5
- package/skills/channels/SKILL.md +14 -14
- package/supabase/migrations/20260903000000_bidirectional_email.sql +630 -0
|
@@ -0,0 +1,630 @@
|
|
|
1
|
+
-- Bidirectional email: provider events are admitted once, one external thread
|
|
2
|
+
-- owns one local Chat session, and only its DPoP-bound computer can claim work.
|
|
3
|
+
|
|
4
|
+
CREATE TABLE IF NOT EXISTS amalgm_channel_threads (
|
|
5
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
6
|
+
user_id uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
|
7
|
+
channel text NOT NULL CHECK (channel = 'email'),
|
|
8
|
+
session_id text NOT NULL CHECK (length(session_id) BETWEEN 1 AND 200),
|
|
9
|
+
computer_id uuid REFERENCES user_computers(id) ON DELETE RESTRICT,
|
|
10
|
+
subject text NOT NULL CHECK (length(subject) BETWEEN 1 AND 500),
|
|
11
|
+
last_message_id text,
|
|
12
|
+
message_references text[] NOT NULL DEFAULT '{}',
|
|
13
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
14
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
15
|
+
UNIQUE (user_id, channel, session_id)
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
ALTER TABLE amalgm_channel_threads ENABLE ROW LEVEL SECURITY;
|
|
19
|
+
REVOKE ALL ON amalgm_channel_threads FROM anon, authenticated;
|
|
20
|
+
GRANT SELECT, INSERT, UPDATE ON amalgm_channel_threads TO service_role;
|
|
21
|
+
|
|
22
|
+
ALTER TABLE amalgm_channel_deliveries
|
|
23
|
+
DROP CONSTRAINT IF EXISTS amalgm_channel_deliveries_status_check;
|
|
24
|
+
ALTER TABLE amalgm_channel_deliveries
|
|
25
|
+
ADD CONSTRAINT amalgm_channel_deliveries_status_check
|
|
26
|
+
CHECK (status IN ('pending', 'sending', 'sent', 'delivered', 'failed')),
|
|
27
|
+
ADD COLUMN IF NOT EXISTS thread_id uuid REFERENCES amalgm_channel_threads(id) ON DELETE RESTRICT,
|
|
28
|
+
ADD COLUMN IF NOT EXISTS session_id text,
|
|
29
|
+
ADD COLUMN IF NOT EXISTS computer_id uuid REFERENCES user_computers(id) ON DELETE RESTRICT,
|
|
30
|
+
ADD COLUMN IF NOT EXISTS rfc_message_id text,
|
|
31
|
+
ADD COLUMN IF NOT EXISTS available_at timestamptz NOT NULL DEFAULT now();
|
|
32
|
+
|
|
33
|
+
CREATE UNIQUE INDEX IF NOT EXISTS amalgm_channel_deliveries_rfc_message
|
|
34
|
+
ON amalgm_channel_deliveries(user_id, rfc_message_id)
|
|
35
|
+
WHERE rfc_message_id IS NOT NULL;
|
|
36
|
+
CREATE INDEX IF NOT EXISTS amalgm_channel_deliveries_provider_message
|
|
37
|
+
ON amalgm_channel_deliveries(user_id, provider_id)
|
|
38
|
+
WHERE provider_id IS NOT NULL;
|
|
39
|
+
|
|
40
|
+
CREATE TABLE IF NOT EXISTS amalgm_channel_inbound (
|
|
41
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
42
|
+
provider_event_id text NOT NULL UNIQUE,
|
|
43
|
+
provider_email_id text NOT NULL UNIQUE,
|
|
44
|
+
provider_message_id text NOT NULL,
|
|
45
|
+
user_id uuid REFERENCES auth.users(id) ON DELETE CASCADE,
|
|
46
|
+
channel text NOT NULL DEFAULT 'email' CHECK (channel = 'email'),
|
|
47
|
+
thread_id uuid REFERENCES amalgm_channel_threads(id) ON DELETE RESTRICT,
|
|
48
|
+
session_id text,
|
|
49
|
+
target_computer_id uuid REFERENCES user_computers(id) ON DELETE RESTRICT,
|
|
50
|
+
starts_session boolean NOT NULL DEFAULT false,
|
|
51
|
+
sender_email text NOT NULL,
|
|
52
|
+
subject text NOT NULL CHECK (length(subject) BETWEEN 1 AND 500),
|
|
53
|
+
message text,
|
|
54
|
+
message_references text[] NOT NULL DEFAULT '{}',
|
|
55
|
+
status text NOT NULL CHECK (status IN ('pending', 'claimed', 'accepted', 'rejected')),
|
|
56
|
+
attempts integer NOT NULL DEFAULT 0 CHECK (attempts >= 0),
|
|
57
|
+
available_at timestamptz NOT NULL DEFAULT now(),
|
|
58
|
+
lease_token uuid,
|
|
59
|
+
lease_expires_at timestamptz,
|
|
60
|
+
error text,
|
|
61
|
+
notice_provider_id text,
|
|
62
|
+
received_at timestamptz NOT NULL,
|
|
63
|
+
accepted_at timestamptz,
|
|
64
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
65
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
66
|
+
CHECK (
|
|
67
|
+
(status = 'claimed' AND lease_token IS NOT NULL AND lease_expires_at IS NOT NULL AND message IS NOT NULL)
|
|
68
|
+
OR (status = 'pending' AND lease_token IS NULL AND lease_expires_at IS NULL AND message IS NOT NULL)
|
|
69
|
+
OR (status IN ('accepted', 'rejected') AND lease_token IS NULL AND lease_expires_at IS NULL AND message IS NULL)
|
|
70
|
+
)
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
CREATE INDEX IF NOT EXISTS amalgm_channel_inbound_machine_work
|
|
74
|
+
ON amalgm_channel_inbound(target_computer_id, status, available_at, received_at, id)
|
|
75
|
+
WHERE status IN ('pending', 'claimed');
|
|
76
|
+
CREATE INDEX IF NOT EXISTS amalgm_channel_inbound_thread_message
|
|
77
|
+
ON amalgm_channel_inbound(user_id, provider_message_id)
|
|
78
|
+
WHERE user_id IS NOT NULL;
|
|
79
|
+
|
|
80
|
+
ALTER TABLE amalgm_channel_inbound ENABLE ROW LEVEL SECURITY;
|
|
81
|
+
REVOKE ALL ON amalgm_channel_inbound FROM anon, authenticated;
|
|
82
|
+
GRANT SELECT, INSERT, UPDATE ON amalgm_channel_inbound TO service_role;
|
|
83
|
+
|
|
84
|
+
CREATE TABLE IF NOT EXISTS amalgm_channel_provider_events (
|
|
85
|
+
provider_event_id text PRIMARY KEY,
|
|
86
|
+
provider_email_id text NOT NULL,
|
|
87
|
+
event_type text NOT NULL,
|
|
88
|
+
occurred_at timestamptz NOT NULL,
|
|
89
|
+
created_at timestamptz NOT NULL DEFAULT now()
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
ALTER TABLE amalgm_channel_provider_events ENABLE ROW LEVEL SECURITY;
|
|
93
|
+
REVOKE ALL ON amalgm_channel_provider_events FROM anon, authenticated;
|
|
94
|
+
GRANT SELECT, INSERT ON amalgm_channel_provider_events TO service_role;
|
|
95
|
+
|
|
96
|
+
CREATE OR REPLACE FUNCTION amalgm_channel_reference_tail(p_values text[])
|
|
97
|
+
RETURNS text[]
|
|
98
|
+
LANGUAGE sql
|
|
99
|
+
IMMUTABLE
|
|
100
|
+
SET search_path = public
|
|
101
|
+
AS $$
|
|
102
|
+
SELECT COALESCE(array_agg(value ORDER BY last_ordinal), '{}')
|
|
103
|
+
FROM (
|
|
104
|
+
SELECT value, max(ordinal) AS last_ordinal
|
|
105
|
+
FROM unnest(COALESCE(p_values, '{}')) WITH ORDINALITY AS refs(value, ordinal)
|
|
106
|
+
WHERE value IS NOT NULL AND value <> ''
|
|
107
|
+
GROUP BY value
|
|
108
|
+
ORDER BY max(ordinal) DESC
|
|
109
|
+
LIMIT 50
|
|
110
|
+
) recent;
|
|
111
|
+
$$;
|
|
112
|
+
|
|
113
|
+
CREATE OR REPLACE FUNCTION amalgm_channel_delivery_json(p_row amalgm_channel_deliveries)
|
|
114
|
+
RETURNS jsonb
|
|
115
|
+
LANGUAGE sql
|
|
116
|
+
IMMUTABLE
|
|
117
|
+
SET search_path = public
|
|
118
|
+
AS $$
|
|
119
|
+
SELECT jsonb_strip_nulls(jsonb_build_object(
|
|
120
|
+
'id', p_row.id,
|
|
121
|
+
'channel', p_row.channel,
|
|
122
|
+
'title', p_row.title,
|
|
123
|
+
'message', p_row.message,
|
|
124
|
+
'idempotency_key', p_row.idempotency_key,
|
|
125
|
+
'status', p_row.status,
|
|
126
|
+
'attempts', p_row.attempts,
|
|
127
|
+
'created_at', p_row.created_at,
|
|
128
|
+
'updated_at', p_row.updated_at,
|
|
129
|
+
'delivered_at', p_row.delivered_at,
|
|
130
|
+
'provider_id', p_row.provider_id,
|
|
131
|
+
'thread_id', p_row.thread_id,
|
|
132
|
+
'session_id', p_row.session_id,
|
|
133
|
+
'error', p_row.error
|
|
134
|
+
));
|
|
135
|
+
$$;
|
|
136
|
+
|
|
137
|
+
DROP FUNCTION IF EXISTS enqueue_amalgm_channel_delivery(uuid, jsonb);
|
|
138
|
+
CREATE OR REPLACE FUNCTION enqueue_amalgm_channel_delivery(
|
|
139
|
+
p_user_id uuid,
|
|
140
|
+
p_computer_id uuid,
|
|
141
|
+
p_delivery jsonb,
|
|
142
|
+
p_session_id text
|
|
143
|
+
) RETURNS jsonb
|
|
144
|
+
LANGUAGE plpgsql
|
|
145
|
+
SECURITY DEFINER
|
|
146
|
+
SET search_path = public, auth
|
|
147
|
+
AS $$
|
|
148
|
+
DECLARE
|
|
149
|
+
v_row public.amalgm_channel_deliveries;
|
|
150
|
+
v_thread public.amalgm_channel_threads;
|
|
151
|
+
v_channel text := CASE WHEN p_delivery->>'channel' = 'auto' THEN 'email' ELSE p_delivery->>'channel' END;
|
|
152
|
+
v_computer_id uuid := p_computer_id;
|
|
153
|
+
v_session_id text := NULLIF(btrim(p_session_id), '');
|
|
154
|
+
v_delivery_id uuid := gen_random_uuid();
|
|
155
|
+
BEGIN
|
|
156
|
+
IF v_channel <> 'email' THEN RAISE EXCEPTION 'unsupported channel' USING ERRCODE = '22023'; END IF;
|
|
157
|
+
|
|
158
|
+
SELECT * INTO v_row
|
|
159
|
+
FROM public.amalgm_channel_deliveries
|
|
160
|
+
WHERE user_id = p_user_id AND idempotency_key = p_delivery->>'idempotencyKey';
|
|
161
|
+
IF FOUND THEN
|
|
162
|
+
IF v_row.channel <> v_channel
|
|
163
|
+
OR v_row.title IS DISTINCT FROM NULLIF(p_delivery->>'title', '')
|
|
164
|
+
OR v_row.message <> p_delivery->>'message'
|
|
165
|
+
OR (v_session_id IS NOT NULL AND v_row.session_id IS DISTINCT FROM v_session_id)
|
|
166
|
+
THEN
|
|
167
|
+
RAISE EXCEPTION 'idempotency key already names another delivery' USING ERRCODE = '23505';
|
|
168
|
+
END IF;
|
|
169
|
+
RETURN public.amalgm_channel_delivery_json(v_row);
|
|
170
|
+
END IF;
|
|
171
|
+
|
|
172
|
+
IF v_session_id IS NOT NULL THEN
|
|
173
|
+
SELECT * INTO v_thread FROM public.amalgm_channel_threads
|
|
174
|
+
WHERE user_id = p_user_id AND channel = v_channel AND session_id = v_session_id;
|
|
175
|
+
END IF;
|
|
176
|
+
|
|
177
|
+
IF NOT FOUND THEN
|
|
178
|
+
IF v_computer_id IS NOT NULL AND NOT EXISTS (
|
|
179
|
+
SELECT 1 FROM public.user_computers WHERE id = v_computer_id AND user_id = p_user_id
|
|
180
|
+
) THEN
|
|
181
|
+
RAISE EXCEPTION 'computer does not belong to user' USING ERRCODE = '22023';
|
|
182
|
+
END IF;
|
|
183
|
+
IF v_computer_id IS NULL THEN
|
|
184
|
+
SELECT COALESCE(up.last_used_computer_id, up.default_computer_id) INTO v_computer_id
|
|
185
|
+
FROM public.user_preferences up WHERE up.user_id = p_user_id;
|
|
186
|
+
END IF;
|
|
187
|
+
IF v_computer_id IS NULL THEN
|
|
188
|
+
SELECT id INTO v_computer_id FROM public.user_computers
|
|
189
|
+
WHERE user_id = p_user_id ORDER BY last_seen_at DESC NULLS LAST, updated_at DESC LIMIT 1;
|
|
190
|
+
END IF;
|
|
191
|
+
IF v_computer_id IS NULL THEN RAISE EXCEPTION 'user has no computer' USING ERRCODE = '22023'; END IF;
|
|
192
|
+
v_session_id := COALESCE(v_session_id, gen_random_uuid()::text);
|
|
193
|
+
INSERT INTO public.amalgm_channel_threads(user_id, channel, session_id, computer_id, subject)
|
|
194
|
+
VALUES (p_user_id, v_channel, v_session_id, v_computer_id, p_delivery->>'title')
|
|
195
|
+
ON CONFLICT (user_id, channel, session_id) DO UPDATE
|
|
196
|
+
SET session_id = EXCLUDED.session_id
|
|
197
|
+
RETURNING * INTO v_thread;
|
|
198
|
+
END IF;
|
|
199
|
+
|
|
200
|
+
INSERT INTO public.amalgm_channel_deliveries(
|
|
201
|
+
id, user_id, channel, title, message, idempotency_key,
|
|
202
|
+
thread_id, session_id, computer_id, rfc_message_id
|
|
203
|
+
) VALUES (
|
|
204
|
+
v_delivery_id, p_user_id, v_channel, NULLIF(p_delivery->>'title', ''),
|
|
205
|
+
p_delivery->>'message', p_delivery->>'idempotencyKey',
|
|
206
|
+
v_thread.id, v_thread.session_id, v_thread.computer_id,
|
|
207
|
+
'<' || v_delivery_id::text || '@amalgm.ai>'
|
|
208
|
+
)
|
|
209
|
+
ON CONFLICT (user_id, idempotency_key) DO NOTHING
|
|
210
|
+
RETURNING * INTO v_row;
|
|
211
|
+
IF NOT FOUND THEN
|
|
212
|
+
SELECT * INTO v_row FROM public.amalgm_channel_deliveries
|
|
213
|
+
WHERE user_id = p_user_id AND idempotency_key = p_delivery->>'idempotencyKey';
|
|
214
|
+
IF v_row.channel <> v_channel
|
|
215
|
+
OR v_row.title IS DISTINCT FROM NULLIF(p_delivery->>'title', '')
|
|
216
|
+
OR v_row.message <> p_delivery->>'message'
|
|
217
|
+
OR v_row.session_id IS DISTINCT FROM v_thread.session_id
|
|
218
|
+
THEN
|
|
219
|
+
RAISE EXCEPTION 'idempotency key already names another delivery' USING ERRCODE = '23505';
|
|
220
|
+
END IF;
|
|
221
|
+
END IF;
|
|
222
|
+
RETURN public.amalgm_channel_delivery_json(v_row);
|
|
223
|
+
END;
|
|
224
|
+
$$;
|
|
225
|
+
|
|
226
|
+
DROP FUNCTION IF EXISTS claim_amalgm_channel_deliveries(integer, integer);
|
|
227
|
+
CREATE OR REPLACE FUNCTION claim_amalgm_channel_deliveries(
|
|
228
|
+
p_limit integer,
|
|
229
|
+
p_lease_seconds integer
|
|
230
|
+
) RETURNS TABLE (
|
|
231
|
+
id uuid, user_id uuid, channel text, title text, message text,
|
|
232
|
+
idempotency_key text, lease_token uuid, attempts integer,
|
|
233
|
+
thread_id uuid, session_id text, thread_subject text, rfc_message_id text,
|
|
234
|
+
reply_to_message_id text, "references" text[]
|
|
235
|
+
)
|
|
236
|
+
LANGUAGE sql
|
|
237
|
+
SECURITY DEFINER
|
|
238
|
+
SET search_path = public
|
|
239
|
+
AS $$
|
|
240
|
+
WITH candidates AS (
|
|
241
|
+
SELECT d.id
|
|
242
|
+
FROM public.amalgm_channel_deliveries d
|
|
243
|
+
WHERE d.attempts < 5
|
|
244
|
+
AND (
|
|
245
|
+
(d.status = 'pending' AND d.available_at <= now())
|
|
246
|
+
OR (d.status = 'sending' AND d.lease_expires_at <= now())
|
|
247
|
+
)
|
|
248
|
+
ORDER BY d.available_at, d.created_at, d.id
|
|
249
|
+
FOR UPDATE SKIP LOCKED
|
|
250
|
+
LIMIT LEAST(GREATEST(p_limit, 1), 100)
|
|
251
|
+
), claimed AS (
|
|
252
|
+
UPDATE public.amalgm_channel_deliveries d SET
|
|
253
|
+
status = 'sending', attempts = d.attempts + 1,
|
|
254
|
+
lease_token = gen_random_uuid(),
|
|
255
|
+
lease_expires_at = now() + make_interval(secs => LEAST(GREATEST(p_lease_seconds, 10), 600)),
|
|
256
|
+
updated_at = now()
|
|
257
|
+
FROM candidates WHERE d.id = candidates.id
|
|
258
|
+
RETURNING d.*
|
|
259
|
+
)
|
|
260
|
+
SELECT c.id, c.user_id, c.channel, c.title, c.message,
|
|
261
|
+
c.idempotency_key, c.lease_token, c.attempts,
|
|
262
|
+
t.id, t.session_id, t.subject, c.rfc_message_id,
|
|
263
|
+
t.last_message_id, t.message_references
|
|
264
|
+
FROM claimed c JOIN public.amalgm_channel_threads t ON t.id = c.thread_id;
|
|
265
|
+
$$;
|
|
266
|
+
|
|
267
|
+
DROP FUNCTION IF EXISTS complete_amalgm_channel_delivery(uuid, uuid, text, timestamptz);
|
|
268
|
+
CREATE OR REPLACE FUNCTION mark_amalgm_channel_delivery_sent(
|
|
269
|
+
p_delivery_id uuid,
|
|
270
|
+
p_lease_token uuid,
|
|
271
|
+
p_provider_id text,
|
|
272
|
+
p_now timestamptz
|
|
273
|
+
) RETURNS boolean
|
|
274
|
+
LANGUAGE plpgsql
|
|
275
|
+
SECURITY DEFINER
|
|
276
|
+
SET search_path = public
|
|
277
|
+
AS $$
|
|
278
|
+
BEGIN
|
|
279
|
+
UPDATE public.amalgm_channel_deliveries SET
|
|
280
|
+
status = 'sent', provider_id = left(p_provider_id, 500), error = NULL,
|
|
281
|
+
lease_token = NULL, lease_expires_at = NULL, updated_at = p_now
|
|
282
|
+
WHERE id = p_delivery_id AND status = 'sending' AND lease_token = p_lease_token;
|
|
283
|
+
RETURN FOUND;
|
|
284
|
+
END;
|
|
285
|
+
$$;
|
|
286
|
+
|
|
287
|
+
CREATE OR REPLACE FUNCTION fail_amalgm_channel_delivery(
|
|
288
|
+
p_delivery_id uuid,
|
|
289
|
+
p_lease_token uuid,
|
|
290
|
+
p_error text,
|
|
291
|
+
p_now timestamptz
|
|
292
|
+
) RETURNS boolean
|
|
293
|
+
LANGUAGE plpgsql
|
|
294
|
+
SECURITY DEFINER
|
|
295
|
+
SET search_path = public
|
|
296
|
+
AS $$
|
|
297
|
+
BEGIN
|
|
298
|
+
UPDATE public.amalgm_channel_deliveries SET
|
|
299
|
+
status = CASE WHEN attempts >= 5 THEN 'failed' ELSE 'pending' END,
|
|
300
|
+
available_at = p_now + make_interval(secs => LEAST(300, 5 * (2 ^ LEAST(attempts, 6))::integer)),
|
|
301
|
+
error = left(p_error, 500), lease_token = NULL, lease_expires_at = NULL,
|
|
302
|
+
updated_at = p_now
|
|
303
|
+
WHERE id = p_delivery_id AND status = 'sending' AND lease_token = p_lease_token;
|
|
304
|
+
RETURN FOUND;
|
|
305
|
+
END;
|
|
306
|
+
$$;
|
|
307
|
+
|
|
308
|
+
CREATE OR REPLACE FUNCTION next_amalgm_channel_delivery_at()
|
|
309
|
+
RETURNS timestamptz
|
|
310
|
+
LANGUAGE sql
|
|
311
|
+
SECURITY DEFINER
|
|
312
|
+
SET search_path = public
|
|
313
|
+
AS $$
|
|
314
|
+
SELECT min(ready_at) FROM (
|
|
315
|
+
SELECT available_at AS ready_at FROM public.amalgm_channel_deliveries
|
|
316
|
+
WHERE status = 'pending' AND attempts < 5
|
|
317
|
+
UNION ALL
|
|
318
|
+
SELECT lease_expires_at FROM public.amalgm_channel_deliveries
|
|
319
|
+
WHERE status = 'sending' AND attempts < 5
|
|
320
|
+
) work;
|
|
321
|
+
$$;
|
|
322
|
+
|
|
323
|
+
CREATE OR REPLACE FUNCTION admit_amalgm_channel_email(p_email jsonb)
|
|
324
|
+
RETURNS jsonb
|
|
325
|
+
LANGUAGE plpgsql
|
|
326
|
+
SECURITY DEFINER
|
|
327
|
+
SET search_path = public, auth
|
|
328
|
+
AS $$
|
|
329
|
+
DECLARE
|
|
330
|
+
v_existing public.amalgm_channel_inbound;
|
|
331
|
+
v_user_id uuid;
|
|
332
|
+
v_thread public.amalgm_channel_threads;
|
|
333
|
+
v_message public.amalgm_channel_inbound;
|
|
334
|
+
v_computer_id uuid;
|
|
335
|
+
v_computer_name text;
|
|
336
|
+
v_online boolean := false;
|
|
337
|
+
v_starts_session boolean := false;
|
|
338
|
+
v_reply_id text;
|
|
339
|
+
BEGIN
|
|
340
|
+
SELECT * INTO v_existing FROM public.amalgm_channel_inbound
|
|
341
|
+
WHERE provider_email_id = p_email->>'providerEmailId'
|
|
342
|
+
OR provider_event_id = p_email->>'providerEventId'
|
|
343
|
+
LIMIT 1;
|
|
344
|
+
IF FOUND THEN
|
|
345
|
+
IF v_existing.status = 'rejected' THEN
|
|
346
|
+
RETURN jsonb_build_object(
|
|
347
|
+
'kind', CASE WHEN v_existing.user_id IS NULL THEN 'unknown_sender' ELSE 'no_computer' END,
|
|
348
|
+
'record_id', v_existing.id, 'sender_email', v_existing.sender_email,
|
|
349
|
+
'subject', v_existing.subject, 'provider_message_id', v_existing.provider_message_id,
|
|
350
|
+
'references', v_existing.message_references
|
|
351
|
+
);
|
|
352
|
+
END IF;
|
|
353
|
+
RETURN jsonb_build_object(
|
|
354
|
+
'kind', 'accepted', 'user_id', v_existing.user_id, 'message_id', v_existing.id,
|
|
355
|
+
'thread_id', v_existing.thread_id, 'session_id', v_existing.session_id,
|
|
356
|
+
'target_computer_id', v_existing.target_computer_id,
|
|
357
|
+
'target_computer_name', COALESCE((SELECT name FROM public.user_computers WHERE id = v_existing.target_computer_id), 'Your computer'),
|
|
358
|
+
'target_online', EXISTS(SELECT 1 FROM public.user_computers WHERE id = v_existing.target_computer_id AND status = 'online'),
|
|
359
|
+
'sender_email', v_existing.sender_email, 'subject', v_existing.subject,
|
|
360
|
+
'provider_message_id', v_existing.provider_message_id, 'references', v_existing.message_references
|
|
361
|
+
);
|
|
362
|
+
END IF;
|
|
363
|
+
|
|
364
|
+
SELECT id INTO v_user_id FROM auth.users
|
|
365
|
+
WHERE lower(email) = lower(p_email->>'senderEmail') LIMIT 1;
|
|
366
|
+
IF v_user_id IS NULL THEN
|
|
367
|
+
INSERT INTO public.amalgm_channel_inbound(
|
|
368
|
+
provider_event_id, provider_email_id, provider_message_id,
|
|
369
|
+
sender_email, subject, message, message_references, status, received_at
|
|
370
|
+
) VALUES (
|
|
371
|
+
p_email->>'providerEventId', p_email->>'providerEmailId', p_email->>'providerMessageId',
|
|
372
|
+
p_email->>'senderEmail', p_email->>'subject', NULL,
|
|
373
|
+
COALESCE(ARRAY(SELECT jsonb_array_elements_text(p_email->'replyMessageIds')), '{}'),
|
|
374
|
+
'rejected', (p_email->>'receivedAt')::timestamptz
|
|
375
|
+
) RETURNING * INTO v_message;
|
|
376
|
+
RETURN jsonb_build_object(
|
|
377
|
+
'kind', 'unknown_sender', 'record_id', v_message.id,
|
|
378
|
+
'sender_email', v_message.sender_email, 'subject', v_message.subject,
|
|
379
|
+
'provider_message_id', v_message.provider_message_id, 'references', v_message.message_references
|
|
380
|
+
);
|
|
381
|
+
END IF;
|
|
382
|
+
|
|
383
|
+
-- A signed reply address narrows a real reply; copying that address into a
|
|
384
|
+
-- fresh composition does not turn the new root into the old thread.
|
|
385
|
+
IF p_email ? 'threadHintId'
|
|
386
|
+
AND jsonb_array_length(COALESCE(p_email->'replyMessageIds', '[]'::jsonb)) > 0
|
|
387
|
+
THEN
|
|
388
|
+
SELECT * INTO v_thread FROM public.amalgm_channel_threads
|
|
389
|
+
WHERE id = (p_email->>'threadHintId')::uuid AND user_id = v_user_id AND channel = 'email';
|
|
390
|
+
END IF;
|
|
391
|
+
IF v_thread.id IS NULL THEN
|
|
392
|
+
FOR v_reply_id IN SELECT jsonb_array_elements_text(COALESCE(p_email->'replyMessageIds', '[]'::jsonb)) LOOP
|
|
393
|
+
SELECT t.* INTO v_thread
|
|
394
|
+
FROM public.amalgm_channel_threads t
|
|
395
|
+
WHERE t.user_id = v_user_id AND t.channel = 'email' AND (
|
|
396
|
+
EXISTS(SELECT 1 FROM public.amalgm_channel_deliveries d
|
|
397
|
+
WHERE d.thread_id = t.id AND (d.rfc_message_id = v_reply_id OR d.provider_id = v_reply_id))
|
|
398
|
+
OR EXISTS(SELECT 1 FROM public.amalgm_channel_inbound i
|
|
399
|
+
WHERE i.thread_id = t.id AND i.provider_message_id = v_reply_id)
|
|
400
|
+
) LIMIT 1;
|
|
401
|
+
EXIT WHEN v_thread.id IS NOT NULL;
|
|
402
|
+
END LOOP;
|
|
403
|
+
END IF;
|
|
404
|
+
|
|
405
|
+
IF v_thread.id IS NULL THEN
|
|
406
|
+
SELECT COALESCE(up.last_used_computer_id, up.default_computer_id) INTO v_computer_id
|
|
407
|
+
FROM public.user_preferences up WHERE up.user_id = v_user_id;
|
|
408
|
+
IF v_computer_id IS NULL THEN
|
|
409
|
+
SELECT id INTO v_computer_id FROM public.user_computers
|
|
410
|
+
WHERE user_id = v_user_id ORDER BY last_seen_at DESC NULLS LAST, updated_at DESC LIMIT 1;
|
|
411
|
+
END IF;
|
|
412
|
+
IF v_computer_id IS NULL THEN
|
|
413
|
+
INSERT INTO public.amalgm_channel_inbound(
|
|
414
|
+
provider_event_id, provider_email_id, provider_message_id, user_id,
|
|
415
|
+
sender_email, subject, message, message_references, status, received_at
|
|
416
|
+
) VALUES (
|
|
417
|
+
p_email->>'providerEventId', p_email->>'providerEmailId', p_email->>'providerMessageId', v_user_id,
|
|
418
|
+
p_email->>'senderEmail', p_email->>'subject', NULL,
|
|
419
|
+
COALESCE(ARRAY(SELECT jsonb_array_elements_text(p_email->'replyMessageIds')), '{}'),
|
|
420
|
+
'rejected', (p_email->>'receivedAt')::timestamptz
|
|
421
|
+
) RETURNING * INTO v_message;
|
|
422
|
+
RETURN jsonb_build_object(
|
|
423
|
+
'kind', 'no_computer', 'record_id', v_message.id,
|
|
424
|
+
'sender_email', v_message.sender_email, 'subject', v_message.subject,
|
|
425
|
+
'provider_message_id', v_message.provider_message_id, 'references', v_message.message_references
|
|
426
|
+
);
|
|
427
|
+
END IF;
|
|
428
|
+
INSERT INTO public.amalgm_channel_threads(user_id, channel, session_id, computer_id, subject)
|
|
429
|
+
VALUES (v_user_id, 'email', gen_random_uuid()::text, v_computer_id, p_email->>'subject')
|
|
430
|
+
RETURNING * INTO v_thread;
|
|
431
|
+
v_starts_session := true;
|
|
432
|
+
END IF;
|
|
433
|
+
|
|
434
|
+
v_computer_id := v_thread.computer_id;
|
|
435
|
+
SELECT name, status = 'online' INTO v_computer_name, v_online
|
|
436
|
+
FROM public.user_computers WHERE id = v_computer_id AND user_id = v_user_id;
|
|
437
|
+
|
|
438
|
+
INSERT INTO public.amalgm_channel_inbound(
|
|
439
|
+
provider_event_id, provider_email_id, provider_message_id, user_id,
|
|
440
|
+
thread_id, session_id, target_computer_id, starts_session,
|
|
441
|
+
sender_email, subject, message, message_references, status, received_at
|
|
442
|
+
) VALUES (
|
|
443
|
+
p_email->>'providerEventId', p_email->>'providerEmailId', p_email->>'providerMessageId', v_user_id,
|
|
444
|
+
v_thread.id, v_thread.session_id, v_computer_id, v_starts_session,
|
|
445
|
+
p_email->>'senderEmail', p_email->>'subject', p_email->>'message',
|
|
446
|
+
COALESCE(ARRAY(SELECT jsonb_array_elements_text(p_email->'replyMessageIds')), '{}'),
|
|
447
|
+
'pending', (p_email->>'receivedAt')::timestamptz
|
|
448
|
+
) RETURNING * INTO v_message;
|
|
449
|
+
|
|
450
|
+
UPDATE public.amalgm_channel_threads SET
|
|
451
|
+
last_message_id = v_message.provider_message_id,
|
|
452
|
+
message_references = public.amalgm_channel_reference_tail(
|
|
453
|
+
message_references || v_message.message_references || ARRAY[v_message.provider_message_id]
|
|
454
|
+
),
|
|
455
|
+
updated_at = now()
|
|
456
|
+
WHERE id = v_thread.id;
|
|
457
|
+
|
|
458
|
+
RETURN jsonb_build_object(
|
|
459
|
+
'kind', 'accepted', 'user_id', v_user_id, 'message_id', v_message.id,
|
|
460
|
+
'thread_id', v_thread.id, 'session_id', v_thread.session_id,
|
|
461
|
+
'target_computer_id', v_computer_id, 'target_computer_name', COALESCE(v_computer_name, 'Your computer'),
|
|
462
|
+
'target_online', COALESCE(v_online, false), 'sender_email', v_message.sender_email,
|
|
463
|
+
'subject', v_message.subject, 'provider_message_id', v_message.provider_message_id,
|
|
464
|
+
'references', v_message.message_references
|
|
465
|
+
);
|
|
466
|
+
EXCEPTION WHEN unique_violation THEN
|
|
467
|
+
SELECT * INTO v_existing FROM public.amalgm_channel_inbound
|
|
468
|
+
WHERE provider_email_id = p_email->>'providerEmailId' OR provider_event_id = p_email->>'providerEventId' LIMIT 1;
|
|
469
|
+
RETURN jsonb_build_object('kind', 'duplicate', 'record_id', v_existing.id);
|
|
470
|
+
END;
|
|
471
|
+
$$;
|
|
472
|
+
|
|
473
|
+
CREATE OR REPLACE FUNCTION claim_amalgm_channel_inbound(
|
|
474
|
+
p_user_id uuid,
|
|
475
|
+
p_computer_id uuid,
|
|
476
|
+
p_limit integer,
|
|
477
|
+
p_lease_seconds integer
|
|
478
|
+
) RETURNS SETOF amalgm_channel_inbound
|
|
479
|
+
LANGUAGE sql
|
|
480
|
+
SECURITY DEFINER
|
|
481
|
+
SET search_path = public
|
|
482
|
+
AS $$
|
|
483
|
+
WITH candidates AS (
|
|
484
|
+
SELECT i.id
|
|
485
|
+
FROM public.amalgm_channel_inbound i
|
|
486
|
+
WHERE i.user_id = p_user_id AND i.target_computer_id = p_computer_id
|
|
487
|
+
AND i.available_at <= now()
|
|
488
|
+
AND (i.status = 'pending' OR (i.status = 'claimed' AND i.lease_expires_at <= now()))
|
|
489
|
+
AND NOT EXISTS (
|
|
490
|
+
SELECT 1 FROM public.amalgm_channel_inbound earlier
|
|
491
|
+
WHERE earlier.thread_id = i.thread_id AND earlier.id <> i.id
|
|
492
|
+
AND earlier.status IN ('pending', 'claimed')
|
|
493
|
+
AND (earlier.received_at, earlier.id) < (i.received_at, i.id)
|
|
494
|
+
)
|
|
495
|
+
ORDER BY i.received_at, i.id
|
|
496
|
+
FOR UPDATE SKIP LOCKED
|
|
497
|
+
LIMIT LEAST(GREATEST(p_limit, 1), 10)
|
|
498
|
+
)
|
|
499
|
+
UPDATE public.amalgm_channel_inbound i SET
|
|
500
|
+
status = 'claimed', attempts = i.attempts + 1,
|
|
501
|
+
lease_token = gen_random_uuid(),
|
|
502
|
+
lease_expires_at = now() + make_interval(secs => LEAST(GREATEST(p_lease_seconds, 30), 900)),
|
|
503
|
+
updated_at = now()
|
|
504
|
+
FROM candidates WHERE i.id = candidates.id
|
|
505
|
+
RETURNING i.*;
|
|
506
|
+
$$;
|
|
507
|
+
|
|
508
|
+
CREATE OR REPLACE FUNCTION accept_amalgm_channel_inbound(
|
|
509
|
+
p_user_id uuid, p_computer_id uuid, p_message_id uuid,
|
|
510
|
+
p_lease_token uuid, p_now timestamptz
|
|
511
|
+
) RETURNS boolean
|
|
512
|
+
LANGUAGE plpgsql SECURITY DEFINER SET search_path = public
|
|
513
|
+
AS $$
|
|
514
|
+
BEGIN
|
|
515
|
+
UPDATE public.amalgm_channel_inbound SET
|
|
516
|
+
status = 'accepted', message = NULL, accepted_at = p_now,
|
|
517
|
+
lease_token = NULL, lease_expires_at = NULL, error = NULL, updated_at = p_now
|
|
518
|
+
WHERE id = p_message_id AND user_id = p_user_id AND target_computer_id = p_computer_id
|
|
519
|
+
AND status = 'claimed' AND lease_token = p_lease_token;
|
|
520
|
+
RETURN FOUND;
|
|
521
|
+
END;
|
|
522
|
+
$$;
|
|
523
|
+
|
|
524
|
+
CREATE OR REPLACE FUNCTION release_amalgm_channel_inbound(
|
|
525
|
+
p_user_id uuid, p_computer_id uuid, p_message_id uuid,
|
|
526
|
+
p_lease_token uuid, p_error text, p_now timestamptz
|
|
527
|
+
) RETURNS boolean
|
|
528
|
+
LANGUAGE plpgsql SECURITY DEFINER SET search_path = public
|
|
529
|
+
AS $$
|
|
530
|
+
BEGIN
|
|
531
|
+
UPDATE public.amalgm_channel_inbound SET
|
|
532
|
+
status = 'pending', available_at = p_now + make_interval(secs => LEAST(60, (2 ^ LEAST(attempts, 5))::integer)),
|
|
533
|
+
lease_token = NULL, lease_expires_at = NULL, error = left(p_error, 500), updated_at = p_now
|
|
534
|
+
WHERE id = p_message_id AND user_id = p_user_id AND target_computer_id = p_computer_id
|
|
535
|
+
AND status = 'claimed' AND lease_token = p_lease_token;
|
|
536
|
+
RETURN FOUND;
|
|
537
|
+
END;
|
|
538
|
+
$$;
|
|
539
|
+
|
|
540
|
+
CREATE OR REPLACE FUNCTION record_amalgm_channel_inbound_notice(
|
|
541
|
+
p_message_id uuid, p_provider_id text, p_now timestamptz
|
|
542
|
+
) RETURNS boolean
|
|
543
|
+
LANGUAGE plpgsql SECURITY DEFINER SET search_path = public
|
|
544
|
+
AS $$
|
|
545
|
+
BEGIN
|
|
546
|
+
UPDATE public.amalgm_channel_inbound SET
|
|
547
|
+
notice_provider_id = COALESCE(notice_provider_id, left(p_provider_id, 500)), updated_at = p_now
|
|
548
|
+
WHERE id = p_message_id;
|
|
549
|
+
RETURN FOUND;
|
|
550
|
+
END;
|
|
551
|
+
$$;
|
|
552
|
+
|
|
553
|
+
CREATE OR REPLACE FUNCTION apply_amalgm_channel_provider_event(p_event jsonb)
|
|
554
|
+
RETURNS boolean
|
|
555
|
+
LANGUAGE plpgsql SECURITY DEFINER SET search_path = public
|
|
556
|
+
AS $$
|
|
557
|
+
DECLARE
|
|
558
|
+
v_delivery public.amalgm_channel_deliveries;
|
|
559
|
+
v_type text := p_event->>'type';
|
|
560
|
+
v_message_id text := NULLIF(p_event->>'providerMessageId', '');
|
|
561
|
+
BEGIN
|
|
562
|
+
INSERT INTO public.amalgm_channel_provider_events(provider_event_id, provider_email_id, event_type, occurred_at)
|
|
563
|
+
VALUES (p_event->>'providerEventId', p_event->>'providerEmailId', v_type, (p_event->>'occurredAt')::timestamptz)
|
|
564
|
+
ON CONFLICT DO NOTHING;
|
|
565
|
+
IF NOT FOUND THEN RETURN false; END IF;
|
|
566
|
+
|
|
567
|
+
SELECT * INTO v_delivery FROM public.amalgm_channel_deliveries
|
|
568
|
+
WHERE provider_id = p_event->>'providerEmailId' LIMIT 1 FOR UPDATE;
|
|
569
|
+
IF NOT FOUND THEN RETURN true; END IF;
|
|
570
|
+
|
|
571
|
+
UPDATE public.amalgm_channel_deliveries SET
|
|
572
|
+
status = CASE
|
|
573
|
+
WHEN status = 'delivered' THEN status
|
|
574
|
+
WHEN v_type = 'delivered' THEN 'delivered'
|
|
575
|
+
WHEN v_type = 'failed' THEN 'failed'
|
|
576
|
+
ELSE 'sent'
|
|
577
|
+
END,
|
|
578
|
+
delivered_at = CASE WHEN v_type = 'delivered' THEN (p_event->>'occurredAt')::timestamptz ELSE delivered_at END,
|
|
579
|
+
rfc_message_id = COALESCE(v_message_id, rfc_message_id),
|
|
580
|
+
error = CASE WHEN v_type = 'failed' THEN left(p_event->>'error', 500) ELSE NULL END,
|
|
581
|
+
updated_at = (p_event->>'occurredAt')::timestamptz
|
|
582
|
+
WHERE id = v_delivery.id;
|
|
583
|
+
|
|
584
|
+
IF v_message_id IS NOT NULL THEN
|
|
585
|
+
UPDATE public.amalgm_channel_threads SET
|
|
586
|
+
last_message_id = v_message_id,
|
|
587
|
+
message_references = public.amalgm_channel_reference_tail(message_references || ARRAY[v_message_id]),
|
|
588
|
+
updated_at = now()
|
|
589
|
+
WHERE id = v_delivery.thread_id;
|
|
590
|
+
END IF;
|
|
591
|
+
RETURN true;
|
|
592
|
+
END;
|
|
593
|
+
$$;
|
|
594
|
+
|
|
595
|
+
DO $$
|
|
596
|
+
BEGIN
|
|
597
|
+
IF EXISTS (SELECT 1 FROM pg_publication WHERE pubname = 'supabase_realtime')
|
|
598
|
+
AND NOT EXISTS (
|
|
599
|
+
SELECT 1 FROM pg_publication_tables
|
|
600
|
+
WHERE pubname = 'supabase_realtime' AND schemaname = 'public' AND tablename = 'amalgm_channel_inbound'
|
|
601
|
+
)
|
|
602
|
+
THEN
|
|
603
|
+
ALTER PUBLICATION supabase_realtime ADD TABLE amalgm_channel_inbound;
|
|
604
|
+
END IF;
|
|
605
|
+
END $$;
|
|
606
|
+
|
|
607
|
+
REVOKE ALL ON FUNCTION enqueue_amalgm_channel_delivery(uuid, uuid, jsonb, text) FROM PUBLIC;
|
|
608
|
+
REVOKE ALL ON FUNCTION amalgm_channel_reference_tail(text[]) FROM PUBLIC;
|
|
609
|
+
REVOKE ALL ON FUNCTION claim_amalgm_channel_deliveries(integer, integer) FROM PUBLIC;
|
|
610
|
+
REVOKE ALL ON FUNCTION mark_amalgm_channel_delivery_sent(uuid, uuid, text, timestamptz) FROM PUBLIC;
|
|
611
|
+
REVOKE ALL ON FUNCTION fail_amalgm_channel_delivery(uuid, uuid, text, timestamptz) FROM PUBLIC;
|
|
612
|
+
REVOKE ALL ON FUNCTION next_amalgm_channel_delivery_at() FROM PUBLIC;
|
|
613
|
+
REVOKE ALL ON FUNCTION admit_amalgm_channel_email(jsonb) FROM PUBLIC;
|
|
614
|
+
REVOKE ALL ON FUNCTION claim_amalgm_channel_inbound(uuid, uuid, integer, integer) FROM PUBLIC;
|
|
615
|
+
REVOKE ALL ON FUNCTION accept_amalgm_channel_inbound(uuid, uuid, uuid, uuid, timestamptz) FROM PUBLIC;
|
|
616
|
+
REVOKE ALL ON FUNCTION release_amalgm_channel_inbound(uuid, uuid, uuid, uuid, text, timestamptz) FROM PUBLIC;
|
|
617
|
+
REVOKE ALL ON FUNCTION record_amalgm_channel_inbound_notice(uuid, text, timestamptz) FROM PUBLIC;
|
|
618
|
+
REVOKE ALL ON FUNCTION apply_amalgm_channel_provider_event(jsonb) FROM PUBLIC;
|
|
619
|
+
|
|
620
|
+
GRANT EXECUTE ON FUNCTION enqueue_amalgm_channel_delivery(uuid, uuid, jsonb, text) TO service_role;
|
|
621
|
+
GRANT EXECUTE ON FUNCTION claim_amalgm_channel_deliveries(integer, integer) TO service_role;
|
|
622
|
+
GRANT EXECUTE ON FUNCTION mark_amalgm_channel_delivery_sent(uuid, uuid, text, timestamptz) TO service_role;
|
|
623
|
+
GRANT EXECUTE ON FUNCTION fail_amalgm_channel_delivery(uuid, uuid, text, timestamptz) TO service_role;
|
|
624
|
+
GRANT EXECUTE ON FUNCTION next_amalgm_channel_delivery_at() TO service_role;
|
|
625
|
+
GRANT EXECUTE ON FUNCTION admit_amalgm_channel_email(jsonb) TO service_role;
|
|
626
|
+
GRANT EXECUTE ON FUNCTION claim_amalgm_channel_inbound(uuid, uuid, integer, integer) TO service_role;
|
|
627
|
+
GRANT EXECUTE ON FUNCTION accept_amalgm_channel_inbound(uuid, uuid, uuid, uuid, timestamptz) TO service_role;
|
|
628
|
+
GRANT EXECUTE ON FUNCTION release_amalgm_channel_inbound(uuid, uuid, uuid, uuid, text, timestamptz) TO service_role;
|
|
629
|
+
GRANT EXECUTE ON FUNCTION record_amalgm_channel_inbound_notice(uuid, text, timestamptz) TO service_role;
|
|
630
|
+
GRANT EXECUTE ON FUNCTION apply_amalgm_channel_provider_event(jsonb) TO service_role;
|