@vrplatform/kysely 1.3.45-10 → 1.3.45-21
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/build/main/local/generate.js +1 -0
- package/build/main/local/generate.js.map +1 -1
- package/build/main/local/index.js +11 -0
- package/build/main/local/index.js.map +1 -1
- package/build/migrations-v2/0000000000015-durable-flow-events.ts +163 -0
- package/build/migrations-v2/0000000000016-billing-listing-facts.ts +539 -0
- package/build/migrations-v2/0000000000017-webhook-secret-reencryption-failures.ts +18 -0
- package/build/migrations-v2/0000000000018-durable-flow-event-sensitive-input.ts +28 -0
- package/build/migrations-v2/0000000000019-asynchronous-operations.ts +736 -0
- package/build/migrations-v2/0000000000031-ui-permission-role-simplification.ts +70 -0
- package/build/module/local/generate.js +1 -0
- package/build/module/local/generate.js.map +1 -1
- package/build/module/local/index.js +11 -0
- package/build/module/local/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,736 @@
|
|
|
1
|
+
import type { Kysely } from 'kysely';
|
|
2
|
+
import { sql } from 'kysely';
|
|
3
|
+
|
|
4
|
+
export async function up(db: Kysely<unknown>): Promise<void> {
|
|
5
|
+
await sql`
|
|
6
|
+
create table core.operation (
|
|
7
|
+
id uuid primary key default gen_random_uuid(),
|
|
8
|
+
tenant_id uuid not null,
|
|
9
|
+
initiated_by_user_id uuid,
|
|
10
|
+
type text not null,
|
|
11
|
+
status text not null default 'queued',
|
|
12
|
+
failure_code text,
|
|
13
|
+
failure_message text,
|
|
14
|
+
created_at timestamptz not null default now(),
|
|
15
|
+
started_at timestamptz,
|
|
16
|
+
finished_at timestamptz,
|
|
17
|
+
updated_at timestamptz not null default now(),
|
|
18
|
+
source_version bigint not null default 1,
|
|
19
|
+
retain_until timestamptz,
|
|
20
|
+
constraint operation_type_check check (type in (
|
|
21
|
+
'ach-verification',
|
|
22
|
+
'bank-rule-run',
|
|
23
|
+
'calendar-block',
|
|
24
|
+
'calendar-unblock',
|
|
25
|
+
'connection-archive',
|
|
26
|
+
'connection-connect',
|
|
27
|
+
'connection-extract',
|
|
28
|
+
'connection-sync',
|
|
29
|
+
'csv-import',
|
|
30
|
+
'csv-import-preview',
|
|
31
|
+
'export',
|
|
32
|
+
'flow-run',
|
|
33
|
+
'historical-gl-import',
|
|
34
|
+
'historical-statement-import',
|
|
35
|
+
'listing-journal-refresh',
|
|
36
|
+
'plaid-initial-sync',
|
|
37
|
+
'pms-cutover',
|
|
38
|
+
'pms-cutover-preview',
|
|
39
|
+
'provider-payment',
|
|
40
|
+
'provider-payment-recovery',
|
|
41
|
+
'recurring-transaction-run',
|
|
42
|
+
'reservation-journal-refresh',
|
|
43
|
+
'team-demo-data',
|
|
44
|
+
'team-duplicate',
|
|
45
|
+
'team-initialize',
|
|
46
|
+
'transaction-journal-refresh',
|
|
47
|
+
'vri-statement-import',
|
|
48
|
+
'vri-team-migration',
|
|
49
|
+
'webhook-test'
|
|
50
|
+
)),
|
|
51
|
+
constraint operation_status_check
|
|
52
|
+
check (status in ('queued', 'running', 'completed', 'failed')),
|
|
53
|
+
constraint operation_failure_check check (
|
|
54
|
+
(status = 'failed' and failure_code is not null and failure_message is not null)
|
|
55
|
+
or (status != 'failed' and failure_code is null and failure_message is null)
|
|
56
|
+
),
|
|
57
|
+
constraint operation_finished_check check (
|
|
58
|
+
(status in ('completed', 'failed') and finished_at is not null)
|
|
59
|
+
or (status in ('queued', 'running') and finished_at is null)
|
|
60
|
+
),
|
|
61
|
+
constraint operation_source_version_check check (source_version > 0)
|
|
62
|
+
)
|
|
63
|
+
`.execute(db);
|
|
64
|
+
await sql`
|
|
65
|
+
create index operation_tenant_history_idx
|
|
66
|
+
on core.operation (tenant_id, created_at desc, id desc)
|
|
67
|
+
`.execute(db);
|
|
68
|
+
await sql`
|
|
69
|
+
create index operation_tenant_status_history_idx
|
|
70
|
+
on core.operation (tenant_id, status, created_at desc, id desc)
|
|
71
|
+
`.execute(db);
|
|
72
|
+
await sql`
|
|
73
|
+
create index operation_tenant_type_history_idx
|
|
74
|
+
on core.operation (tenant_id, type, created_at desc, id desc)
|
|
75
|
+
`.execute(db);
|
|
76
|
+
|
|
77
|
+
await sql`
|
|
78
|
+
create table core.operation_source (
|
|
79
|
+
operation_id uuid not null references core.operation(id) on delete cascade,
|
|
80
|
+
source_type text not null,
|
|
81
|
+
source_id uuid not null,
|
|
82
|
+
required boolean not null default true,
|
|
83
|
+
status text not null default 'queued',
|
|
84
|
+
started_at timestamptz,
|
|
85
|
+
finished_at timestamptz,
|
|
86
|
+
source_version bigint not null default 1,
|
|
87
|
+
primary key (operation_id, source_type, source_id),
|
|
88
|
+
constraint operation_source_type_check check (source_type in (
|
|
89
|
+
'audit-action',
|
|
90
|
+
'export-job',
|
|
91
|
+
'sync',
|
|
92
|
+
'webhook-delivery'
|
|
93
|
+
)),
|
|
94
|
+
constraint operation_source_status_check
|
|
95
|
+
check (status in ('queued', 'running', 'completed', 'failed')),
|
|
96
|
+
constraint operation_source_finished_check check (
|
|
97
|
+
(status in ('completed', 'failed') and finished_at is not null)
|
|
98
|
+
or (status in ('queued', 'running') and finished_at is null)
|
|
99
|
+
),
|
|
100
|
+
constraint operation_source_version_check check (source_version > 0),
|
|
101
|
+
constraint operation_source_unique unique (source_type, source_id)
|
|
102
|
+
)
|
|
103
|
+
`.execute(db);
|
|
104
|
+
await sql`
|
|
105
|
+
create index operation_source_operation_idx
|
|
106
|
+
on core.operation_source (operation_id, required, status)
|
|
107
|
+
`.execute(db);
|
|
108
|
+
|
|
109
|
+
await sql`
|
|
110
|
+
create table core.operation_resource (
|
|
111
|
+
operation_id uuid not null references core.operation(id) on delete cascade,
|
|
112
|
+
resource_type text not null,
|
|
113
|
+
resource_id uuid not null,
|
|
114
|
+
role text not null,
|
|
115
|
+
is_primary boolean not null default false,
|
|
116
|
+
created_at timestamptz not null default now(),
|
|
117
|
+
primary key (operation_id, resource_type, resource_id, role),
|
|
118
|
+
constraint operation_resource_type_check check (resource_type in (
|
|
119
|
+
'bank-rule',
|
|
120
|
+
'connection',
|
|
121
|
+
'contact',
|
|
122
|
+
'file',
|
|
123
|
+
'flow',
|
|
124
|
+
'listing',
|
|
125
|
+
'recurring-transaction-template',
|
|
126
|
+
'statement',
|
|
127
|
+
'team',
|
|
128
|
+
'transaction',
|
|
129
|
+
'webhook'
|
|
130
|
+
)),
|
|
131
|
+
constraint operation_resource_role_check
|
|
132
|
+
check (role in ('source', 'target', 'result'))
|
|
133
|
+
)
|
|
134
|
+
`.execute(db);
|
|
135
|
+
await sql`
|
|
136
|
+
create unique index operation_resource_primary_idx
|
|
137
|
+
on core.operation_resource (operation_id)
|
|
138
|
+
where is_primary
|
|
139
|
+
`.execute(db);
|
|
140
|
+
await sql`
|
|
141
|
+
create index operation_resource_lookup_idx
|
|
142
|
+
on core.operation_resource (
|
|
143
|
+
resource_type,
|
|
144
|
+
resource_id,
|
|
145
|
+
operation_id,
|
|
146
|
+
role
|
|
147
|
+
)
|
|
148
|
+
`.execute(db);
|
|
149
|
+
|
|
150
|
+
await sql`
|
|
151
|
+
create table core.operation_projection_outbox (
|
|
152
|
+
id uuid primary key default gen_random_uuid(),
|
|
153
|
+
operation_id uuid not null references core.operation(id) on delete cascade,
|
|
154
|
+
source_version bigint not null,
|
|
155
|
+
attempts integer not null default 0,
|
|
156
|
+
available_at timestamptz not null default now(),
|
|
157
|
+
published_at timestamptz,
|
|
158
|
+
last_error text,
|
|
159
|
+
created_at timestamptz not null default now(),
|
|
160
|
+
constraint operation_projection_outbox_version_check
|
|
161
|
+
check (source_version > 0),
|
|
162
|
+
constraint operation_projection_outbox_attempts_check
|
|
163
|
+
check (attempts >= 0),
|
|
164
|
+
constraint operation_projection_outbox_unique
|
|
165
|
+
unique (operation_id, source_version)
|
|
166
|
+
)
|
|
167
|
+
`.execute(db);
|
|
168
|
+
await sql`
|
|
169
|
+
create index operation_projection_outbox_pending_idx
|
|
170
|
+
on core.operation_projection_outbox (available_at, created_at, id)
|
|
171
|
+
where published_at is null
|
|
172
|
+
`.execute(db);
|
|
173
|
+
|
|
174
|
+
await sql`
|
|
175
|
+
create table core.csv_operation_result (
|
|
176
|
+
sync_id uuid primary key references core.sync(id) on delete cascade,
|
|
177
|
+
tenant_id uuid not null,
|
|
178
|
+
connection_id uuid not null,
|
|
179
|
+
kind text not null,
|
|
180
|
+
status text not null default 'queued',
|
|
181
|
+
result jsonb,
|
|
182
|
+
error_message text,
|
|
183
|
+
created_at timestamptz not null default now(),
|
|
184
|
+
updated_at timestamptz not null default now(),
|
|
185
|
+
constraint csv_operation_result_kind_check
|
|
186
|
+
check (kind in ('import', 'preview')),
|
|
187
|
+
constraint csv_operation_result_status_check
|
|
188
|
+
check (status in ('queued', 'processing', 'completed', 'failed')),
|
|
189
|
+
constraint csv_operation_result_terminal_check check (
|
|
190
|
+
(status = 'completed' and result is not null and error_message is null)
|
|
191
|
+
or (status = 'failed' and error_message is not null)
|
|
192
|
+
or (status in ('queued', 'processing') and result is null and error_message is null)
|
|
193
|
+
)
|
|
194
|
+
)
|
|
195
|
+
`.execute(db);
|
|
196
|
+
await sql`
|
|
197
|
+
create index csv_operation_result_tenant_connection_idx
|
|
198
|
+
on core.csv_operation_result (tenant_id, connection_id, created_at desc)
|
|
199
|
+
`.execute(db);
|
|
200
|
+
|
|
201
|
+
await sql`
|
|
202
|
+
create table core.async_operation_result (
|
|
203
|
+
operation_id uuid primary key references core.operation(id) on delete cascade,
|
|
204
|
+
tenant_id uuid not null,
|
|
205
|
+
type text not null,
|
|
206
|
+
status text not null default 'queued',
|
|
207
|
+
result jsonb,
|
|
208
|
+
error_message text,
|
|
209
|
+
created_at timestamptz not null default now(),
|
|
210
|
+
updated_at timestamptz not null default now(),
|
|
211
|
+
constraint async_operation_result_status_check
|
|
212
|
+
check (status in ('queued', 'completed', 'failed')),
|
|
213
|
+
constraint async_operation_result_terminal_check check (
|
|
214
|
+
(status = 'completed' and result is not null and error_message is null)
|
|
215
|
+
or (status = 'failed' and error_message is not null)
|
|
216
|
+
or (status = 'queued' and result is null and error_message is null)
|
|
217
|
+
)
|
|
218
|
+
)
|
|
219
|
+
`.execute(db);
|
|
220
|
+
await sql`
|
|
221
|
+
create index async_operation_result_tenant_type_idx
|
|
222
|
+
on core.async_operation_result (tenant_id, type, created_at desc)
|
|
223
|
+
`.execute(db);
|
|
224
|
+
|
|
225
|
+
await sql`
|
|
226
|
+
create table core.export_job (
|
|
227
|
+
id uuid primary key,
|
|
228
|
+
tenant_id uuid not null,
|
|
229
|
+
task_id text not null,
|
|
230
|
+
payload jsonb not null,
|
|
231
|
+
filename text not null,
|
|
232
|
+
format text not null,
|
|
233
|
+
file_id uuid not null,
|
|
234
|
+
status text not null default 'queued',
|
|
235
|
+
error_message text,
|
|
236
|
+
created_at timestamptz not null default now(),
|
|
237
|
+
started_at timestamptz,
|
|
238
|
+
finished_at timestamptz,
|
|
239
|
+
updated_at timestamptz not null default now(),
|
|
240
|
+
constraint export_job_format_check check (format in ('csv', 'pdf', 'zip')),
|
|
241
|
+
constraint export_job_status_check
|
|
242
|
+
check (status in ('queued', 'running', 'completed', 'failed')),
|
|
243
|
+
constraint export_job_terminal_check check (
|
|
244
|
+
(status = 'completed' and finished_at is not null and error_message is null)
|
|
245
|
+
or (status = 'failed' and finished_at is not null and error_message is not null)
|
|
246
|
+
or (status in ('queued', 'running') and finished_at is null and error_message is null)
|
|
247
|
+
)
|
|
248
|
+
)
|
|
249
|
+
`.execute(db);
|
|
250
|
+
await sql`
|
|
251
|
+
create index export_job_tenant_history_idx
|
|
252
|
+
on core.export_job (tenant_id, created_at desc, id desc)
|
|
253
|
+
`.execute(db);
|
|
254
|
+
|
|
255
|
+
await sql`
|
|
256
|
+
create function core.refresh_operation_state(target_operation_id uuid)
|
|
257
|
+
returns void
|
|
258
|
+
language plpgsql
|
|
259
|
+
as $$
|
|
260
|
+
declare
|
|
261
|
+
next_status text;
|
|
262
|
+
next_started_at timestamptz;
|
|
263
|
+
next_finished_at timestamptz;
|
|
264
|
+
begin
|
|
265
|
+
select
|
|
266
|
+
case
|
|
267
|
+
when count(*) filter (where required) = 0 then 'queued'
|
|
268
|
+
when bool_and(status = 'completed') filter (where required)
|
|
269
|
+
then 'completed'
|
|
270
|
+
when bool_and(status in ('completed', 'failed')) filter (where required)
|
|
271
|
+
then 'failed'
|
|
272
|
+
when bool_or(status = 'running') filter (where required)
|
|
273
|
+
or bool_or(status in ('completed', 'failed')) filter (where required)
|
|
274
|
+
then 'running'
|
|
275
|
+
else 'queued'
|
|
276
|
+
end,
|
|
277
|
+
min(started_at) filter (where required),
|
|
278
|
+
max(finished_at) filter (where required)
|
|
279
|
+
into next_status, next_started_at, next_finished_at
|
|
280
|
+
from core.operation_source
|
|
281
|
+
where operation_id = target_operation_id;
|
|
282
|
+
|
|
283
|
+
update core.operation
|
|
284
|
+
set
|
|
285
|
+
status = next_status,
|
|
286
|
+
started_at = coalesce(core.operation.started_at, next_started_at),
|
|
287
|
+
finished_at = case
|
|
288
|
+
when next_status in ('completed', 'failed')
|
|
289
|
+
then coalesce(next_finished_at, now())
|
|
290
|
+
else null
|
|
291
|
+
end,
|
|
292
|
+
failure_code = case when next_status = 'failed' then 'operation_failed' end,
|
|
293
|
+
failure_message = case when next_status = 'failed' then 'Operation failed' end,
|
|
294
|
+
updated_at = now(),
|
|
295
|
+
source_version = source_version + 1
|
|
296
|
+
where id = target_operation_id
|
|
297
|
+
and (
|
|
298
|
+
status is distinct from next_status
|
|
299
|
+
or started_at is distinct from coalesce(started_at, next_started_at)
|
|
300
|
+
or finished_at is distinct from case
|
|
301
|
+
when next_status in ('completed', 'failed')
|
|
302
|
+
then coalesce(next_finished_at, finished_at, now())
|
|
303
|
+
else null
|
|
304
|
+
end
|
|
305
|
+
);
|
|
306
|
+
end;
|
|
307
|
+
$$
|
|
308
|
+
`.execute(db);
|
|
309
|
+
|
|
310
|
+
await sql`
|
|
311
|
+
create function core.refresh_audit_action_source(target_action_id uuid)
|
|
312
|
+
returns void
|
|
313
|
+
language plpgsql
|
|
314
|
+
as $$
|
|
315
|
+
declare
|
|
316
|
+
target_operation_id uuid;
|
|
317
|
+
begin
|
|
318
|
+
update core.operation_source as source
|
|
319
|
+
set
|
|
320
|
+
status = state.status,
|
|
321
|
+
started_at = state.started_at,
|
|
322
|
+
finished_at = state.finished_at,
|
|
323
|
+
source_version = source.source_version + 1
|
|
324
|
+
from (
|
|
325
|
+
select
|
|
326
|
+
case
|
|
327
|
+
when count(*) = 0 then 'completed'
|
|
328
|
+
when bool_and(effect.status = 'completed') then 'completed'
|
|
329
|
+
when bool_and(
|
|
330
|
+
effect.status in ('completed', 'dead_letter')
|
|
331
|
+
or (
|
|
332
|
+
effect.status = 'failed'
|
|
333
|
+
and effect.retry_count >= effect.max_retries
|
|
334
|
+
)
|
|
335
|
+
) then 'failed'
|
|
336
|
+
when bool_or(effect.status in ('claimed', 'running'))
|
|
337
|
+
or bool_or(
|
|
338
|
+
effect.status in ('completed', 'dead_letter')
|
|
339
|
+
or (
|
|
340
|
+
effect.status = 'failed'
|
|
341
|
+
and effect.retry_count >= effect.max_retries
|
|
342
|
+
)
|
|
343
|
+
) then 'running'
|
|
344
|
+
else 'queued'
|
|
345
|
+
end as status,
|
|
346
|
+
case
|
|
347
|
+
when count(*) = 0 then now()
|
|
348
|
+
else min(coalesce(effect.last_attempt_at, effect.claimed_at))
|
|
349
|
+
end as started_at,
|
|
350
|
+
case
|
|
351
|
+
when count(*) = 0 then now()
|
|
352
|
+
when bool_and(
|
|
353
|
+
effect.status in ('completed', 'dead_letter')
|
|
354
|
+
or (
|
|
355
|
+
effect.status = 'failed'
|
|
356
|
+
and effect.retry_count >= effect.max_retries
|
|
357
|
+
)
|
|
358
|
+
) then coalesce(
|
|
359
|
+
max(coalesce(effect.completed_at, effect.last_attempt_at)),
|
|
360
|
+
now()
|
|
361
|
+
)
|
|
362
|
+
end as finished_at
|
|
363
|
+
from audit.effect
|
|
364
|
+
where action_id = target_action_id
|
|
365
|
+
) as state
|
|
366
|
+
where source.source_type = 'audit-action'
|
|
367
|
+
and source.source_id = target_action_id
|
|
368
|
+
and (
|
|
369
|
+
source.status is distinct from state.status
|
|
370
|
+
or source.started_at is distinct from state.started_at
|
|
371
|
+
or source.finished_at is distinct from state.finished_at
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
for target_operation_id in
|
|
375
|
+
select operation_id
|
|
376
|
+
from core.operation_source
|
|
377
|
+
where source_type = 'audit-action'
|
|
378
|
+
and source_id = target_action_id
|
|
379
|
+
loop
|
|
380
|
+
perform core.refresh_operation_state(target_operation_id);
|
|
381
|
+
end loop;
|
|
382
|
+
end;
|
|
383
|
+
$$
|
|
384
|
+
`.execute(db);
|
|
385
|
+
await sql`
|
|
386
|
+
create function core.refresh_audit_action_operation()
|
|
387
|
+
returns trigger
|
|
388
|
+
language plpgsql
|
|
389
|
+
as $$
|
|
390
|
+
begin
|
|
391
|
+
perform core.refresh_audit_action_source(
|
|
392
|
+
coalesce(new.action_id, old.action_id)
|
|
393
|
+
);
|
|
394
|
+
return coalesce(new, old);
|
|
395
|
+
end;
|
|
396
|
+
$$
|
|
397
|
+
`.execute(db);
|
|
398
|
+
await sql`
|
|
399
|
+
create trigger refresh_audit_action_operation
|
|
400
|
+
after insert or update of status, retry_count, claimed_at, completed_at, last_attempt_at
|
|
401
|
+
or delete on audit.effect
|
|
402
|
+
for each row execute function core.refresh_audit_action_operation()
|
|
403
|
+
`.execute(db);
|
|
404
|
+
|
|
405
|
+
await sql`
|
|
406
|
+
create function core.refresh_sync_operation()
|
|
407
|
+
returns trigger
|
|
408
|
+
language plpgsql
|
|
409
|
+
as $$
|
|
410
|
+
declare
|
|
411
|
+
target_operation_id uuid;
|
|
412
|
+
begin
|
|
413
|
+
update core.operation_source as source
|
|
414
|
+
set
|
|
415
|
+
status = case
|
|
416
|
+
when new.status = 'started' then 'running'
|
|
417
|
+
when new.status = 'completed' then 'completed'
|
|
418
|
+
when new.status in ('failed', 'canceled') then 'failed'
|
|
419
|
+
else 'queued'
|
|
420
|
+
end,
|
|
421
|
+
started_at = case
|
|
422
|
+
when new.status = 'started'
|
|
423
|
+
then coalesce(source.started_at, new.updated_at)
|
|
424
|
+
else source.started_at
|
|
425
|
+
end,
|
|
426
|
+
finished_at = case
|
|
427
|
+
when new.status in ('completed', 'failed', 'canceled')
|
|
428
|
+
then coalesce(new.synced_at, new.updated_at, now())
|
|
429
|
+
end,
|
|
430
|
+
source_version = source.source_version + 1
|
|
431
|
+
where source.source_type = 'sync'
|
|
432
|
+
and source.source_id = new.id;
|
|
433
|
+
|
|
434
|
+
for target_operation_id in
|
|
435
|
+
select operation_id
|
|
436
|
+
from core.operation_source
|
|
437
|
+
where source_type = 'sync' and source_id = new.id
|
|
438
|
+
loop
|
|
439
|
+
perform core.refresh_operation_state(target_operation_id);
|
|
440
|
+
end loop;
|
|
441
|
+
return new;
|
|
442
|
+
end;
|
|
443
|
+
$$
|
|
444
|
+
`.execute(db);
|
|
445
|
+
await sql`
|
|
446
|
+
create trigger refresh_sync_operation
|
|
447
|
+
after insert or update of status, synced_at on core.sync
|
|
448
|
+
for each row execute function core.refresh_sync_operation()
|
|
449
|
+
`.execute(db);
|
|
450
|
+
|
|
451
|
+
await sql`
|
|
452
|
+
create function core.refresh_export_job_operation()
|
|
453
|
+
returns trigger
|
|
454
|
+
language plpgsql
|
|
455
|
+
as $$
|
|
456
|
+
declare
|
|
457
|
+
target_operation_id uuid;
|
|
458
|
+
begin
|
|
459
|
+
update core.operation_source as source
|
|
460
|
+
set
|
|
461
|
+
status = new.status,
|
|
462
|
+
started_at = coalesce(source.started_at, new.started_at),
|
|
463
|
+
finished_at = new.finished_at,
|
|
464
|
+
source_version = source.source_version + 1
|
|
465
|
+
where source.source_type = 'export-job'
|
|
466
|
+
and source.source_id = new.id;
|
|
467
|
+
|
|
468
|
+
for target_operation_id in
|
|
469
|
+
select operation_id
|
|
470
|
+
from core.operation_source
|
|
471
|
+
where source_type = 'export-job' and source_id = new.id
|
|
472
|
+
loop
|
|
473
|
+
perform core.refresh_operation_state(target_operation_id);
|
|
474
|
+
end loop;
|
|
475
|
+
return new;
|
|
476
|
+
end;
|
|
477
|
+
$$
|
|
478
|
+
`.execute(db);
|
|
479
|
+
await sql`
|
|
480
|
+
create trigger refresh_export_job_operation
|
|
481
|
+
after insert or update of status, started_at, finished_at
|
|
482
|
+
on core.export_job
|
|
483
|
+
for each row execute function core.refresh_export_job_operation()
|
|
484
|
+
`.execute(db);
|
|
485
|
+
|
|
486
|
+
await sql`
|
|
487
|
+
create function core.refresh_webhook_delivery_operation()
|
|
488
|
+
returns trigger
|
|
489
|
+
language plpgsql
|
|
490
|
+
as $$
|
|
491
|
+
declare
|
|
492
|
+
target_operation_id uuid;
|
|
493
|
+
begin
|
|
494
|
+
update core.operation_source as source
|
|
495
|
+
set
|
|
496
|
+
status = case
|
|
497
|
+
when new.status in ('queued', 'retrying') then 'queued'
|
|
498
|
+
when new.status = 'delivering' then 'running'
|
|
499
|
+
when new.status = 'succeeded' then 'completed'
|
|
500
|
+
else 'failed'
|
|
501
|
+
end,
|
|
502
|
+
started_at = case
|
|
503
|
+
when new.status = 'delivering'
|
|
504
|
+
then coalesce(source.started_at, new.claimed_at, new.updated_at)
|
|
505
|
+
else source.started_at
|
|
506
|
+
end,
|
|
507
|
+
finished_at = case
|
|
508
|
+
when new.status = 'succeeded'
|
|
509
|
+
then coalesce(new.delivered_at, new.updated_at, now())
|
|
510
|
+
when new.status in ('failed', 'canceled')
|
|
511
|
+
then coalesce(new.failed_at, new.updated_at, now())
|
|
512
|
+
end,
|
|
513
|
+
source_version = source.source_version + 1
|
|
514
|
+
where source.source_type = 'webhook-delivery'
|
|
515
|
+
and source.source_id = new.id;
|
|
516
|
+
|
|
517
|
+
for target_operation_id in
|
|
518
|
+
select operation_id
|
|
519
|
+
from core.operation_source
|
|
520
|
+
where source_type = 'webhook-delivery' and source_id = new.id
|
|
521
|
+
loop
|
|
522
|
+
perform core.refresh_operation_state(target_operation_id);
|
|
523
|
+
end loop;
|
|
524
|
+
return new;
|
|
525
|
+
end;
|
|
526
|
+
$$
|
|
527
|
+
`.execute(db);
|
|
528
|
+
await sql`
|
|
529
|
+
create trigger refresh_webhook_delivery_operation
|
|
530
|
+
after insert or update of status, claimed_at, delivered_at, failed_at
|
|
531
|
+
on webhook.delivery
|
|
532
|
+
for each row execute function core.refresh_webhook_delivery_operation()
|
|
533
|
+
`.execute(db);
|
|
534
|
+
|
|
535
|
+
await sql`
|
|
536
|
+
create function core.queue_operation_projection()
|
|
537
|
+
returns trigger
|
|
538
|
+
language plpgsql
|
|
539
|
+
as $$
|
|
540
|
+
begin
|
|
541
|
+
if exists (
|
|
542
|
+
select 1
|
|
543
|
+
from core.operation_resource
|
|
544
|
+
where operation_id = new.id and resource_type = 'team'
|
|
545
|
+
) then
|
|
546
|
+
insert into core.operation_projection_outbox (
|
|
547
|
+
operation_id,
|
|
548
|
+
source_version
|
|
549
|
+
) values (new.id, new.source_version)
|
|
550
|
+
on conflict (operation_id, source_version) do nothing;
|
|
551
|
+
end if;
|
|
552
|
+
return new;
|
|
553
|
+
end;
|
|
554
|
+
$$
|
|
555
|
+
`.execute(db);
|
|
556
|
+
await sql`
|
|
557
|
+
create trigger queue_operation_projection
|
|
558
|
+
after insert or update of source_version on core.operation
|
|
559
|
+
for each row execute function core.queue_operation_projection()
|
|
560
|
+
`.execute(db);
|
|
561
|
+
|
|
562
|
+
await sql`
|
|
563
|
+
create function core.operation_resource_changed()
|
|
564
|
+
returns trigger
|
|
565
|
+
language plpgsql
|
|
566
|
+
as $$
|
|
567
|
+
declare
|
|
568
|
+
target_operation_id uuid;
|
|
569
|
+
begin
|
|
570
|
+
target_operation_id := coalesce(new.operation_id, old.operation_id);
|
|
571
|
+
update core.operation
|
|
572
|
+
set updated_at = now(), source_version = source_version + 1
|
|
573
|
+
where id = target_operation_id;
|
|
574
|
+
return coalesce(new, old);
|
|
575
|
+
end;
|
|
576
|
+
$$
|
|
577
|
+
`.execute(db);
|
|
578
|
+
await sql`
|
|
579
|
+
create trigger operation_resource_changed
|
|
580
|
+
after insert or update or delete on core.operation_resource
|
|
581
|
+
for each row execute function core.operation_resource_changed()
|
|
582
|
+
`.execute(db);
|
|
583
|
+
|
|
584
|
+
await sql`
|
|
585
|
+
insert into core.operation (
|
|
586
|
+
id,
|
|
587
|
+
tenant_id,
|
|
588
|
+
initiated_by_user_id,
|
|
589
|
+
type,
|
|
590
|
+
status,
|
|
591
|
+
created_at,
|
|
592
|
+
updated_at
|
|
593
|
+
)
|
|
594
|
+
select
|
|
595
|
+
action.id,
|
|
596
|
+
action.tenant_id,
|
|
597
|
+
action.actor_id,
|
|
598
|
+
'vri-statement-import',
|
|
599
|
+
'queued',
|
|
600
|
+
action.created_at,
|
|
601
|
+
action.created_at
|
|
602
|
+
from audit.action as action
|
|
603
|
+
where action.action_type = 'partner.vri_to_vrt.statements.create'
|
|
604
|
+
and exists (
|
|
605
|
+
select 1 from audit.effect where action_id = action.id
|
|
606
|
+
)
|
|
607
|
+
on conflict (id) do nothing
|
|
608
|
+
`.execute(db);
|
|
609
|
+
await sql`
|
|
610
|
+
insert into core.operation_source (
|
|
611
|
+
operation_id,
|
|
612
|
+
source_type,
|
|
613
|
+
source_id
|
|
614
|
+
)
|
|
615
|
+
select id, 'audit-action', id
|
|
616
|
+
from core.operation
|
|
617
|
+
where type = 'vri-statement-import'
|
|
618
|
+
on conflict (source_type, source_id) do nothing
|
|
619
|
+
`.execute(db);
|
|
620
|
+
await sql`
|
|
621
|
+
insert into core.operation_resource (
|
|
622
|
+
operation_id,
|
|
623
|
+
resource_type,
|
|
624
|
+
resource_id,
|
|
625
|
+
role,
|
|
626
|
+
is_primary
|
|
627
|
+
)
|
|
628
|
+
select
|
|
629
|
+
operation.id,
|
|
630
|
+
'team',
|
|
631
|
+
coalesce(action.root_entity_id, effect.entity_id),
|
|
632
|
+
'target',
|
|
633
|
+
true
|
|
634
|
+
from core.operation as operation
|
|
635
|
+
inner join audit.action as action on action.id = operation.id
|
|
636
|
+
left join lateral (
|
|
637
|
+
select entity_id
|
|
638
|
+
from audit.effect
|
|
639
|
+
where action_id = action.id and entity_id is not null
|
|
640
|
+
order by created_at, id
|
|
641
|
+
limit 1
|
|
642
|
+
) as effect on true
|
|
643
|
+
where operation.type = 'vri-statement-import'
|
|
644
|
+
and coalesce(action.root_entity_id, effect.entity_id) is not null
|
|
645
|
+
on conflict do nothing
|
|
646
|
+
`.execute(db);
|
|
647
|
+
await sql`
|
|
648
|
+
do $$
|
|
649
|
+
declare operation_row record;
|
|
650
|
+
begin
|
|
651
|
+
for operation_row in
|
|
652
|
+
select id from core.operation where type = 'vri-statement-import'
|
|
653
|
+
loop
|
|
654
|
+
update audit.effect set status = status
|
|
655
|
+
where action_id = operation_row.id
|
|
656
|
+
and id = (
|
|
657
|
+
select id from audit.effect
|
|
658
|
+
where action_id = operation_row.id
|
|
659
|
+
order by created_at, id
|
|
660
|
+
limit 1
|
|
661
|
+
);
|
|
662
|
+
end loop;
|
|
663
|
+
end;
|
|
664
|
+
$$
|
|
665
|
+
`.execute(db);
|
|
666
|
+
|
|
667
|
+
await sql`
|
|
668
|
+
do $$
|
|
669
|
+
begin
|
|
670
|
+
if exists (select 1 from pg_roles where rolname = 'application') then
|
|
671
|
+
grant usage on schema core to application;
|
|
672
|
+
grant select, insert, update, delete
|
|
673
|
+
on core.operation,
|
|
674
|
+
core.operation_source,
|
|
675
|
+
core.operation_resource,
|
|
676
|
+
core.operation_projection_outbox,
|
|
677
|
+
core.csv_operation_result,
|
|
678
|
+
core.async_operation_result,
|
|
679
|
+
core.export_job
|
|
680
|
+
to application;
|
|
681
|
+
grant execute on function core.refresh_operation_state(uuid),
|
|
682
|
+
core.refresh_audit_action_source(uuid)
|
|
683
|
+
to application;
|
|
684
|
+
end if;
|
|
685
|
+
end
|
|
686
|
+
$$
|
|
687
|
+
`.execute(db);
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
export async function down(db: Kysely<unknown>): Promise<void> {
|
|
691
|
+
await sql`drop trigger if exists operation_resource_changed on core.operation_resource`.execute(
|
|
692
|
+
db
|
|
693
|
+
);
|
|
694
|
+
await sql`drop function if exists core.operation_resource_changed`.execute(
|
|
695
|
+
db
|
|
696
|
+
);
|
|
697
|
+
await sql`drop trigger if exists queue_operation_projection on core.operation`.execute(
|
|
698
|
+
db
|
|
699
|
+
);
|
|
700
|
+
await sql`drop function if exists core.queue_operation_projection`.execute(
|
|
701
|
+
db
|
|
702
|
+
);
|
|
703
|
+
await sql`drop trigger if exists refresh_webhook_delivery_operation on webhook.delivery`.execute(
|
|
704
|
+
db
|
|
705
|
+
);
|
|
706
|
+
await sql`drop function if exists core.refresh_webhook_delivery_operation`.execute(
|
|
707
|
+
db
|
|
708
|
+
);
|
|
709
|
+
await sql`drop trigger if exists refresh_export_job_operation on core.export_job`.execute(
|
|
710
|
+
db
|
|
711
|
+
);
|
|
712
|
+
await sql`drop function if exists core.refresh_export_job_operation`.execute(
|
|
713
|
+
db
|
|
714
|
+
);
|
|
715
|
+
await sql`drop trigger if exists refresh_sync_operation on core.sync`.execute(
|
|
716
|
+
db
|
|
717
|
+
);
|
|
718
|
+
await sql`drop function if exists core.refresh_sync_operation`.execute(db);
|
|
719
|
+
await sql`drop trigger if exists refresh_audit_action_operation on audit.effect`.execute(
|
|
720
|
+
db
|
|
721
|
+
);
|
|
722
|
+
await sql`drop function if exists core.refresh_audit_action_operation`.execute(
|
|
723
|
+
db
|
|
724
|
+
);
|
|
725
|
+
await sql`drop function if exists core.refresh_audit_action_source`.execute(
|
|
726
|
+
db
|
|
727
|
+
);
|
|
728
|
+
await sql`drop function if exists core.refresh_operation_state`.execute(db);
|
|
729
|
+
await sql`drop table if exists core.async_operation_result`.execute(db);
|
|
730
|
+
await sql`drop table if exists core.export_job`.execute(db);
|
|
731
|
+
await sql`drop table if exists core.csv_operation_result`.execute(db);
|
|
732
|
+
await sql`drop table if exists core.operation_projection_outbox`.execute(db);
|
|
733
|
+
await sql`drop table if exists core.operation_resource`.execute(db);
|
|
734
|
+
await sql`drop table if exists core.operation_source`.execute(db);
|
|
735
|
+
await sql`drop table if exists core.operation`.execute(db);
|
|
736
|
+
}
|