@vrplatform/kysely 1.3.45-stage.6066 → 1.3.45-stage.607
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/.data.tar +0 -0
- package/build/main/control-plane/index.d.ts +312 -0
- package/build/main/control-plane/index.js.map +1 -1
- package/build/main/index.d.ts +13 -2
- package/build/main/index.js +217 -23
- package/build/main/index.js.map +1 -1
- package/build/main/local/generate.js +2 -0
- package/build/main/local/generate.js.map +1 -1
- package/build/main/local/index.js +110 -8
- package/build/main/local/index.js.map +1 -1
- package/build/main/postgres-error.d.ts +3 -3
- package/build/main/postgres-error.js +4 -3
- package/build/main/postgres-error.js.map +1 -1
- package/build/main/query/listing/status.js +2 -6
- package/build/main/query/listing/status.js.map +1 -1
- package/build/main/query/tenant/status.d.ts +1 -1
- package/build/main/query/tenant/status.js +6 -4
- package/build/main/query/tenant/status.js.map +1 -1
- package/build/main/regional.d.ts +1 -0
- package/build/main/regional.js +1 -0
- package/build/main/regional.js.map +1 -1
- package/build/main/test/index.d.ts +23 -3
- package/build/main/test/index.js +35 -13
- package/build/main/test/index.js.map +1 -1
- package/build/main/v1.generated.d.ts +560 -8
- package/build/main/v1.generated.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/0000000000016-managed-team-access.ts +34 -0
- package/build/migrations-v2/0000000000017-drop-trial-until.ts +42 -0
- package/build/migrations-v2/0000000000017-webhook-secret-reencryption-failures.ts +18 -0
- package/build/migrations-v2/0000000000018-accounting-integrity-monitoring.ts +276 -0
- package/build/migrations-v2/0000000000018-durable-flow-event-sensitive-input.ts +28 -0
- package/build/migrations-v2/0000000000019-accounting-integrity-run-shards.ts +26 -0
- package/build/migrations-v2/0000000000019-asynchronous-operations.ts +736 -0
- package/build/migrations-v2/0000000000020-accounting-integrity-case-classification.ts +52 -0
- package/build/migrations-v2/0000000000021-payment-line-classification-monitor-indexes.ts +31 -0
- package/build/migrations-v2/0000000000022-accounting-integrity-controlled-repairs.ts +281 -0
- package/build/migrations-v2/0000000000023-line-classification-usage.ts +325 -0
- package/build/migrations-v2/0000000000024-tenant-region-migration.ts +108 -0
- package/build/migrations-v2/0000000000025-partner-ach-denial-default.ts +30 -0
- package/build/migrations-v2/0000000000026-line-classification-usage-capture.ts +399 -0
- package/build/migrations-v2/0000000000027-payout-replacement-chain.ts +27 -0
- package/build/migrations-v2/0000000000028-accounting-integrity-operator-workflow.ts +108 -0
- package/build/migrations-v2/0000000000029-accounting-integrity-definition-artifacts.ts +40 -0
- package/build/migrations-v2/0000000000030-accounting-integrity-review-metadata-compatibility.ts +49 -0
- package/build/migrations-v2/0000000000031-ui-permission-role-simplification.ts +70 -0
- package/build/migrations-v2/0000000000032-tenant-delete-ownership-cascades.ts +44 -0
- package/build/migrations-v2/0000000000033-reservation-issue-seed-bucket-index.ts +19 -0
- package/build/migrations-v2/0000000000034-retire-recent-classification-indexes.ts +29 -0
- package/build/migrations-v2/0000000000035-core-change-autovacuum.ts +24 -0
- package/build/migrations-v2/0000000000036-journal-entry-inactive-reason-constraint.ts +33 -0
- package/build/migrations-v2/0000000000037-journal-entry-inactive-reasons.ts +19 -0
- package/build/migrations-v2/0000000000038-journal-entry-group-inactive-reason.ts +14 -0
- package/build/migrations-v2/0000000000039-reservation-guest-breakdown.ts +19 -0
- package/build/migrations-v2/0000000000040-csv-import-source-identity-index.ts +23 -0
- package/build/migrations-v2/0000000000041-payment-returns.ts +38 -0
- package/build/migrations-v2/0000000000042-operation-resource-types.ts +609 -0
- package/build/migrations-v2/0000000000043-reservation-journal-freshness.ts +149 -0
- package/build/migrations-v2/0000000000044-reservation-journal-freshness-backfill.ts +254 -0
- package/build/migrations-v2/0000000000045-reservation-journal-freshness-revision-repair.ts +83 -0
- package/build/migrations-v2/0000000000046-materialized-reservation-journal-operations.ts +709 -0
- package/build/migrations-v2/0000000000047-public-audit-completion.ts +77 -0
- package/build/migrations-v2/0000000000048-reservation-journal-full-refresh-repair.ts +136 -0
- package/build/migrations-v2/0000000000049-queued-journal-operation-failures.ts +60 -0
- package/build/migrations-v2/0000000000050-connection-oauth-state.ts +43 -0
- package/build/migrations-v2/0000000000051-owner-statement-email-delivery.ts +134 -0
- package/build/migrations-v2/0000000000052-fee-journal-lock-skip-operations.ts +59 -0
- package/build/migrations-v2/0000000000053-fee-journal-lock-skip-null-attempt.ts +44 -0
- package/build/migrations-v2/0000000000054-journal-refresh-issue-codes.ts +16 -0
- package/build/migrations-v2/0000000000055-tenant-billing-subscription.ts +8 -0
- package/build/module/control-plane/index.d.ts +312 -0
- package/build/module/control-plane/index.js.map +1 -1
- package/build/module/index.d.ts +13 -2
- package/build/module/index.js +219 -24
- package/build/module/index.js.map +1 -1
- package/build/module/local/generate.js +2 -0
- package/build/module/local/generate.js.map +1 -1
- package/build/module/local/index.js +110 -8
- package/build/module/local/index.js.map +1 -1
- package/build/module/postgres-error.d.ts +3 -3
- package/build/module/postgres-error.js +4 -3
- package/build/module/postgres-error.js.map +1 -1
- package/build/module/query/listing/status.js +2 -6
- package/build/module/query/listing/status.js.map +1 -1
- package/build/module/query/tenant/status.d.ts +1 -1
- package/build/module/query/tenant/status.js +6 -4
- package/build/module/query/tenant/status.js.map +1 -1
- package/build/module/regional.d.ts +1 -0
- package/build/module/regional.js +1 -0
- package/build/module/regional.js.map +1 -1
- package/build/module/test/index.d.ts +23 -3
- package/build/module/test/index.js +34 -13
- package/build/module/test/index.js.map +1 -1
- package/build/module/v1.generated.d.ts +560 -8
- package/build/module/v1.generated.js.map +1 -1
- package/package.json +4 -2
|
@@ -0,0 +1,709 @@
|
|
|
1
|
+
import type { Kysely } from 'kysely';
|
|
2
|
+
import { sql } from 'kysely';
|
|
3
|
+
|
|
4
|
+
async function replaceOperationRefreshFunctions(db: Kysely<unknown>) {
|
|
5
|
+
await sql`
|
|
6
|
+
create or replace function core.refresh_operation_state(
|
|
7
|
+
target_operation_id uuid
|
|
8
|
+
) returns void
|
|
9
|
+
language plpgsql
|
|
10
|
+
as $$
|
|
11
|
+
declare
|
|
12
|
+
next_status core.operation_status;
|
|
13
|
+
next_started_at timestamptz;
|
|
14
|
+
next_finished_at timestamptz;
|
|
15
|
+
next_failure_code text;
|
|
16
|
+
next_failure_message text;
|
|
17
|
+
target_operation_type core.operation_type;
|
|
18
|
+
target_public_id_format core.operation_public_id_format;
|
|
19
|
+
target_action_id uuid;
|
|
20
|
+
begin
|
|
21
|
+
select
|
|
22
|
+
operation.type,
|
|
23
|
+
operation.public_id_format,
|
|
24
|
+
source.source_id
|
|
25
|
+
into
|
|
26
|
+
target_operation_type,
|
|
27
|
+
target_public_id_format,
|
|
28
|
+
target_action_id
|
|
29
|
+
from core.operation as operation
|
|
30
|
+
left join core.operation_source as source
|
|
31
|
+
on source.operation_id = operation.id
|
|
32
|
+
and source.source_type = 'audit-action'
|
|
33
|
+
where operation.id = target_operation_id;
|
|
34
|
+
|
|
35
|
+
select
|
|
36
|
+
case
|
|
37
|
+
when count(*) filter (where required) = 0 then 'queued'
|
|
38
|
+
when bool_and(status = 'completed') filter (where required)
|
|
39
|
+
then 'completed'
|
|
40
|
+
when bool_and(status in ('completed', 'failed')) filter (where required)
|
|
41
|
+
then 'failed'
|
|
42
|
+
when bool_or(status = 'running') filter (where required)
|
|
43
|
+
or bool_or(status in ('completed', 'failed')) filter (where required)
|
|
44
|
+
then 'running'
|
|
45
|
+
else 'queued'
|
|
46
|
+
end::core.operation_status,
|
|
47
|
+
min(started_at) filter (where required),
|
|
48
|
+
max(finished_at) filter (where required)
|
|
49
|
+
into next_status, next_started_at, next_finished_at
|
|
50
|
+
from core.operation_source
|
|
51
|
+
where operation_id = target_operation_id;
|
|
52
|
+
|
|
53
|
+
if next_status = 'failed' then
|
|
54
|
+
if target_operation_type = 'reservation-journal-refresh'
|
|
55
|
+
and target_public_id_format = 'action'
|
|
56
|
+
and target_action_id is not null then
|
|
57
|
+
select
|
|
58
|
+
case
|
|
59
|
+
when bool_or(
|
|
60
|
+
invalidation.applied_at is null
|
|
61
|
+
and attempt.result_code = 'journal_unbalanced'
|
|
62
|
+
) then 'journalUnbalanced'
|
|
63
|
+
when bool_or(
|
|
64
|
+
invalidation.applied_at is null
|
|
65
|
+
and attempt.result_code = 'journal_configuration_invalid'
|
|
66
|
+
) then 'journalConfigurationInvalid'
|
|
67
|
+
when bool_or(
|
|
68
|
+
invalidation.applied_at is null
|
|
69
|
+
and attempt.result_code in (
|
|
70
|
+
'statement_period_locked',
|
|
71
|
+
'skipped_locked'
|
|
72
|
+
)
|
|
73
|
+
) then 'journalLocked'
|
|
74
|
+
else 'journalRefreshFailed'
|
|
75
|
+
end,
|
|
76
|
+
case
|
|
77
|
+
when bool_or(
|
|
78
|
+
invalidation.applied_at is null
|
|
79
|
+
and attempt.result_code = 'journal_unbalanced'
|
|
80
|
+
) then 'Journal refresh produced unbalanced entries'
|
|
81
|
+
when bool_or(
|
|
82
|
+
invalidation.applied_at is null
|
|
83
|
+
and attempt.result_code = 'journal_configuration_invalid'
|
|
84
|
+
) then 'Journal configuration is invalid'
|
|
85
|
+
when bool_or(
|
|
86
|
+
invalidation.applied_at is null
|
|
87
|
+
and attempt.result_code in (
|
|
88
|
+
'statement_period_locked',
|
|
89
|
+
'skipped_locked'
|
|
90
|
+
)
|
|
91
|
+
) then 'Journal refresh is blocked by accounting locks'
|
|
92
|
+
else 'Journal refresh failed'
|
|
93
|
+
end
|
|
94
|
+
into next_failure_code, next_failure_message
|
|
95
|
+
from audit.effect as effect
|
|
96
|
+
left join accounting.reservation_journal_invalidation as invalidation
|
|
97
|
+
on invalidation.tenant_id = effect.tenant_id
|
|
98
|
+
and invalidation.effect_id = effect.id
|
|
99
|
+
left join lateral (
|
|
100
|
+
select effect_attempt.result_code
|
|
101
|
+
from audit.effect_attempt as effect_attempt
|
|
102
|
+
where effect_attempt.tenant_id = effect.tenant_id
|
|
103
|
+
and effect_attempt.effect_id = effect.id
|
|
104
|
+
order by effect_attempt.attempt_num desc
|
|
105
|
+
limit 1
|
|
106
|
+
) as attempt on true
|
|
107
|
+
where effect.action_id = target_action_id
|
|
108
|
+
and effect.effect_type = 'REFRESH_RESERVATION_JOURNAL'
|
|
109
|
+
and effect.entity_type = 'reservation';
|
|
110
|
+
else
|
|
111
|
+
next_failure_code := 'operation_failed';
|
|
112
|
+
next_failure_message := 'Operation failed';
|
|
113
|
+
end if;
|
|
114
|
+
else
|
|
115
|
+
next_failure_code := null;
|
|
116
|
+
next_failure_message := null;
|
|
117
|
+
end if;
|
|
118
|
+
|
|
119
|
+
update core.operation
|
|
120
|
+
set
|
|
121
|
+
status = next_status,
|
|
122
|
+
started_at = coalesce(core.operation.started_at, next_started_at),
|
|
123
|
+
finished_at = case
|
|
124
|
+
when next_status in ('completed', 'failed')
|
|
125
|
+
then coalesce(next_finished_at, now())
|
|
126
|
+
else null
|
|
127
|
+
end,
|
|
128
|
+
failure_code = next_failure_code,
|
|
129
|
+
failure_message = next_failure_message,
|
|
130
|
+
updated_at = now(),
|
|
131
|
+
source_version = source_version + 1
|
|
132
|
+
where id = target_operation_id
|
|
133
|
+
and (
|
|
134
|
+
status is distinct from next_status
|
|
135
|
+
or started_at is distinct from coalesce(started_at, next_started_at)
|
|
136
|
+
or finished_at is distinct from case
|
|
137
|
+
when next_status in ('completed', 'failed')
|
|
138
|
+
then coalesce(next_finished_at, finished_at, now())
|
|
139
|
+
else null
|
|
140
|
+
end
|
|
141
|
+
or failure_code is distinct from next_failure_code
|
|
142
|
+
or failure_message is distinct from next_failure_message
|
|
143
|
+
);
|
|
144
|
+
end;
|
|
145
|
+
$$
|
|
146
|
+
`.execute(db);
|
|
147
|
+
|
|
148
|
+
await sql`
|
|
149
|
+
create or replace function core.refresh_audit_action_source(
|
|
150
|
+
target_action_id uuid
|
|
151
|
+
) returns void
|
|
152
|
+
language plpgsql
|
|
153
|
+
as $$
|
|
154
|
+
declare
|
|
155
|
+
operation_row record;
|
|
156
|
+
next_status core.operation_status;
|
|
157
|
+
next_started_at timestamptz;
|
|
158
|
+
next_finished_at timestamptz;
|
|
159
|
+
begin
|
|
160
|
+
for operation_row in
|
|
161
|
+
select
|
|
162
|
+
operation.id,
|
|
163
|
+
operation.type,
|
|
164
|
+
operation.public_id_format
|
|
165
|
+
from core.operation_source as source
|
|
166
|
+
inner join core.operation as operation
|
|
167
|
+
on operation.id = source.operation_id
|
|
168
|
+
where source.source_type = 'audit-action'
|
|
169
|
+
and source.source_id = target_action_id
|
|
170
|
+
loop
|
|
171
|
+
if operation_row.type = 'reservation-journal-refresh' then
|
|
172
|
+
select
|
|
173
|
+
case
|
|
174
|
+
when count(*) = 0 then 'failed'
|
|
175
|
+
when bool_or(
|
|
176
|
+
(
|
|
177
|
+
invalidation.id is null
|
|
178
|
+
or invalidation.applied_at is null
|
|
179
|
+
)
|
|
180
|
+
and effect.status in ('claimed', 'running')
|
|
181
|
+
) then 'running'
|
|
182
|
+
when bool_or(
|
|
183
|
+
(
|
|
184
|
+
invalidation.id is null
|
|
185
|
+
or invalidation.applied_at is null
|
|
186
|
+
)
|
|
187
|
+
and (
|
|
188
|
+
effect.status = 'pending'
|
|
189
|
+
or (
|
|
190
|
+
effect.status = 'failed'
|
|
191
|
+
and effect.retry_count < effect.max_retries
|
|
192
|
+
)
|
|
193
|
+
)
|
|
194
|
+
) then 'queued'
|
|
195
|
+
when bool_and(
|
|
196
|
+
(
|
|
197
|
+
invalidation.id is not null
|
|
198
|
+
and invalidation.applied_at is not null
|
|
199
|
+
)
|
|
200
|
+
or (
|
|
201
|
+
invalidation.id is null
|
|
202
|
+
and effect.status = 'completed'
|
|
203
|
+
)
|
|
204
|
+
) then 'completed'
|
|
205
|
+
else 'failed'
|
|
206
|
+
end::core.operation_status,
|
|
207
|
+
case
|
|
208
|
+
when count(*) = 0 then now()
|
|
209
|
+
else min(coalesce(effect.claimed_at, effect.last_attempt_at))
|
|
210
|
+
end,
|
|
211
|
+
case
|
|
212
|
+
when count(*) = 0 then now()
|
|
213
|
+
when bool_or(
|
|
214
|
+
(
|
|
215
|
+
invalidation.id is null
|
|
216
|
+
or invalidation.applied_at is null
|
|
217
|
+
)
|
|
218
|
+
and (
|
|
219
|
+
effect.status in ('pending', 'claimed', 'running')
|
|
220
|
+
or (
|
|
221
|
+
effect.status = 'failed'
|
|
222
|
+
and effect.retry_count < effect.max_retries
|
|
223
|
+
)
|
|
224
|
+
)
|
|
225
|
+
) then null
|
|
226
|
+
else coalesce(
|
|
227
|
+
max(coalesce(
|
|
228
|
+
invalidation.applied_at,
|
|
229
|
+
effect.completed_at,
|
|
230
|
+
effect.last_attempt_at,
|
|
231
|
+
effect.claimed_at,
|
|
232
|
+
invalidation.created_at
|
|
233
|
+
)),
|
|
234
|
+
now()
|
|
235
|
+
)
|
|
236
|
+
end
|
|
237
|
+
into next_status, next_started_at, next_finished_at
|
|
238
|
+
from audit.effect as effect
|
|
239
|
+
left join accounting.reservation_journal_invalidation as invalidation
|
|
240
|
+
on invalidation.tenant_id = effect.tenant_id
|
|
241
|
+
and invalidation.effect_id = effect.id
|
|
242
|
+
where effect.action_id = target_action_id
|
|
243
|
+
and (
|
|
244
|
+
operation_row.public_id_format = 'legacy'
|
|
245
|
+
or (
|
|
246
|
+
effect.effect_type = 'REFRESH_RESERVATION_JOURNAL'
|
|
247
|
+
and effect.entity_type = 'reservation'
|
|
248
|
+
)
|
|
249
|
+
);
|
|
250
|
+
else
|
|
251
|
+
select
|
|
252
|
+
case
|
|
253
|
+
when count(*) = 0 then 'completed'
|
|
254
|
+
when bool_and(effect.status = 'completed') then 'completed'
|
|
255
|
+
when bool_and(
|
|
256
|
+
effect.status in ('completed', 'dead_letter')
|
|
257
|
+
or (
|
|
258
|
+
effect.status = 'failed'
|
|
259
|
+
and effect.retry_count >= effect.max_retries
|
|
260
|
+
)
|
|
261
|
+
) then 'failed'
|
|
262
|
+
when bool_or(effect.status in ('claimed', 'running'))
|
|
263
|
+
or bool_or(
|
|
264
|
+
effect.status in ('completed', 'dead_letter')
|
|
265
|
+
or (
|
|
266
|
+
effect.status = 'failed'
|
|
267
|
+
and effect.retry_count >= effect.max_retries
|
|
268
|
+
)
|
|
269
|
+
) then 'running'
|
|
270
|
+
else 'queued'
|
|
271
|
+
end::core.operation_status,
|
|
272
|
+
case
|
|
273
|
+
when count(*) = 0 then now()
|
|
274
|
+
else min(coalesce(effect.last_attempt_at, effect.claimed_at))
|
|
275
|
+
end,
|
|
276
|
+
case
|
|
277
|
+
when count(*) = 0 then now()
|
|
278
|
+
when bool_and(
|
|
279
|
+
effect.status in ('completed', 'dead_letter')
|
|
280
|
+
or (
|
|
281
|
+
effect.status = 'failed'
|
|
282
|
+
and effect.retry_count >= effect.max_retries
|
|
283
|
+
)
|
|
284
|
+
) then coalesce(
|
|
285
|
+
max(coalesce(effect.completed_at, effect.last_attempt_at)),
|
|
286
|
+
now()
|
|
287
|
+
)
|
|
288
|
+
end
|
|
289
|
+
into next_status, next_started_at, next_finished_at
|
|
290
|
+
from audit.effect as effect
|
|
291
|
+
where effect.action_id = target_action_id;
|
|
292
|
+
end if;
|
|
293
|
+
|
|
294
|
+
update core.operation_source as source
|
|
295
|
+
set
|
|
296
|
+
status = next_status,
|
|
297
|
+
started_at = next_started_at,
|
|
298
|
+
finished_at = next_finished_at,
|
|
299
|
+
source_version = source.source_version + 1
|
|
300
|
+
where source.operation_id = operation_row.id
|
|
301
|
+
and source.source_type = 'audit-action'
|
|
302
|
+
and source.source_id = target_action_id
|
|
303
|
+
and (
|
|
304
|
+
source.status is distinct from next_status
|
|
305
|
+
or source.started_at is distinct from next_started_at
|
|
306
|
+
or source.finished_at is distinct from next_finished_at
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
perform core.refresh_operation_state(operation_row.id);
|
|
310
|
+
end loop;
|
|
311
|
+
end;
|
|
312
|
+
$$
|
|
313
|
+
`.execute(db);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
async function restoreOperationRefreshFunctions(db: Kysely<unknown>) {
|
|
317
|
+
await sql`
|
|
318
|
+
create or replace function core.refresh_operation_state(
|
|
319
|
+
target_operation_id uuid
|
|
320
|
+
) returns void
|
|
321
|
+
language plpgsql
|
|
322
|
+
as $$
|
|
323
|
+
declare
|
|
324
|
+
next_status core.operation_status;
|
|
325
|
+
next_started_at timestamptz;
|
|
326
|
+
next_finished_at timestamptz;
|
|
327
|
+
begin
|
|
328
|
+
select
|
|
329
|
+
case
|
|
330
|
+
when count(*) filter (where required) = 0 then 'queued'
|
|
331
|
+
when bool_and(status = 'completed') filter (where required)
|
|
332
|
+
then 'completed'
|
|
333
|
+
when bool_and(status in ('completed', 'failed')) filter (where required)
|
|
334
|
+
then 'failed'
|
|
335
|
+
when bool_or(status = 'running') filter (where required)
|
|
336
|
+
or bool_or(status in ('completed', 'failed')) filter (where required)
|
|
337
|
+
then 'running'
|
|
338
|
+
else 'queued'
|
|
339
|
+
end::core.operation_status,
|
|
340
|
+
min(started_at) filter (where required),
|
|
341
|
+
max(finished_at) filter (where required)
|
|
342
|
+
into next_status, next_started_at, next_finished_at
|
|
343
|
+
from core.operation_source
|
|
344
|
+
where operation_id = target_operation_id;
|
|
345
|
+
|
|
346
|
+
update core.operation
|
|
347
|
+
set
|
|
348
|
+
status = next_status,
|
|
349
|
+
started_at = coalesce(core.operation.started_at, next_started_at),
|
|
350
|
+
finished_at = case
|
|
351
|
+
when next_status in ('completed', 'failed')
|
|
352
|
+
then coalesce(next_finished_at, now())
|
|
353
|
+
else null
|
|
354
|
+
end,
|
|
355
|
+
failure_code = case
|
|
356
|
+
when next_status = 'failed' then 'operation_failed'
|
|
357
|
+
end,
|
|
358
|
+
failure_message = case
|
|
359
|
+
when next_status = 'failed' then 'Operation failed'
|
|
360
|
+
end,
|
|
361
|
+
updated_at = now(),
|
|
362
|
+
source_version = source_version + 1
|
|
363
|
+
where id = target_operation_id
|
|
364
|
+
and (
|
|
365
|
+
status is distinct from next_status
|
|
366
|
+
or started_at is distinct from coalesce(started_at, next_started_at)
|
|
367
|
+
or finished_at is distinct from case
|
|
368
|
+
when next_status in ('completed', 'failed')
|
|
369
|
+
then coalesce(next_finished_at, finished_at, now())
|
|
370
|
+
else null
|
|
371
|
+
end
|
|
372
|
+
);
|
|
373
|
+
end;
|
|
374
|
+
$$
|
|
375
|
+
`.execute(db);
|
|
376
|
+
|
|
377
|
+
await sql`
|
|
378
|
+
create or replace function core.refresh_audit_action_source(
|
|
379
|
+
target_action_id uuid
|
|
380
|
+
) returns void
|
|
381
|
+
language plpgsql
|
|
382
|
+
as $$
|
|
383
|
+
declare
|
|
384
|
+
target_operation_id uuid;
|
|
385
|
+
begin
|
|
386
|
+
update core.operation_source as source
|
|
387
|
+
set
|
|
388
|
+
status = state.status,
|
|
389
|
+
started_at = state.started_at,
|
|
390
|
+
finished_at = state.finished_at,
|
|
391
|
+
source_version = source.source_version + 1
|
|
392
|
+
from (
|
|
393
|
+
select
|
|
394
|
+
case
|
|
395
|
+
when count(*) = 0 then 'completed'
|
|
396
|
+
when bool_and(effect.status = 'completed') then 'completed'
|
|
397
|
+
when bool_and(
|
|
398
|
+
effect.status in ('completed', 'dead_letter')
|
|
399
|
+
or (
|
|
400
|
+
effect.status = 'failed'
|
|
401
|
+
and effect.retry_count >= effect.max_retries
|
|
402
|
+
)
|
|
403
|
+
) then 'failed'
|
|
404
|
+
when bool_or(effect.status in ('claimed', 'running'))
|
|
405
|
+
or bool_or(
|
|
406
|
+
effect.status in ('completed', 'dead_letter')
|
|
407
|
+
or (
|
|
408
|
+
effect.status = 'failed'
|
|
409
|
+
and effect.retry_count >= effect.max_retries
|
|
410
|
+
)
|
|
411
|
+
) then 'running'
|
|
412
|
+
else 'queued'
|
|
413
|
+
end::core.operation_status as status,
|
|
414
|
+
case
|
|
415
|
+
when count(*) = 0 then now()
|
|
416
|
+
else min(coalesce(effect.last_attempt_at, effect.claimed_at))
|
|
417
|
+
end as started_at,
|
|
418
|
+
case
|
|
419
|
+
when count(*) = 0 then now()
|
|
420
|
+
when bool_and(
|
|
421
|
+
effect.status in ('completed', 'dead_letter')
|
|
422
|
+
or (
|
|
423
|
+
effect.status = 'failed'
|
|
424
|
+
and effect.retry_count >= effect.max_retries
|
|
425
|
+
)
|
|
426
|
+
) then coalesce(
|
|
427
|
+
max(coalesce(effect.completed_at, effect.last_attempt_at)),
|
|
428
|
+
now()
|
|
429
|
+
)
|
|
430
|
+
end as finished_at
|
|
431
|
+
from audit.effect
|
|
432
|
+
where action_id = target_action_id
|
|
433
|
+
) as state
|
|
434
|
+
where source.source_type = 'audit-action'
|
|
435
|
+
and source.source_id = target_action_id
|
|
436
|
+
and (
|
|
437
|
+
source.status is distinct from state.status
|
|
438
|
+
or source.started_at is distinct from state.started_at
|
|
439
|
+
or source.finished_at is distinct from state.finished_at
|
|
440
|
+
);
|
|
441
|
+
|
|
442
|
+
for target_operation_id in
|
|
443
|
+
select operation_id
|
|
444
|
+
from core.operation_source
|
|
445
|
+
where source_type = 'audit-action'
|
|
446
|
+
and source_id = target_action_id
|
|
447
|
+
loop
|
|
448
|
+
perform core.refresh_operation_state(target_operation_id);
|
|
449
|
+
end loop;
|
|
450
|
+
end;
|
|
451
|
+
$$
|
|
452
|
+
`.execute(db);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
export async function up(db: Kysely<unknown>): Promise<void> {
|
|
456
|
+
await sql`
|
|
457
|
+
create type core.operation_public_id_format as enum ('action', 'legacy')
|
|
458
|
+
`.execute(db);
|
|
459
|
+
await sql`
|
|
460
|
+
alter table core.operation
|
|
461
|
+
add column public_id_format core.operation_public_id_format
|
|
462
|
+
not null default 'legacy'
|
|
463
|
+
`.execute(db);
|
|
464
|
+
await sql`
|
|
465
|
+
create index reservation_journal_invalidation_operation_idx
|
|
466
|
+
on accounting.reservation_journal_invalidation (
|
|
467
|
+
action_id,
|
|
468
|
+
tenant_id,
|
|
469
|
+
effect_id
|
|
470
|
+
)
|
|
471
|
+
where action_id is not null
|
|
472
|
+
`.execute(db);
|
|
473
|
+
await sql`
|
|
474
|
+
create table core.operation_backfill (
|
|
475
|
+
name text primary key,
|
|
476
|
+
completed_at timestamptz,
|
|
477
|
+
created_at timestamptz not null default now(),
|
|
478
|
+
updated_at timestamptz not null default now()
|
|
479
|
+
)
|
|
480
|
+
`.execute(db);
|
|
481
|
+
await sql`
|
|
482
|
+
insert into core.operation_backfill (name)
|
|
483
|
+
values ('reservation-journal-refresh-v1')
|
|
484
|
+
`.execute(db);
|
|
485
|
+
|
|
486
|
+
await replaceOperationRefreshFunctions(db);
|
|
487
|
+
|
|
488
|
+
await sql`
|
|
489
|
+
create function core.register_reservation_journal_operation(
|
|
490
|
+
target_action_id uuid
|
|
491
|
+
) returns void
|
|
492
|
+
language plpgsql
|
|
493
|
+
as $$
|
|
494
|
+
begin
|
|
495
|
+
insert into core.operation (
|
|
496
|
+
id,
|
|
497
|
+
tenant_id,
|
|
498
|
+
initiated_by_user_id,
|
|
499
|
+
type,
|
|
500
|
+
public_id_format,
|
|
501
|
+
created_at,
|
|
502
|
+
updated_at
|
|
503
|
+
)
|
|
504
|
+
select
|
|
505
|
+
action.id,
|
|
506
|
+
action.tenant_id,
|
|
507
|
+
case when action.actor_type = 'user' then action.actor_id end,
|
|
508
|
+
'reservation-journal-refresh',
|
|
509
|
+
'action',
|
|
510
|
+
action.created_at,
|
|
511
|
+
action.created_at
|
|
512
|
+
from audit.action as action
|
|
513
|
+
where action.id = target_action_id
|
|
514
|
+
and exists (
|
|
515
|
+
select 1
|
|
516
|
+
from accounting.reservation_journal_invalidation as invalidation
|
|
517
|
+
where invalidation.action_id = action.id
|
|
518
|
+
and invalidation.tenant_id = action.tenant_id
|
|
519
|
+
)
|
|
520
|
+
and exists (
|
|
521
|
+
select 1
|
|
522
|
+
from audit.effect as effect
|
|
523
|
+
where effect.action_id = action.id
|
|
524
|
+
and effect.tenant_id = action.tenant_id
|
|
525
|
+
and effect.effect_type = 'REFRESH_RESERVATION_JOURNAL'
|
|
526
|
+
and effect.entity_type = 'reservation'
|
|
527
|
+
)
|
|
528
|
+
and not exists (
|
|
529
|
+
select 1
|
|
530
|
+
from core.operation_source as source
|
|
531
|
+
where source.source_type = 'audit-action'
|
|
532
|
+
and source.source_id = action.id
|
|
533
|
+
)
|
|
534
|
+
on conflict (id) do nothing;
|
|
535
|
+
|
|
536
|
+
insert into core.operation_source (
|
|
537
|
+
operation_id,
|
|
538
|
+
source_type,
|
|
539
|
+
source_id
|
|
540
|
+
)
|
|
541
|
+
select operation.id, 'audit-action', target_action_id
|
|
542
|
+
from core.operation as operation
|
|
543
|
+
where operation.id = target_action_id
|
|
544
|
+
and operation.type = 'reservation-journal-refresh'
|
|
545
|
+
and operation.public_id_format = 'action'
|
|
546
|
+
and exists (
|
|
547
|
+
select 1
|
|
548
|
+
from audit.effect as effect
|
|
549
|
+
where effect.action_id = target_action_id
|
|
550
|
+
and effect.tenant_id = operation.tenant_id
|
|
551
|
+
and effect.effect_type = 'REFRESH_RESERVATION_JOURNAL'
|
|
552
|
+
and effect.entity_type = 'reservation'
|
|
553
|
+
)
|
|
554
|
+
on conflict (source_type, source_id) do nothing;
|
|
555
|
+
|
|
556
|
+
insert into core.operation_resource (
|
|
557
|
+
operation_id,
|
|
558
|
+
resource_type,
|
|
559
|
+
resource_id,
|
|
560
|
+
role,
|
|
561
|
+
is_primary
|
|
562
|
+
)
|
|
563
|
+
select
|
|
564
|
+
operation.id,
|
|
565
|
+
case
|
|
566
|
+
when recurring_fee.id is not null
|
|
567
|
+
then 'recurring-fee'::core.operation_resource_type
|
|
568
|
+
else case action.root_entity_type
|
|
569
|
+
when 'recurringFee'
|
|
570
|
+
then 'recurring-fee'::core.operation_resource_type
|
|
571
|
+
when 'reservation' then 'reservation'::core.operation_resource_type
|
|
572
|
+
when 'listing' then 'listing'::core.operation_resource_type
|
|
573
|
+
when 'ownerStatement' then 'statement'::core.operation_resource_type
|
|
574
|
+
else 'team'::core.operation_resource_type
|
|
575
|
+
end
|
|
576
|
+
end,
|
|
577
|
+
case
|
|
578
|
+
when recurring_fee.id is not null then recurring_fee.id
|
|
579
|
+
when action.root_entity_type in (
|
|
580
|
+
'recurringFee',
|
|
581
|
+
'reservation',
|
|
582
|
+
'listing',
|
|
583
|
+
'ownerStatement'
|
|
584
|
+
) and action.root_entity_id is not null then action.root_entity_id
|
|
585
|
+
else action.tenant_id
|
|
586
|
+
end,
|
|
587
|
+
'source',
|
|
588
|
+
true
|
|
589
|
+
from core.operation as operation
|
|
590
|
+
inner join audit.action as action
|
|
591
|
+
on action.id = operation.id
|
|
592
|
+
and action.tenant_id = operation.tenant_id
|
|
593
|
+
left join lateral (
|
|
594
|
+
select mutation.subject_id as id
|
|
595
|
+
from audit.mutation as mutation
|
|
596
|
+
where mutation.action_id = action.id
|
|
597
|
+
and mutation.tenant_id = action.tenant_id
|
|
598
|
+
and action.action_type = 'recurring_fees.update'
|
|
599
|
+
and mutation.subject_type = 'recurringFee'
|
|
600
|
+
order by mutation.created_at, mutation.id
|
|
601
|
+
limit 1
|
|
602
|
+
) as recurring_fee on true
|
|
603
|
+
where operation.id = target_action_id
|
|
604
|
+
and operation.type = 'reservation-journal-refresh'
|
|
605
|
+
and operation.public_id_format = 'action'
|
|
606
|
+
on conflict do nothing;
|
|
607
|
+
|
|
608
|
+
perform core.refresh_audit_action_source(target_action_id);
|
|
609
|
+
end;
|
|
610
|
+
$$
|
|
611
|
+
`.execute(db);
|
|
612
|
+
|
|
613
|
+
await sql`
|
|
614
|
+
create function core.refresh_reservation_journal_invalidation_operation()
|
|
615
|
+
returns trigger
|
|
616
|
+
language plpgsql
|
|
617
|
+
as $$
|
|
618
|
+
begin
|
|
619
|
+
if new.action_id is not null then
|
|
620
|
+
perform core.register_reservation_journal_operation(new.action_id);
|
|
621
|
+
end if;
|
|
622
|
+
if tg_op = 'UPDATE'
|
|
623
|
+
and old.action_id is distinct from new.action_id
|
|
624
|
+
and old.action_id is not null then
|
|
625
|
+
perform core.refresh_audit_action_source(old.action_id);
|
|
626
|
+
end if;
|
|
627
|
+
return new;
|
|
628
|
+
end;
|
|
629
|
+
$$
|
|
630
|
+
`.execute(db);
|
|
631
|
+
await sql`
|
|
632
|
+
create trigger refresh_reservation_journal_invalidation_operation
|
|
633
|
+
after insert or update of action_id, applied_at
|
|
634
|
+
on accounting.reservation_journal_invalidation
|
|
635
|
+
for each row
|
|
636
|
+
execute function core.refresh_reservation_journal_invalidation_operation()
|
|
637
|
+
`.execute(db);
|
|
638
|
+
|
|
639
|
+
await sql`
|
|
640
|
+
create function core.refresh_audit_action_operation_attempt()
|
|
641
|
+
returns trigger
|
|
642
|
+
language plpgsql
|
|
643
|
+
as $$
|
|
644
|
+
begin
|
|
645
|
+
perform core.refresh_audit_action_source(
|
|
646
|
+
coalesce(new.action_id, old.action_id)
|
|
647
|
+
);
|
|
648
|
+
return coalesce(new, old);
|
|
649
|
+
end;
|
|
650
|
+
$$
|
|
651
|
+
`.execute(db);
|
|
652
|
+
await sql`
|
|
653
|
+
create trigger refresh_audit_action_operation_attempt
|
|
654
|
+
after insert or update of status, result_code, finished_at or delete
|
|
655
|
+
on audit.effect_attempt
|
|
656
|
+
for each row execute function core.refresh_audit_action_operation_attempt()
|
|
657
|
+
`.execute(db);
|
|
658
|
+
|
|
659
|
+
await sql`
|
|
660
|
+
do $$
|
|
661
|
+
begin
|
|
662
|
+
if exists (select 1 from pg_roles where rolname = 'application') then
|
|
663
|
+
grant execute on function
|
|
664
|
+
core.register_reservation_journal_operation(uuid),
|
|
665
|
+
core.refresh_reservation_journal_invalidation_operation(),
|
|
666
|
+
core.refresh_audit_action_operation_attempt()
|
|
667
|
+
to application;
|
|
668
|
+
grant select, update on core.operation_backfill to application;
|
|
669
|
+
end if;
|
|
670
|
+
end;
|
|
671
|
+
$$
|
|
672
|
+
`.execute(db);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
export async function down(db: Kysely<unknown>): Promise<void> {
|
|
676
|
+
await sql`
|
|
677
|
+
drop trigger if exists refresh_audit_action_operation_attempt
|
|
678
|
+
on audit.effect_attempt
|
|
679
|
+
`.execute(db);
|
|
680
|
+
await sql`
|
|
681
|
+
drop function if exists core.refresh_audit_action_operation_attempt
|
|
682
|
+
`.execute(db);
|
|
683
|
+
await sql`
|
|
684
|
+
drop trigger if exists refresh_reservation_journal_invalidation_operation
|
|
685
|
+
on accounting.reservation_journal_invalidation
|
|
686
|
+
`.execute(db);
|
|
687
|
+
await sql`
|
|
688
|
+
drop function if exists
|
|
689
|
+
core.refresh_reservation_journal_invalidation_operation
|
|
690
|
+
`.execute(db);
|
|
691
|
+
await sql`
|
|
692
|
+
drop function if exists core.register_reservation_journal_operation
|
|
693
|
+
`.execute(db);
|
|
694
|
+
|
|
695
|
+
await sql`
|
|
696
|
+
delete from core.operation
|
|
697
|
+
where public_id_format = 'action'
|
|
698
|
+
and type = 'reservation-journal-refresh'
|
|
699
|
+
`.execute(db);
|
|
700
|
+
await restoreOperationRefreshFunctions(db);
|
|
701
|
+
await sql`drop table core.operation_backfill`.execute(db);
|
|
702
|
+
await sql`
|
|
703
|
+
drop index accounting.reservation_journal_invalidation_operation_idx
|
|
704
|
+
`.execute(db);
|
|
705
|
+
await sql`
|
|
706
|
+
alter table core.operation drop column public_id_format
|
|
707
|
+
`.execute(db);
|
|
708
|
+
await sql`drop type core.operation_public_id_format`.execute(db);
|
|
709
|
+
}
|