mcp-scraper 0.95.1 → 0.96.0
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/CHANGELOG.md +98 -39
- package/README.md +25 -7
- package/THIRD_PARTY_NOTICES.html +203 -0
- package/dist/actions-YCMJY3PF.js +1 -0
- package/dist/artifacts-4ZDNP2JW.js +1 -0
- package/dist/artifacts-N62OPIT5.js +1 -0
- package/dist/artifacts-PSADD5OC.js +1 -0
- package/dist/artifacts-RUR3OHAK.js +1 -0
- package/dist/bin/api-server.js +1 -1
- package/dist/bin/mcp-scraper-cli.js +22 -22
- package/dist/bin/mcp-scraper-core.js +1 -1
- package/dist/bin/mcp-scraper-install.js +1 -1
- package/dist/bin/mcp-stdio-server.js +1 -1
- package/dist/bin/paa-harvest.js +1 -1
- package/dist/blob-store-GIH26EDO.js +1 -0
- package/dist/bundle-TTJP6DHU.js +1 -0
- package/dist/chunk-24YDQ3EO.js +5 -0
- package/dist/chunk-2PRH4Z3N.js +181 -0
- package/dist/chunk-3F7AFM53.js +1 -0
- package/dist/chunk-4HZXATHB.js +27 -0
- package/dist/chunk-55ZANTHL.js +3 -0
- package/dist/chunk-5BKSKLHX.js +1 -0
- package/dist/chunk-63VWBMYR.js +3 -0
- package/dist/chunk-6N5EC7ZO.js +1 -0
- package/dist/chunk-6SNOUSSZ.js +182 -0
- package/dist/chunk-7POHNTVW.js +1 -0
- package/dist/chunk-7SR7MEU5.js +62 -0
- package/dist/chunk-7UHDOIUE.js +1 -0
- package/dist/chunk-ARSQZCTJ.js +945 -0
- package/dist/chunk-B3JRL45T.js +1 -0
- package/dist/chunk-B4ZKPLSP.js +1 -0
- package/dist/chunk-C7MGJUTU.js +1 -0
- package/dist/{chunk-W2BVJ7S2.js → chunk-CNSVZTTT.js} +4 -4
- package/dist/chunk-DGAW7FD7.js +12 -0
- package/dist/chunk-FRXKMOXI.js +41 -0
- package/dist/chunk-GVYGAZV7.js +1 -0
- package/dist/chunk-HCK6PLM3.js +3 -0
- package/dist/{chunk-I3HNHCIJ.js → chunk-HJIOH7HF.js} +199 -84
- package/dist/chunk-HNNYXMSI.js +1 -0
- package/dist/chunk-HUNSEGUN.js +57 -0
- package/dist/chunk-IFPION5D.js +121 -0
- package/dist/{chunk-DYDAQQMC.js → chunk-JAV5V3JT.js} +41 -8
- package/dist/chunk-JMZIDHYQ.js +2 -0
- package/dist/{chunk-FN7U6KRI.js → chunk-JW4GDY7S.js} +1 -1
- package/dist/chunk-KXJCGV3H.js +1 -0
- package/dist/chunk-LFRAA3MY.js +4 -0
- package/dist/{chunk-Y6STAKF3.js → chunk-M5QHXNFZ.js} +3 -3
- package/dist/chunk-MVMWPKRC.js +2 -0
- package/dist/chunk-NALXKTUU.js +1 -0
- package/dist/chunk-NNOX3FYG.js +198 -0
- package/dist/chunk-OB6E6UTD.js +1 -0
- package/dist/chunk-OILOHQNL.js +2 -0
- package/dist/chunk-OLYPLX5C.js +1 -0
- package/dist/chunk-QER2ELZP.js +1 -0
- package/dist/chunk-QWE7VKIX.js +1 -0
- package/dist/chunk-R5LJL6IR.js +225 -0
- package/dist/chunk-RDHGQ2WZ.js +1 -0
- package/dist/chunk-SDQW7IJN.js +1 -0
- package/dist/chunk-SEZETSB3.js +1 -0
- package/dist/chunk-SNJR5N36.js +1 -0
- package/dist/chunk-TPN25AFZ.js +159 -0
- package/dist/{chunk-AK6HVF5F.js → chunk-U4WIJZCX.js} +1 -1
- package/dist/chunk-UIOSADPB.js +1718 -0
- package/dist/{chunk-DYKSPHHR.js → chunk-UNEJYZ4T.js} +1 -1
- package/dist/chunk-VAC3SQ5T.js +16 -0
- package/dist/chunk-VVSHZV5S.js +73 -0
- package/dist/chunk-WNUXMPVX.js +1 -0
- package/dist/chunk-WP3QS4CJ.js +1 -0
- package/dist/chunk-ZPQNY5TI.js +1 -0
- package/dist/contracts-GUXGFCVV.js +1 -0
- package/dist/core-schema-423BSTE6.js +936 -0
- package/dist/dispatch-3HY4735M.js +1 -0
- package/dist/index.cjs +104 -214
- package/dist/index.d.cts +192 -180
- package/dist/index.d.ts +192 -180
- package/dist/index.js +1 -1
- package/dist/media-artifacts-67CJWI7O.js +1 -0
- package/dist/metering-ZH2P62IC.js +1 -0
- package/dist/migrations-3AKEC5QE.js +1 -0
- package/dist/queue-HUP2GNXF.js +1 -0
- package/dist/reconciliation-ANXNJWME.js +1 -0
- package/dist/report-artifact-offload-6OOY2PTM.js +1 -0
- package/dist/repository-4EC5EXVN.js +1 -0
- package/dist/{location-data-repository-NWWKDYG6.js → repository-CYXVMYAI.js} +1 -1
- package/dist/server-VQU6S24A.js +5825 -0
- package/dist/settlement-6CCE2PDH.js +1 -0
- package/dist/worker-5G5BK23B.js +1 -0
- package/package.json +18 -138
- package/dist/analytics-repository-A7VMA24D.js +0 -1
- package/dist/chunk-56C243PX.js +0 -2595
- package/dist/chunk-6ND5AFV5.js +0 -27
- package/dist/chunk-6NE4OIXR.js +0 -6
- package/dist/chunk-DOJMTFLB.js +0 -1
- package/dist/chunk-EDQ57S4P.js +0 -1
- package/dist/chunk-FCJXL4PU.js +0 -182
- package/dist/chunk-GPSPSJBJ.js +0 -1
- package/dist/chunk-HE45FFBU.js +0 -1
- package/dist/chunk-HUV2WTRW.js +0 -1
- package/dist/chunk-L552AQQY.js +0 -84
- package/dist/chunk-MVDMZ3N2.js +0 -1282
- package/dist/chunk-NA76FFGD.js +0 -73
- package/dist/chunk-NZSDHDX3.js +0 -1
- package/dist/chunk-OS4XFDIG.js +0 -21
- package/dist/chunk-QQ7OD7BJ.js +0 -100
- package/dist/chunk-RPA5ADXW.js +0 -16
- package/dist/chunk-TMB56NCA.js +0 -1
- package/dist/chunk-WO3N5FH2.js +0 -5
- package/dist/chunk-XPEJB4BZ.js +0 -1
- package/dist/chunk-YK4OAZRQ.js +0 -4
- package/dist/chunk-YWUNN24S.js +0 -1
- package/dist/chunk-ZJTUAWZA.js +0 -19
- package/dist/chunk-ZPK5LYEN.js +0 -1565
- package/dist/chunk-ZUGTYD2I.js +0 -63
- package/dist/db-B6RNXKEI.js +0 -1
- package/dist/extract-bundle-X7SRN63Y.js +0 -26
- package/dist/gmail-service-FZVJBSUT.js +0 -1
- package/dist/lead-list-enrichment-repository-SXYSS7ZX.js +0 -1
- package/dist/operation-metering-OBKSWJVJ.js +0 -1
- package/dist/server-JV6L7F2X.js +0 -5014
- package/dist/site-extract-repository-UVE7TWJO.js +0 -1
- package/dist/stripe-event-worker-2LSUVQOM.js +0 -1
- package/dist/worker-XPIO5TAV.js +0 -1
- /package/dist/{chunk-YGBTTW5D.js → chunk-2IATUJO6.js} +0 -0
- /package/dist/{chunk-6DTXIZY2.js → chunk-4ZVO6GX4.js} +0 -0
|
@@ -0,0 +1,1718 @@
|
|
|
1
|
+
import{b as i,w as a}from"./chunk-NNOX3FYG.js";async function _(){let t=await i().connect();try{await t.query(`
|
|
2
|
+
CREATE TABLE IF NOT EXISTS analytics_schema_migrations (
|
|
3
|
+
version text PRIMARY KEY,
|
|
4
|
+
applied_at timestamptz NOT NULL DEFAULT now()
|
|
5
|
+
)
|
|
6
|
+
`),await t.query(`
|
|
7
|
+
CREATE TABLE IF NOT EXISTS analytics_sites (
|
|
8
|
+
id uuid PRIMARY KEY,
|
|
9
|
+
slug text NOT NULL,
|
|
10
|
+
name text NOT NULL,
|
|
11
|
+
owner_user_id bigint NOT NULL,
|
|
12
|
+
timezone text NOT NULL DEFAULT 'UTC',
|
|
13
|
+
status text NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'disabled')),
|
|
14
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
15
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
16
|
+
UNIQUE(owner_user_id, slug)
|
|
17
|
+
)
|
|
18
|
+
`),await t.query(`
|
|
19
|
+
CREATE TABLE IF NOT EXISTS analytics_site_grants (
|
|
20
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
21
|
+
user_id bigint NOT NULL,
|
|
22
|
+
role text NOT NULL CHECK (role IN ('viewer', 'editor', 'owner')),
|
|
23
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
24
|
+
PRIMARY KEY(site_id, user_id)
|
|
25
|
+
)
|
|
26
|
+
`),await t.query(`
|
|
27
|
+
CREATE TABLE IF NOT EXISTS analytics_pixels (
|
|
28
|
+
id uuid PRIMARY KEY,
|
|
29
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
30
|
+
public_id text NOT NULL UNIQUE,
|
|
31
|
+
name text NOT NULL,
|
|
32
|
+
environment text NOT NULL DEFAULT 'production',
|
|
33
|
+
status text NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'disabled')),
|
|
34
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
35
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
36
|
+
disabled_at timestamptz
|
|
37
|
+
)
|
|
38
|
+
`),await t.query(`
|
|
39
|
+
CREATE TABLE IF NOT EXISTS analytics_pixel_domains (
|
|
40
|
+
id uuid PRIMARY KEY,
|
|
41
|
+
pixel_id uuid NOT NULL REFERENCES analytics_pixels(id) ON DELETE CASCADE,
|
|
42
|
+
hostname text NOT NULL,
|
|
43
|
+
state text NOT NULL DEFAULT 'observed_unverified'
|
|
44
|
+
CHECK (state IN ('observed_unverified', 'approved', 'rejected', 'revoked')),
|
|
45
|
+
first_seen_at timestamptz NOT NULL DEFAULT now(),
|
|
46
|
+
last_seen_at timestamptz NOT NULL DEFAULT now(),
|
|
47
|
+
accepted_count bigint NOT NULL DEFAULT 0,
|
|
48
|
+
rejected_count bigint NOT NULL DEFAULT 0,
|
|
49
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
50
|
+
UNIQUE(pixel_id, hostname)
|
|
51
|
+
)
|
|
52
|
+
`),await t.query(`
|
|
53
|
+
CREATE TABLE IF NOT EXISTS analytics_host_groups (
|
|
54
|
+
id uuid PRIMARY KEY,
|
|
55
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
56
|
+
pixel_id uuid NOT NULL REFERENCES analytics_pixels(id) ON DELETE CASCADE,
|
|
57
|
+
name text NOT NULL,
|
|
58
|
+
auto_decorate boolean NOT NULL DEFAULT false,
|
|
59
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
60
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
61
|
+
UNIQUE(pixel_id, name)
|
|
62
|
+
)
|
|
63
|
+
`),await t.query(`
|
|
64
|
+
CREATE TABLE IF NOT EXISTS analytics_host_group_members (
|
|
65
|
+
group_id uuid NOT NULL REFERENCES analytics_host_groups(id) ON DELETE CASCADE,
|
|
66
|
+
hostname text NOT NULL,
|
|
67
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
68
|
+
PRIMARY KEY(group_id, hostname)
|
|
69
|
+
)
|
|
70
|
+
`),await t.query(`
|
|
71
|
+
CREATE TABLE IF NOT EXISTS analytics_events (
|
|
72
|
+
id uuid PRIMARY KEY,
|
|
73
|
+
event_id text NOT NULL,
|
|
74
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
75
|
+
pixel_id uuid NOT NULL REFERENCES analytics_pixels(id) ON DELETE CASCADE,
|
|
76
|
+
hostname text NOT NULL,
|
|
77
|
+
visitor_id text,
|
|
78
|
+
session_id text,
|
|
79
|
+
visitor_hmac text,
|
|
80
|
+
session_hmac text,
|
|
81
|
+
event_name text NOT NULL,
|
|
82
|
+
occurred_at timestamptz NOT NULL,
|
|
83
|
+
received_at timestamptz NOT NULL DEFAULT now(),
|
|
84
|
+
path text NOT NULL DEFAULT '/',
|
|
85
|
+
canonical_url text,
|
|
86
|
+
title text,
|
|
87
|
+
referrer text,
|
|
88
|
+
source text,
|
|
89
|
+
medium text,
|
|
90
|
+
campaign text,
|
|
91
|
+
device_class text,
|
|
92
|
+
properties jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
93
|
+
bot_status text NOT NULL DEFAULT 'unknown',
|
|
94
|
+
UNIQUE(site_id, event_id)
|
|
95
|
+
)
|
|
96
|
+
`),await t.query(`
|
|
97
|
+
CREATE TABLE IF NOT EXISTS analytics_conversions (
|
|
98
|
+
id uuid PRIMARY KEY,
|
|
99
|
+
source_event_id text NOT NULL,
|
|
100
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
101
|
+
pixel_id uuid REFERENCES analytics_pixels(id) ON DELETE SET NULL,
|
|
102
|
+
session_id text,
|
|
103
|
+
session_hmac text,
|
|
104
|
+
conversion_kind text NOT NULL,
|
|
105
|
+
value_minor bigint NOT NULL DEFAULT 0,
|
|
106
|
+
currency text NOT NULL DEFAULT 'USD',
|
|
107
|
+
occurred_at timestamptz NOT NULL,
|
|
108
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
109
|
+
UNIQUE(site_id, source_event_id)
|
|
110
|
+
)
|
|
111
|
+
`),await t.query(`
|
|
112
|
+
CREATE TABLE IF NOT EXISTS analytics_ingestion_receipts (
|
|
113
|
+
id uuid PRIMARY KEY,
|
|
114
|
+
request_id text NOT NULL UNIQUE,
|
|
115
|
+
site_id uuid REFERENCES analytics_sites(id) ON DELETE SET NULL,
|
|
116
|
+
pixel_id uuid REFERENCES analytics_pixels(id) ON DELETE SET NULL,
|
|
117
|
+
hostname text,
|
|
118
|
+
accepted_count integer NOT NULL DEFAULT 0,
|
|
119
|
+
rejected_count integer NOT NULL DEFAULT 0,
|
|
120
|
+
reason_codes jsonb NOT NULL DEFAULT '[]'::jsonb,
|
|
121
|
+
created_at timestamptz NOT NULL DEFAULT now()
|
|
122
|
+
)
|
|
123
|
+
`),await t.query(`
|
|
124
|
+
CREATE TABLE IF NOT EXISTS analytics_rollups_daily (
|
|
125
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
126
|
+
pixel_id uuid NOT NULL REFERENCES analytics_pixels(id) ON DELETE CASCADE,
|
|
127
|
+
hostname text NOT NULL,
|
|
128
|
+
bucket date NOT NULL,
|
|
129
|
+
source text NOT NULL DEFAULT '(direct)',
|
|
130
|
+
medium text NOT NULL DEFAULT '(none)',
|
|
131
|
+
campaign text NOT NULL DEFAULT '(none)',
|
|
132
|
+
pageviews integer NOT NULL DEFAULT 0,
|
|
133
|
+
events integer NOT NULL DEFAULT 0,
|
|
134
|
+
visitors integer NOT NULL DEFAULT 0,
|
|
135
|
+
sessions integer NOT NULL DEFAULT 0,
|
|
136
|
+
refreshed_at timestamptz NOT NULL DEFAULT now(),
|
|
137
|
+
PRIMARY KEY(site_id, pixel_id, hostname, bucket, source, medium, campaign)
|
|
138
|
+
)
|
|
139
|
+
`),await t.query(`
|
|
140
|
+
CREATE TABLE IF NOT EXISTS analytics_rollup_runs (
|
|
141
|
+
id uuid PRIMARY KEY,
|
|
142
|
+
window_start timestamptz NOT NULL,
|
|
143
|
+
window_end timestamptz NOT NULL,
|
|
144
|
+
status text NOT NULL CHECK (status IN ('running', 'complete', 'failed')),
|
|
145
|
+
row_count integer NOT NULL DEFAULT 0,
|
|
146
|
+
started_at timestamptz NOT NULL DEFAULT now(),
|
|
147
|
+
completed_at timestamptz,
|
|
148
|
+
error_code text
|
|
149
|
+
)
|
|
150
|
+
`),await t.query(`
|
|
151
|
+
CREATE TABLE IF NOT EXISTS analytics_exports (
|
|
152
|
+
id uuid PRIMARY KEY,
|
|
153
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
154
|
+
requested_by_user_id bigint NOT NULL,
|
|
155
|
+
idempotency_key text,
|
|
156
|
+
report text NOT NULL,
|
|
157
|
+
format text NOT NULL CHECK (format IN ('csv', 'json', 'markdown')),
|
|
158
|
+
filters jsonb NOT NULL,
|
|
159
|
+
content text NOT NULL,
|
|
160
|
+
created_at timestamptz NOT NULL DEFAULT now()
|
|
161
|
+
)
|
|
162
|
+
`),await t.query(`
|
|
163
|
+
CREATE TABLE IF NOT EXISTS analytics_campaign_links (
|
|
164
|
+
id uuid PRIMARY KEY,
|
|
165
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
166
|
+
pixel_id uuid REFERENCES analytics_pixels(id) ON DELETE SET NULL,
|
|
167
|
+
name text NOT NULL,
|
|
168
|
+
destination_url text NOT NULL,
|
|
169
|
+
short_code text NOT NULL UNIQUE,
|
|
170
|
+
utm_source text NOT NULL,
|
|
171
|
+
utm_medium text NOT NULL,
|
|
172
|
+
utm_campaign text NOT NULL,
|
|
173
|
+
utm_term text,
|
|
174
|
+
utm_content text,
|
|
175
|
+
ad_group text,
|
|
176
|
+
ad_name text,
|
|
177
|
+
creative_id text,
|
|
178
|
+
status text NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'archived')),
|
|
179
|
+
created_by_user_id bigint NOT NULL,
|
|
180
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
181
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
182
|
+
)
|
|
183
|
+
`),await t.query(`
|
|
184
|
+
CREATE TABLE IF NOT EXISTS analytics_campaign_clicks (
|
|
185
|
+
id uuid PRIMARY KEY,
|
|
186
|
+
link_id uuid NOT NULL REFERENCES analytics_campaign_links(id) ON DELETE CASCADE,
|
|
187
|
+
occurred_at timestamptz NOT NULL DEFAULT now(),
|
|
188
|
+
referrer text
|
|
189
|
+
)
|
|
190
|
+
`),await t.query(`
|
|
191
|
+
CREATE TABLE IF NOT EXISTS analytics_forms (
|
|
192
|
+
id uuid PRIMARY KEY,
|
|
193
|
+
public_id text NOT NULL UNIQUE,
|
|
194
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
195
|
+
pixel_id uuid NOT NULL REFERENCES analytics_pixels(id) ON DELETE RESTRICT,
|
|
196
|
+
name text NOT NULL,
|
|
197
|
+
slug text NOT NULL,
|
|
198
|
+
fields jsonb NOT NULL,
|
|
199
|
+
brand jsonb NOT NULL,
|
|
200
|
+
submit_label text NOT NULL DEFAULT 'Submit',
|
|
201
|
+
success_message text NOT NULL DEFAULT 'Thank you. Your response was received.',
|
|
202
|
+
redirect_url text,
|
|
203
|
+
consent_text text,
|
|
204
|
+
status text NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'published', 'archived')),
|
|
205
|
+
version integer NOT NULL DEFAULT 1,
|
|
206
|
+
created_by_user_id bigint NOT NULL,
|
|
207
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
208
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
209
|
+
UNIQUE(site_id, slug)
|
|
210
|
+
)
|
|
211
|
+
`),await t.query(`
|
|
212
|
+
CREATE TABLE IF NOT EXISTS analytics_form_submissions (
|
|
213
|
+
id uuid PRIMARY KEY,
|
|
214
|
+
form_id uuid NOT NULL REFERENCES analytics_forms(id) ON DELETE CASCADE,
|
|
215
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
216
|
+
pixel_id uuid NOT NULL REFERENCES analytics_pixels(id) ON DELETE RESTRICT,
|
|
217
|
+
visitor_id text,
|
|
218
|
+
session_id text,
|
|
219
|
+
crm_person_ref text,
|
|
220
|
+
source text,
|
|
221
|
+
medium text,
|
|
222
|
+
campaign text,
|
|
223
|
+
occurred_at timestamptz NOT NULL DEFAULT now()
|
|
224
|
+
)
|
|
225
|
+
`),await t.query("ALTER TABLE analytics_form_submissions ADD COLUMN IF NOT EXISTS crm_delivery_status text NOT NULL DEFAULT 'pending'"),await t.query("ALTER TABLE analytics_form_submissions ADD COLUMN IF NOT EXISTS crm_entity_id uuid"),await t.query("ALTER TABLE analytics_form_submissions ADD COLUMN IF NOT EXISTS bridge_run_id uuid"),await t.query("ALTER TABLE analytics_form_submissions ADD COLUMN IF NOT EXISTS idempotency_key text"),await t.query(`CREATE UNIQUE INDEX IF NOT EXISTS analytics_form_submissions_idempotency
|
|
226
|
+
ON analytics_form_submissions(form_id, idempotency_key)
|
|
227
|
+
WHERE idempotency_key IS NOT NULL`),await t.query(`
|
|
228
|
+
CREATE TABLE IF NOT EXISTS analytics_form_delivery_jobs (
|
|
229
|
+
id uuid PRIMARY KEY,
|
|
230
|
+
submission_id uuid NOT NULL UNIQUE REFERENCES analytics_form_submissions(id) ON DELETE CASCADE,
|
|
231
|
+
owner_user_id bigint NOT NULL,
|
|
232
|
+
payload_ciphertext text NOT NULL,
|
|
233
|
+
status text NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'delivered', 'failed', 'expired')),
|
|
234
|
+
attempts integer NOT NULL DEFAULT 0,
|
|
235
|
+
last_error_code text,
|
|
236
|
+
next_attempt_at timestamptz NOT NULL DEFAULT now(),
|
|
237
|
+
expires_at timestamptz NOT NULL DEFAULT now() + interval '7 days',
|
|
238
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
239
|
+
delivered_at timestamptz
|
|
240
|
+
)
|
|
241
|
+
`),await t.query("ALTER TABLE analytics_form_delivery_jobs ADD COLUMN IF NOT EXISTS bridge_run_id uuid"),await t.query("ALTER TABLE analytics_form_delivery_jobs ADD COLUMN IF NOT EXISTS crm_entity_id uuid"),await t.query("ALTER TABLE analytics_form_delivery_jobs ADD COLUMN IF NOT EXISTS idempotency_key text"),await t.query(`
|
|
242
|
+
CREATE TABLE IF NOT EXISTS analytics_form_bridge_receipts (
|
|
243
|
+
id uuid PRIMARY KEY,
|
|
244
|
+
bridge_run_id uuid NOT NULL,
|
|
245
|
+
submission_id uuid NOT NULL REFERENCES analytics_form_submissions(id) ON DELETE CASCADE,
|
|
246
|
+
system text NOT NULL CHECK (system IN ('analytics','crm')),
|
|
247
|
+
status text NOT NULL CHECK (status IN ('pending','delivered','failed','expired')),
|
|
248
|
+
opaque_entity_id uuid,
|
|
249
|
+
safe_error_code text,
|
|
250
|
+
occurred_at timestamptz NOT NULL DEFAULT now(),
|
|
251
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
252
|
+
UNIQUE(bridge_run_id, system)
|
|
253
|
+
)
|
|
254
|
+
`),await t.query(`
|
|
255
|
+
CREATE TABLE IF NOT EXISTS analytics_people (
|
|
256
|
+
id uuid PRIMARY KEY,
|
|
257
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
258
|
+
crm_person_ref text NOT NULL,
|
|
259
|
+
first_seen_at timestamptz NOT NULL DEFAULT now(),
|
|
260
|
+
last_seen_at timestamptz NOT NULL DEFAULT now(),
|
|
261
|
+
UNIQUE(site_id, crm_person_ref)
|
|
262
|
+
)
|
|
263
|
+
`),await t.query("ALTER TABLE analytics_people ADD COLUMN IF NOT EXISTS crm_entity_id uuid"),await t.query(`CREATE UNIQUE INDEX IF NOT EXISTS analytics_people_crm_entity
|
|
264
|
+
ON analytics_people(site_id, crm_entity_id)
|
|
265
|
+
WHERE crm_entity_id IS NOT NULL`),await t.query(`
|
|
266
|
+
CREATE TABLE IF NOT EXISTS analytics_identity_nodes (
|
|
267
|
+
id uuid PRIMARY KEY,
|
|
268
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
269
|
+
kind text NOT NULL CHECK (kind IN ('visitor_id','session_id','email','phone','customer_id','click_id','device_signal','network_signal')),
|
|
270
|
+
value_hmac text NOT NULL,
|
|
271
|
+
first_seen_at timestamptz NOT NULL DEFAULT now(),
|
|
272
|
+
last_seen_at timestamptz NOT NULL DEFAULT now(),
|
|
273
|
+
UNIQUE(site_id, kind, value_hmac)
|
|
274
|
+
)
|
|
275
|
+
`),await t.query(`
|
|
276
|
+
CREATE TABLE IF NOT EXISTS analytics_identity_edges (
|
|
277
|
+
id uuid PRIMARY KEY,
|
|
278
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
279
|
+
person_id uuid NOT NULL REFERENCES analytics_people(id) ON DELETE CASCADE,
|
|
280
|
+
identity_node_id uuid NOT NULL REFERENCES analytics_identity_nodes(id) ON DELETE CASCADE,
|
|
281
|
+
evidence_kind text NOT NULL,
|
|
282
|
+
confidence numeric(4,3) NOT NULL CHECK (confidence >= 0 AND confidence <= 1),
|
|
283
|
+
consent_scope text NOT NULL DEFAULT 'analytics',
|
|
284
|
+
first_seen_at timestamptz NOT NULL DEFAULT now(),
|
|
285
|
+
last_seen_at timestamptz NOT NULL DEFAULT now(),
|
|
286
|
+
UNIQUE(person_id, identity_node_id, evidence_kind)
|
|
287
|
+
)
|
|
288
|
+
`),await t.query(`
|
|
289
|
+
CREATE TABLE IF NOT EXISTS analytics_identity_assignments (
|
|
290
|
+
identity_node_id uuid PRIMARY KEY REFERENCES analytics_identity_nodes(id) ON DELETE CASCADE,
|
|
291
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
292
|
+
person_id uuid NOT NULL REFERENCES analytics_people(id) ON DELETE CASCADE,
|
|
293
|
+
crm_entity_id uuid,
|
|
294
|
+
evidence_kind text NOT NULL,
|
|
295
|
+
assigned_at timestamptz NOT NULL DEFAULT now(),
|
|
296
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
297
|
+
CHECK (crm_entity_id IS NULL OR crm_entity_id::text <> '')
|
|
298
|
+
)
|
|
299
|
+
`),await t.query(`
|
|
300
|
+
CREATE TABLE IF NOT EXISTS analytics_identity_assignment_conflicts (
|
|
301
|
+
id uuid PRIMARY KEY,
|
|
302
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
303
|
+
identity_node_id uuid REFERENCES analytics_identity_nodes(id) ON DELETE CASCADE,
|
|
304
|
+
active_person_id uuid REFERENCES analytics_people(id) ON DELETE CASCADE,
|
|
305
|
+
attempted_person_id uuid REFERENCES analytics_people(id) ON DELETE CASCADE,
|
|
306
|
+
active_crm_entity_id uuid,
|
|
307
|
+
attempted_crm_entity_id uuid,
|
|
308
|
+
evidence_kind text NOT NULL,
|
|
309
|
+
status text NOT NULL DEFAULT 'pending' CHECK (status IN ('pending','resolved','dismissed')),
|
|
310
|
+
observed_at timestamptz NOT NULL DEFAULT now(),
|
|
311
|
+
resolved_at timestamptz,
|
|
312
|
+
CONSTRAINT analytics_identity_assignment_conflicts_pending_identity
|
|
313
|
+
CHECK (status <> 'pending' OR (
|
|
314
|
+
identity_node_id IS NOT NULL AND active_person_id IS NOT NULL
|
|
315
|
+
AND attempted_person_id IS NOT NULL AND active_person_id <> attempted_person_id
|
|
316
|
+
))
|
|
317
|
+
)
|
|
318
|
+
`),await t.query("ALTER TABLE analytics_identity_assignment_conflicts ALTER COLUMN identity_node_id DROP NOT NULL"),await t.query("ALTER TABLE analytics_identity_assignment_conflicts ALTER COLUMN active_person_id DROP NOT NULL"),await t.query("ALTER TABLE analytics_identity_assignment_conflicts ALTER COLUMN attempted_person_id DROP NOT NULL"),await t.query(`ALTER TABLE analytics_identity_assignment_conflicts
|
|
319
|
+
DROP CONSTRAINT IF EXISTS analytics_identity_assignment_conflicts_check`),await t.query(`
|
|
320
|
+
DO $$ BEGIN
|
|
321
|
+
IF NOT EXISTS (
|
|
322
|
+
SELECT 1 FROM pg_constraint
|
|
323
|
+
WHERE conrelid='analytics_identity_assignment_conflicts'::regclass
|
|
324
|
+
AND conname='analytics_identity_assignment_conflicts_pending_identity'
|
|
325
|
+
) THEN
|
|
326
|
+
ALTER TABLE analytics_identity_assignment_conflicts
|
|
327
|
+
ADD CONSTRAINT analytics_identity_assignment_conflicts_pending_identity
|
|
328
|
+
CHECK (status <> 'pending' OR (
|
|
329
|
+
identity_node_id IS NOT NULL AND active_person_id IS NOT NULL
|
|
330
|
+
AND attempted_person_id IS NOT NULL AND active_person_id <> attempted_person_id
|
|
331
|
+
));
|
|
332
|
+
END IF;
|
|
333
|
+
END $$
|
|
334
|
+
`),await t.query(`CREATE UNIQUE INDEX IF NOT EXISTS analytics_identity_assignment_conflicts_pending
|
|
335
|
+
ON analytics_identity_assignment_conflicts(identity_node_id, active_person_id, attempted_person_id)
|
|
336
|
+
WHERE status='pending'`),await t.query(`
|
|
337
|
+
CREATE TABLE IF NOT EXISTS analytics_linker_tokens (
|
|
338
|
+
id uuid PRIMARY KEY,
|
|
339
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
340
|
+
pixel_id uuid NOT NULL REFERENCES analytics_pixels(id) ON DELETE CASCADE,
|
|
341
|
+
host_group_id uuid NOT NULL REFERENCES analytics_host_groups(id) ON DELETE CASCADE,
|
|
342
|
+
identity_node_id uuid NOT NULL REFERENCES analytics_identity_nodes(id) ON DELETE CASCADE,
|
|
343
|
+
source_hostname text NOT NULL,
|
|
344
|
+
audience_hostname text NOT NULL,
|
|
345
|
+
expires_at timestamptz NOT NULL,
|
|
346
|
+
consumed_at timestamptz,
|
|
347
|
+
created_at timestamptz NOT NULL DEFAULT now()
|
|
348
|
+
)
|
|
349
|
+
`),await t.query("ALTER TABLE analytics_events ADD COLUMN IF NOT EXISTS click_ids jsonb NOT NULL DEFAULT '{}'::jsonb"),await t.query("ALTER TABLE analytics_events ADD COLUMN IF NOT EXISTS visitor_hmac text"),await t.query("ALTER TABLE analytics_events ADD COLUMN IF NOT EXISTS session_hmac text"),await t.query("ALTER TABLE analytics_conversions ADD COLUMN IF NOT EXISTS session_hmac text"),await a(t),await t.query("ALTER TABLE analytics_form_submissions ADD COLUMN IF NOT EXISTS click_ids jsonb NOT NULL DEFAULT '{}'::jsonb"),await t.query("ALTER TABLE analytics_form_submissions ADD COLUMN IF NOT EXISTS person_id uuid REFERENCES analytics_people(id) ON DELETE SET NULL"),await t.query("ALTER TABLE analytics_events ADD COLUMN IF NOT EXISTS person_id uuid REFERENCES analytics_people(id) ON DELETE SET NULL"),await t.query("ALTER TABLE analytics_events ADD COLUMN IF NOT EXISTS channel_family text"),await t.query("ALTER TABLE analytics_events ADD COLUMN IF NOT EXISTS platform text"),await t.query("ALTER TABLE analytics_events ADD COLUMN IF NOT EXISTS campaign_id text"),await t.query("ALTER TABLE analytics_events ADD COLUMN IF NOT EXISTS ad_set_id text"),await t.query("ALTER TABLE analytics_events ADD COLUMN IF NOT EXISTS ad_id text"),await t.query("ALTER TABLE analytics_events ADD COLUMN IF NOT EXISTS creative_id text"),await t.query("ALTER TABLE analytics_events ADD COLUMN IF NOT EXISTS placement text"),await t.query("ALTER TABLE analytics_events ADD COLUMN IF NOT EXISTS utm_term text"),await t.query("ALTER TABLE analytics_events ADD COLUMN IF NOT EXISTS utm_content text"),await t.query("ALTER TABLE analytics_events ADD COLUMN IF NOT EXISTS engaged_ms integer"),await t.query("ALTER TABLE analytics_events ADD COLUMN IF NOT EXISTS scroll_depth smallint"),await t.query("ALTER TABLE analytics_conversions ADD COLUMN IF NOT EXISTS person_id uuid REFERENCES analytics_people(id) ON DELETE SET NULL"),await t.query("ALTER TABLE analytics_sites ADD COLUMN IF NOT EXISTS business_model text NOT NULL DEFAULT 'leadgen' CHECK (business_model IN ('leadgen','saas','ecommerce'))"),await t.query(`
|
|
350
|
+
CREATE TABLE IF NOT EXISTS analytics_ad_spend_daily (
|
|
351
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
352
|
+
bucket date NOT NULL,
|
|
353
|
+
platform text NOT NULL,
|
|
354
|
+
campaign text NOT NULL DEFAULT '(all)',
|
|
355
|
+
spend_minor bigint NOT NULL CHECK (spend_minor >= 0),
|
|
356
|
+
currency text NOT NULL DEFAULT 'USD',
|
|
357
|
+
source_ref text,
|
|
358
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
359
|
+
PRIMARY KEY(site_id, bucket, platform, campaign)
|
|
360
|
+
)
|
|
361
|
+
`),await t.query(`
|
|
362
|
+
CREATE TABLE IF NOT EXISTS analytics_crm_imports (
|
|
363
|
+
id uuid PRIMARY KEY,
|
|
364
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
365
|
+
requested_by_user_id bigint NOT NULL,
|
|
366
|
+
source_system text NOT NULL,
|
|
367
|
+
filename text NOT NULL,
|
|
368
|
+
status text NOT NULL DEFAULT 'queued' CHECK (status IN ('queued','processing','complete','partial','failed')),
|
|
369
|
+
accepted_count integer NOT NULL DEFAULT 0,
|
|
370
|
+
rejected_count integer NOT NULL DEFAULT 0,
|
|
371
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
372
|
+
completed_at timestamptz
|
|
373
|
+
)
|
|
374
|
+
`),await t.query(`
|
|
375
|
+
CREATE TABLE IF NOT EXISTS analytics_crm_import_rows (
|
|
376
|
+
id uuid PRIMARY KEY,
|
|
377
|
+
import_id uuid NOT NULL REFERENCES analytics_crm_imports(id) ON DELETE CASCADE,
|
|
378
|
+
crm_person_ref text NOT NULL,
|
|
379
|
+
payload_ciphertext text NOT NULL,
|
|
380
|
+
delivery_status text NOT NULL DEFAULT 'pending' CHECK (delivery_status IN ('pending','delivered','failed','expired')),
|
|
381
|
+
attempts integer NOT NULL DEFAULT 0,
|
|
382
|
+
last_error_code text,
|
|
383
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
384
|
+
delivered_at timestamptz,
|
|
385
|
+
UNIQUE(import_id, crm_person_ref)
|
|
386
|
+
)
|
|
387
|
+
`),await t.query(`
|
|
388
|
+
CREATE TABLE IF NOT EXISTS analytics_activation_destinations (
|
|
389
|
+
id uuid PRIMARY KEY,
|
|
390
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
391
|
+
platform text NOT NULL CHECK (platform IN ('meta','google','tiktok','reddit')),
|
|
392
|
+
name text NOT NULL,
|
|
393
|
+
connection_ref text,
|
|
394
|
+
external_dataset_id text,
|
|
395
|
+
status text NOT NULL DEFAULT 'active' CHECK (status IN ('active','paused','archived')),
|
|
396
|
+
automatic_delivery_enabled boolean NOT NULL DEFAULT false,
|
|
397
|
+
event_mapping jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
398
|
+
created_by_user_id bigint NOT NULL,
|
|
399
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
400
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
401
|
+
)
|
|
402
|
+
`),await t.query(`
|
|
403
|
+
CREATE TABLE IF NOT EXISTS analytics_activation_jobs (
|
|
404
|
+
id uuid PRIMARY KEY,
|
|
405
|
+
destination_id uuid NOT NULL REFERENCES analytics_activation_destinations(id) ON DELETE CASCADE,
|
|
406
|
+
conversion_id uuid NOT NULL REFERENCES analytics_conversions(id) ON DELETE CASCADE,
|
|
407
|
+
person_id uuid REFERENCES analytics_people(id) ON DELETE SET NULL,
|
|
408
|
+
payload_ciphertext text NOT NULL,
|
|
409
|
+
status text NOT NULL DEFAULT 'pending' CHECK (status IN ('pending','delivered','failed','expired')),
|
|
410
|
+
attempts integer NOT NULL DEFAULT 0,
|
|
411
|
+
last_error_code text,
|
|
412
|
+
next_attempt_at timestamptz NOT NULL DEFAULT now(),
|
|
413
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
414
|
+
delivered_at timestamptz,
|
|
415
|
+
UNIQUE(destination_id, conversion_id)
|
|
416
|
+
)
|
|
417
|
+
`),await t.query(`
|
|
418
|
+
CREATE TABLE IF NOT EXISTS analytics_thorbit_entitlements (
|
|
419
|
+
user_id bigint PRIMARY KEY,
|
|
420
|
+
thorbit_org_id text,
|
|
421
|
+
connection_source text NOT NULL DEFAULT 'trial' CHECK (connection_source IN ('trial','api_key','thorbit_session')),
|
|
422
|
+
eligible boolean NOT NULL DEFAULT false,
|
|
423
|
+
reason text NOT NULL,
|
|
424
|
+
plan_id text,
|
|
425
|
+
subscription_status text,
|
|
426
|
+
monthly_recurring_revenue_cents integer NOT NULL DEFAULT 0,
|
|
427
|
+
minimum_monthly_recurring_revenue_cents integer NOT NULL DEFAULT 0,
|
|
428
|
+
verified_at timestamptz NOT NULL,
|
|
429
|
+
refresh_after timestamptz NOT NULL,
|
|
430
|
+
access_expires_at timestamptz NOT NULL,
|
|
431
|
+
trial_started_at timestamptz NOT NULL DEFAULT now(),
|
|
432
|
+
trial_ends_at timestamptz NOT NULL DEFAULT (now() + interval '30 days'),
|
|
433
|
+
last_refresh_error text,
|
|
434
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
435
|
+
)
|
|
436
|
+
`),await t.query("ALTER TABLE analytics_thorbit_entitlements ALTER COLUMN thorbit_org_id DROP NOT NULL"),await t.query("ALTER TABLE analytics_thorbit_entitlements ADD COLUMN IF NOT EXISTS trial_started_at timestamptz NOT NULL DEFAULT now()"),await t.query("ALTER TABLE analytics_thorbit_entitlements ADD COLUMN IF NOT EXISTS trial_ends_at timestamptz NOT NULL DEFAULT (now() + interval '30 days')"),await t.query("ALTER TABLE analytics_thorbit_entitlements DROP CONSTRAINT IF EXISTS analytics_thorbit_entitlements_connection_source_check"),await t.query("ALTER TABLE analytics_thorbit_entitlements ADD CONSTRAINT analytics_thorbit_entitlements_connection_source_check CHECK (connection_source IN ('trial','api_key','thorbit_session'))"),await t.query("ALTER TABLE analytics_thorbit_entitlements ALTER COLUMN minimum_monthly_recurring_revenue_cents SET DEFAULT 0"),await t.query(`
|
|
437
|
+
CREATE TABLE IF NOT EXISTS analytics_thorbit_connect_states (
|
|
438
|
+
state_hash text PRIMARY KEY,
|
|
439
|
+
user_id bigint NOT NULL,
|
|
440
|
+
expires_at timestamptz NOT NULL,
|
|
441
|
+
consumed_at timestamptz,
|
|
442
|
+
created_at timestamptz NOT NULL DEFAULT now()
|
|
443
|
+
)
|
|
444
|
+
`),await t.query("ALTER TABLE analytics_exports ADD COLUMN IF NOT EXISTS idempotency_key text"),await t.query("ALTER TABLE analytics_events ADD COLUMN IF NOT EXISTS country_code text"),await t.query("ALTER TABLE analytics_events ADD COLUMN IF NOT EXISTS region_code text"),await t.query(`
|
|
445
|
+
CREATE TABLE IF NOT EXISTS analytics_sessions (
|
|
446
|
+
id uuid PRIMARY KEY,
|
|
447
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
448
|
+
pixel_id uuid REFERENCES analytics_pixels(id) ON DELETE SET NULL,
|
|
449
|
+
visitor_hmac text NOT NULL,
|
|
450
|
+
session_hmac text NOT NULL,
|
|
451
|
+
person_id uuid REFERENCES analytics_people(id) ON DELETE SET NULL,
|
|
452
|
+
started_at timestamptz NOT NULL,
|
|
453
|
+
last_active_at timestamptz NOT NULL,
|
|
454
|
+
ended_at timestamptz,
|
|
455
|
+
duration_ms bigint NOT NULL DEFAULT 0 CHECK (duration_ms >= 0),
|
|
456
|
+
landing_path text,
|
|
457
|
+
exit_path text,
|
|
458
|
+
pageview_count integer NOT NULL DEFAULT 0,
|
|
459
|
+
event_count integer NOT NULL DEFAULT 0,
|
|
460
|
+
first_touch_id uuid,
|
|
461
|
+
last_touch_id uuid,
|
|
462
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
463
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
464
|
+
UNIQUE(site_id, session_hmac)
|
|
465
|
+
)
|
|
466
|
+
`),await t.query(`
|
|
467
|
+
CREATE TABLE IF NOT EXISTS analytics_attribution_touches (
|
|
468
|
+
id uuid PRIMARY KEY,
|
|
469
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
470
|
+
session_id uuid REFERENCES analytics_sessions(id) ON DELETE SET NULL,
|
|
471
|
+
person_id uuid REFERENCES analytics_people(id) ON DELETE SET NULL,
|
|
472
|
+
visitor_hmac text,
|
|
473
|
+
occurred_at timestamptz NOT NULL,
|
|
474
|
+
source text,
|
|
475
|
+
medium text,
|
|
476
|
+
campaign text,
|
|
477
|
+
referrer text,
|
|
478
|
+
click_ids jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
479
|
+
landing_path text,
|
|
480
|
+
touch_hash text NOT NULL,
|
|
481
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
482
|
+
UNIQUE(site_id, touch_hash)
|
|
483
|
+
)
|
|
484
|
+
`),await t.query(`
|
|
485
|
+
CREATE TABLE IF NOT EXISTS analytics_connections (
|
|
486
|
+
id uuid PRIMARY KEY,
|
|
487
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
488
|
+
provider text NOT NULL,
|
|
489
|
+
name text NOT NULL,
|
|
490
|
+
source_account_ref text NOT NULL,
|
|
491
|
+
service_connection_ref text,
|
|
492
|
+
secret_ciphertext text,
|
|
493
|
+
config jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
494
|
+
readiness text NOT NULL DEFAULT 'configured_unverified'
|
|
495
|
+
CHECK (readiness IN ('not_configured','configured_unverified','verified','live','degraded','disabled')),
|
|
496
|
+
last_verified_at timestamptz,
|
|
497
|
+
last_event_at timestamptz,
|
|
498
|
+
last_reconciled_at timestamptz,
|
|
499
|
+
last_error_code text,
|
|
500
|
+
verification_evidence text CHECK (verification_evidence IN ('synthetic','provider')),
|
|
501
|
+
last_verified_receipt_id uuid,
|
|
502
|
+
created_by_user_id bigint NOT NULL,
|
|
503
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
504
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
505
|
+
UNIQUE(site_id, provider, source_account_ref)
|
|
506
|
+
)
|
|
507
|
+
`),await t.query(`
|
|
508
|
+
CREATE TABLE IF NOT EXISTS analytics_webhook_receipts (
|
|
509
|
+
id uuid PRIMARY KEY,
|
|
510
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
511
|
+
connection_id uuid REFERENCES analytics_connections(id) ON DELETE SET NULL,
|
|
512
|
+
provider text NOT NULL,
|
|
513
|
+
source_account_ref text NOT NULL,
|
|
514
|
+
source_event_id text NOT NULL,
|
|
515
|
+
signature_status text NOT NULL CHECK (signature_status IN ('verified','invalid','not_supported')),
|
|
516
|
+
normalization_status text NOT NULL DEFAULT 'pending'
|
|
517
|
+
CHECK (normalization_status IN ('pending','accepted','rejected','replayed')),
|
|
518
|
+
payload_ciphertext text,
|
|
519
|
+
payload_expires_at timestamptz,
|
|
520
|
+
error_code text,
|
|
521
|
+
received_at timestamptz NOT NULL DEFAULT now(),
|
|
522
|
+
normalized_at timestamptz,
|
|
523
|
+
UNIQUE(site_id, provider, source_account_ref, source_event_id)
|
|
524
|
+
)
|
|
525
|
+
`),await t.query(`
|
|
526
|
+
CREATE TABLE IF NOT EXISTS analytics_external_events (
|
|
527
|
+
id uuid PRIMARY KEY,
|
|
528
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
529
|
+
receipt_id uuid REFERENCES analytics_webhook_receipts(id) ON DELETE SET NULL,
|
|
530
|
+
person_id uuid REFERENCES analytics_people(id) ON DELETE SET NULL,
|
|
531
|
+
source text NOT NULL,
|
|
532
|
+
source_account_ref text NOT NULL,
|
|
533
|
+
source_event_id text NOT NULL,
|
|
534
|
+
event_kind text NOT NULL CHECK (event_kind IN ('form','call','crm','transaction','custom_server','conversion','delivery')),
|
|
535
|
+
event_name text NOT NULL,
|
|
536
|
+
occurred_at timestamptz NOT NULL,
|
|
537
|
+
visitor_hmac text,
|
|
538
|
+
session_hmac text,
|
|
539
|
+
call_id text,
|
|
540
|
+
deal_id text,
|
|
541
|
+
order_id text,
|
|
542
|
+
value_minor bigint,
|
|
543
|
+
currency text,
|
|
544
|
+
click_ids jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
545
|
+
properties jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
546
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
547
|
+
UNIQUE(site_id, source, source_account_ref, source_event_id)
|
|
548
|
+
)
|
|
549
|
+
`),await t.query(`
|
|
550
|
+
CREATE TABLE IF NOT EXISTS analytics_reconciliation_cursors (
|
|
551
|
+
connection_id uuid PRIMARY KEY REFERENCES analytics_connections(id) ON DELETE CASCADE,
|
|
552
|
+
cursor jsonb,
|
|
553
|
+
health text NOT NULL DEFAULT 'webhook_only'
|
|
554
|
+
CHECK (health IN ('webhook_only','reconciled','degraded')),
|
|
555
|
+
records_replayed bigint NOT NULL DEFAULT 0,
|
|
556
|
+
last_started_at timestamptz,
|
|
557
|
+
last_completed_at timestamptz,
|
|
558
|
+
last_source_event_id text,
|
|
559
|
+
last_error_code text,
|
|
560
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
561
|
+
)
|
|
562
|
+
`),await t.query(`
|
|
563
|
+
CREATE TABLE IF NOT EXISTS analytics_reconciliation_jobs (
|
|
564
|
+
id uuid PRIMARY KEY,
|
|
565
|
+
connection_id uuid NOT NULL UNIQUE REFERENCES analytics_connections(id) ON DELETE CASCADE,
|
|
566
|
+
status text NOT NULL DEFAULT 'pending' CHECK (status IN ('pending','leased')),
|
|
567
|
+
attempts integer NOT NULL DEFAULT 0,
|
|
568
|
+
next_attempt_at timestamptz NOT NULL DEFAULT now(),
|
|
569
|
+
lease_until timestamptz,
|
|
570
|
+
last_error_code text,
|
|
571
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
572
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
573
|
+
)
|
|
574
|
+
`),await t.query(`
|
|
575
|
+
CREATE TABLE IF NOT EXISTS analytics_event_definitions (
|
|
576
|
+
id uuid PRIMARY KEY,
|
|
577
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
578
|
+
name text NOT NULL,
|
|
579
|
+
event_name text NOT NULL,
|
|
580
|
+
trigger_kind text NOT NULL CHECK (trigger_kind IN ('page','click','form','semantic','custom_server','call','crm_stage','transaction')),
|
|
581
|
+
definition jsonb NOT NULL,
|
|
582
|
+
version integer NOT NULL DEFAULT 1,
|
|
583
|
+
status text NOT NULL DEFAULT 'active' CHECK (status IN ('active','disabled','archived')),
|
|
584
|
+
created_by_user_id bigint NOT NULL,
|
|
585
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
586
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
587
|
+
UNIQUE(site_id, name)
|
|
588
|
+
)
|
|
589
|
+
`),await t.query(`
|
|
590
|
+
DO $$
|
|
591
|
+
BEGIN
|
|
592
|
+
IF EXISTS (
|
|
593
|
+
SELECT 1
|
|
594
|
+
FROM pg_constraint
|
|
595
|
+
WHERE conrelid = 'analytics_event_definitions'::regclass
|
|
596
|
+
AND conname = 'analytics_event_definitions_trigger_kind_check'
|
|
597
|
+
AND pg_get_constraintdef(oid) NOT LIKE '%semantic%'
|
|
598
|
+
) THEN
|
|
599
|
+
ALTER TABLE analytics_event_definitions
|
|
600
|
+
DROP CONSTRAINT analytics_event_definitions_trigger_kind_check;
|
|
601
|
+
ALTER TABLE analytics_event_definitions
|
|
602
|
+
ADD CONSTRAINT analytics_event_definitions_trigger_kind_check
|
|
603
|
+
CHECK (trigger_kind IN ('page','click','form','semantic','custom_server','call','crm_stage','transaction'));
|
|
604
|
+
END IF;
|
|
605
|
+
END $$
|
|
606
|
+
`),await t.query(`
|
|
607
|
+
CREATE TABLE IF NOT EXISTS analytics_conversion_rules (
|
|
608
|
+
id uuid PRIMARY KEY,
|
|
609
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
610
|
+
name text NOT NULL,
|
|
611
|
+
conversion_kind text NOT NULL,
|
|
612
|
+
condition jsonb NOT NULL,
|
|
613
|
+
default_value_minor bigint,
|
|
614
|
+
default_currency text NOT NULL DEFAULT 'USD',
|
|
615
|
+
version integer NOT NULL DEFAULT 1,
|
|
616
|
+
status text NOT NULL DEFAULT 'active' CHECK (status IN ('active','disabled','archived')),
|
|
617
|
+
created_by_user_id bigint NOT NULL,
|
|
618
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
619
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
620
|
+
UNIQUE(site_id, name)
|
|
621
|
+
)
|
|
622
|
+
`),await t.query(`
|
|
623
|
+
CREATE TABLE IF NOT EXISTS analytics_restricted_data (
|
|
624
|
+
id uuid PRIMARY KEY,
|
|
625
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
626
|
+
person_id uuid REFERENCES analytics_people(id) ON DELETE CASCADE,
|
|
627
|
+
purpose text NOT NULL,
|
|
628
|
+
subject_ref text NOT NULL,
|
|
629
|
+
payload_ciphertext text NOT NULL,
|
|
630
|
+
expires_at timestamptz NOT NULL,
|
|
631
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
632
|
+
UNIQUE(site_id, purpose, subject_ref)
|
|
633
|
+
)
|
|
634
|
+
`),await t.query(`
|
|
635
|
+
CREATE TABLE IF NOT EXISTS analytics_identity_tombstones (
|
|
636
|
+
id uuid PRIMARY KEY,
|
|
637
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
638
|
+
person_id uuid,
|
|
639
|
+
kind text NOT NULL,
|
|
640
|
+
value_hmac text NOT NULL,
|
|
641
|
+
reason text NOT NULL CHECK (reason IN ('subject_deletion')),
|
|
642
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
643
|
+
UNIQUE(site_id,kind,value_hmac)
|
|
644
|
+
)
|
|
645
|
+
`),await t.query(`
|
|
646
|
+
CREATE TABLE IF NOT EXISTS analytics_identity_profile_access_audits (
|
|
647
|
+
id uuid PRIMARY KEY,
|
|
648
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
649
|
+
person_id uuid,
|
|
650
|
+
actor_user_id bigint NOT NULL,
|
|
651
|
+
subject_ref_hmac text NOT NULL,
|
|
652
|
+
purpose text NOT NULL CHECK (purpose IN ('journey_identity_display')),
|
|
653
|
+
result text NOT NULL CHECK (result IN ('returned_masked','not_found','unavailable','rate_limited')),
|
|
654
|
+
created_at timestamptz NOT NULL DEFAULT now()
|
|
655
|
+
)
|
|
656
|
+
`),await t.query("CREATE INDEX IF NOT EXISTS analytics_identity_profile_access_rate ON analytics_identity_profile_access_audits(site_id,actor_user_id,created_at DESC)"),await t.query("ALTER TABLE analytics_activation_destinations ADD COLUMN IF NOT EXISTS readiness text NOT NULL DEFAULT 'configured_unverified' CHECK (readiness IN ('not_configured','configured_unverified','verified','live','degraded','disabled'))"),await t.query("ALTER TABLE analytics_activation_destinations ADD COLUMN IF NOT EXISTS last_tested_at timestamptz"),await t.query("ALTER TABLE analytics_activation_destinations ADD COLUMN IF NOT EXISTS last_receipt_id text"),await t.query("ALTER TABLE analytics_activation_destinations ADD COLUMN IF NOT EXISTS provider_config jsonb NOT NULL DEFAULT '{}'::jsonb"),await t.query("ALTER TABLE analytics_activation_destinations ADD COLUMN IF NOT EXISTS mapping_version integer NOT NULL DEFAULT 1"),await t.query("ALTER TABLE analytics_activation_destinations ADD COLUMN IF NOT EXISTS automatic_delivery_enabled boolean NOT NULL DEFAULT false"),await t.query("ALTER TABLE analytics_activation_jobs ADD COLUMN IF NOT EXISTS lease_until timestamptz"),await t.query("ALTER TABLE analytics_activation_jobs ADD COLUMN IF NOT EXISTS mapping_version integer NOT NULL DEFAULT 1"),await t.query("ALTER TABLE analytics_activation_jobs ADD COLUMN IF NOT EXISTS external_receipt_id text"),await t.query("ALTER TABLE analytics_activation_jobs ADD COLUMN IF NOT EXISTS response_code integer"),await t.query("ALTER TABLE analytics_activation_jobs ADD COLUMN IF NOT EXISTS response_category text"),await t.query("ALTER TABLE analytics_activation_jobs ADD COLUMN IF NOT EXISTS dead_lettered_at timestamptz"),await t.query(`
|
|
657
|
+
CREATE TABLE IF NOT EXISTS analytics_activation_destination_tests (
|
|
658
|
+
id uuid PRIMARY KEY,
|
|
659
|
+
destination_id uuid NOT NULL REFERENCES analytics_activation_destinations(id) ON DELETE CASCADE,
|
|
660
|
+
mapping_version integer NOT NULL,
|
|
661
|
+
provider_request_id text,
|
|
662
|
+
provider_event_id text,
|
|
663
|
+
warnings jsonb NOT NULL DEFAULT '[]'::jsonb,
|
|
664
|
+
safe_error_category text,
|
|
665
|
+
request_started_at timestamptz NOT NULL DEFAULT now(),
|
|
666
|
+
response_received_at timestamptz,
|
|
667
|
+
next_diagnostic_at timestamptz,
|
|
668
|
+
actor text NOT NULL,
|
|
669
|
+
accepted boolean NOT NULL DEFAULT false,
|
|
670
|
+
validation_only boolean NOT NULL DEFAULT false,
|
|
671
|
+
created_at timestamptz NOT NULL DEFAULT now()
|
|
672
|
+
)
|
|
673
|
+
`),await t.query(`
|
|
674
|
+
CREATE TABLE IF NOT EXISTS analytics_activation_delivery_attempts (
|
|
675
|
+
id uuid PRIMARY KEY,
|
|
676
|
+
job_id uuid NOT NULL REFERENCES analytics_activation_jobs(id) ON DELETE CASCADE,
|
|
677
|
+
destination_id uuid NOT NULL REFERENCES analytics_activation_destinations(id) ON DELETE CASCADE,
|
|
678
|
+
mapping_version integer NOT NULL,
|
|
679
|
+
attempt_number integer NOT NULL,
|
|
680
|
+
provider_request_id text,
|
|
681
|
+
provider_event_id text,
|
|
682
|
+
warnings jsonb NOT NULL DEFAULT '[]'::jsonb,
|
|
683
|
+
safe_error_category text,
|
|
684
|
+
request_started_at timestamptz NOT NULL DEFAULT now(),
|
|
685
|
+
response_received_at timestamptz,
|
|
686
|
+
next_diagnostic_at timestamptz,
|
|
687
|
+
actor text NOT NULL,
|
|
688
|
+
accepted boolean NOT NULL DEFAULT false,
|
|
689
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
690
|
+
UNIQUE(job_id, attempt_number)
|
|
691
|
+
)
|
|
692
|
+
`),await t.query("ALTER TABLE analytics_crm_import_rows ADD COLUMN IF NOT EXISTS next_attempt_at timestamptz NOT NULL DEFAULT now()"),await t.query("ALTER TABLE analytics_crm_import_rows ADD COLUMN IF NOT EXISTS lease_until timestamptz"),await t.query("ALTER TABLE analytics_crm_import_rows ADD COLUMN IF NOT EXISTS expires_at timestamptz NOT NULL DEFAULT (now() + interval '7 days')"),await t.query("ALTER TABLE analytics_conversions ADD COLUMN IF NOT EXISTS rule_id uuid REFERENCES analytics_conversion_rules(id) ON DELETE SET NULL"),await t.query("ALTER TABLE analytics_conversions ADD COLUMN IF NOT EXISTS rule_version integer"),await t.query("ALTER TABLE analytics_conversions ADD COLUMN IF NOT EXISTS order_id text"),await t.query("ALTER TABLE analytics_connections ADD COLUMN IF NOT EXISTS verification_evidence text CHECK (verification_evidence IN ('synthetic','provider'))"),await t.query("ALTER TABLE analytics_connections ADD COLUMN IF NOT EXISTS last_verified_receipt_id uuid"),await t.query("ALTER TABLE analytics_sessions ADD COLUMN IF NOT EXISTS public_ref text"),await t.query("ALTER TABLE analytics_people ADD COLUMN IF NOT EXISTS public_ref text"),await t.query(`UPDATE analytics_people
|
|
693
|
+
SET public_ref = 'xrp_' || substr(md5(random()::text || clock_timestamp()::text || id::text || site_id::text), 1, 24)
|
|
694
|
+
WHERE public_ref IS NULL`),await t.query("ALTER TABLE analytics_events ADD COLUMN IF NOT EXISTS event_definition_id uuid REFERENCES analytics_event_definitions(id) ON DELETE SET NULL"),await t.query("ALTER TABLE analytics_events ADD COLUMN IF NOT EXISTS event_definition_version integer"),await t.query("ALTER TABLE analytics_conversions ADD COLUMN IF NOT EXISTS value_source text NOT NULL DEFAULT 'default_zero' CHECK (value_source IN ('absent','default_zero','fixed','dynamic','conditional_override','provider','verified_revenue','restatement'))"),await t.query("ALTER TABLE analytics_conversions ADD COLUMN IF NOT EXISTS value_provenance jsonb NOT NULL DEFAULT '{}'::jsonb"),await t.query("ALTER TABLE analytics_attribution_touches ADD COLUMN IF NOT EXISTS touch_kind text NOT NULL DEFAULT 'click' CHECK (touch_kind IN ('click','view','direct','organic','referral','email','sms','call','other'))"),await t.query("ALTER TABLE analytics_attribution_touches ADD COLUMN IF NOT EXISTS platform text"),await t.query("ALTER TABLE analytics_attribution_touches ADD COLUMN IF NOT EXISTS account_id text"),await t.query("ALTER TABLE analytics_attribution_touches ADD COLUMN IF NOT EXISTS campaign_id text"),await t.query("ALTER TABLE analytics_attribution_touches ADD COLUMN IF NOT EXISTS ad_set_id text"),await t.query("ALTER TABLE analytics_attribution_touches ADD COLUMN IF NOT EXISTS ad_id text"),await t.query("ALTER TABLE analytics_attribution_touches ADD COLUMN IF NOT EXISTS creative_id text"),await t.query("ALTER TABLE analytics_attribution_touches ADD COLUMN IF NOT EXISTS placement text"),await t.query(`
|
|
695
|
+
CREATE TABLE IF NOT EXISTS analytics_onboarding_presentations (
|
|
696
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
697
|
+
user_id bigint NOT NULL,
|
|
698
|
+
preferences jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
699
|
+
last_presented_step text,
|
|
700
|
+
dismissed_help jsonb NOT NULL DEFAULT '[]'::jsonb,
|
|
701
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
702
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
703
|
+
PRIMARY KEY(site_id, user_id)
|
|
704
|
+
)
|
|
705
|
+
`),await t.query(`
|
|
706
|
+
CREATE TABLE IF NOT EXISTS analytics_funnel_definitions (
|
|
707
|
+
id uuid PRIMARY KEY,
|
|
708
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
709
|
+
name text NOT NULL,
|
|
710
|
+
stages jsonb NOT NULL CHECK (jsonb_typeof(stages) = 'array'),
|
|
711
|
+
version integer NOT NULL DEFAULT 1 CHECK (version > 0),
|
|
712
|
+
status text NOT NULL DEFAULT 'active' CHECK (status IN ('active','disabled','archived')),
|
|
713
|
+
is_default boolean NOT NULL DEFAULT false,
|
|
714
|
+
created_by_user_id bigint NOT NULL,
|
|
715
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
716
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
717
|
+
UNIQUE(site_id, name, version)
|
|
718
|
+
)
|
|
719
|
+
`),await t.query(`
|
|
720
|
+
CREATE TABLE IF NOT EXISTS analytics_saved_views (
|
|
721
|
+
id uuid PRIMARY KEY,
|
|
722
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
723
|
+
owner_user_id bigint NOT NULL,
|
|
724
|
+
name text NOT NULL,
|
|
725
|
+
report_type text NOT NULL,
|
|
726
|
+
schema_version integer NOT NULL DEFAULT 1 CHECK (schema_version > 0),
|
|
727
|
+
settings jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
728
|
+
is_default boolean NOT NULL DEFAULT false,
|
|
729
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
730
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
731
|
+
UNIQUE(site_id, owner_user_id, report_type, name)
|
|
732
|
+
)
|
|
733
|
+
`),await t.query(`
|
|
734
|
+
CREATE TABLE IF NOT EXISTS analytics_product_milestones (
|
|
735
|
+
id uuid PRIMARY KEY,
|
|
736
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
737
|
+
user_id bigint NOT NULL,
|
|
738
|
+
milestone text NOT NULL,
|
|
739
|
+
evidence_kind text NOT NULL,
|
|
740
|
+
evidence_ref text,
|
|
741
|
+
occurred_at timestamptz NOT NULL DEFAULT now(),
|
|
742
|
+
safe_metadata jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
743
|
+
UNIQUE(site_id, user_id, milestone)
|
|
744
|
+
)
|
|
745
|
+
`),await t.query(`
|
|
746
|
+
CREATE TABLE IF NOT EXISTS analytics_scheduled_occurrences (
|
|
747
|
+
id uuid PRIMARY KEY,
|
|
748
|
+
lane_key text NOT NULL,
|
|
749
|
+
scheduled_for timestamptz NOT NULL,
|
|
750
|
+
status text NOT NULL DEFAULT 'pending' CHECK (status IN ('pending','running','retrying','succeeded','dead_letter')),
|
|
751
|
+
attempt_count integer NOT NULL DEFAULT 0 CHECK (attempt_count >= 0),
|
|
752
|
+
max_attempts integer NOT NULL DEFAULT 5 CHECK (max_attempts BETWEEN 1 AND 20),
|
|
753
|
+
next_attempt_at timestamptz NOT NULL,
|
|
754
|
+
lease_owner text,
|
|
755
|
+
lease_until timestamptz,
|
|
756
|
+
started_at timestamptz,
|
|
757
|
+
completed_at timestamptz,
|
|
758
|
+
dead_lettered_at timestamptz,
|
|
759
|
+
last_error_code text,
|
|
760
|
+
result_summary jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
761
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
762
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
763
|
+
UNIQUE(lane_key, scheduled_for)
|
|
764
|
+
)
|
|
765
|
+
`),await t.query(`
|
|
766
|
+
CREATE TABLE IF NOT EXISTS analytics_purpose_policies (
|
|
767
|
+
id uuid PRIMARY KEY,
|
|
768
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
769
|
+
purpose text NOT NULL CHECK (purpose IN ('necessary','site_analytics','enhanced_matching','advertising_measurement')),
|
|
770
|
+
policy_version integer NOT NULL CHECK (policy_version > 0),
|
|
771
|
+
jurisdiction_profile text NOT NULL,
|
|
772
|
+
retention_days integer CHECK (retention_days IS NULL OR retention_days > 0),
|
|
773
|
+
enabled boolean NOT NULL DEFAULT true,
|
|
774
|
+
policy jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
775
|
+
created_by_user_id bigint NOT NULL,
|
|
776
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
777
|
+
retired_at timestamptz,
|
|
778
|
+
UNIQUE(site_id, purpose, policy_version, jurisdiction_profile)
|
|
779
|
+
)
|
|
780
|
+
`),await t.query(`
|
|
781
|
+
CREATE TABLE IF NOT EXISTS analytics_consent_receipts (
|
|
782
|
+
id uuid PRIMARY KEY,
|
|
783
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
784
|
+
pixel_id uuid REFERENCES analytics_pixels(id) ON DELETE SET NULL,
|
|
785
|
+
subject_ref text NOT NULL,
|
|
786
|
+
policy_version integer NOT NULL CHECK (policy_version > 0),
|
|
787
|
+
jurisdiction_profile text NOT NULL,
|
|
788
|
+
necessary boolean NOT NULL DEFAULT true CHECK (necessary),
|
|
789
|
+
site_analytics boolean NOT NULL DEFAULT false,
|
|
790
|
+
enhanced_matching boolean NOT NULL DEFAULT false,
|
|
791
|
+
advertising_measurement boolean NOT NULL DEFAULT false,
|
|
792
|
+
gpc boolean NOT NULL DEFAULT false,
|
|
793
|
+
action text NOT NULL CHECK (action IN ('accept_all','reject_all','save_choices','withdraw','gpc')),
|
|
794
|
+
source text NOT NULL CHECK (source IN ('gateway','api','gpc','system')),
|
|
795
|
+
presented_at timestamptz,
|
|
796
|
+
occurred_at timestamptz NOT NULL,
|
|
797
|
+
expires_at timestamptz,
|
|
798
|
+
supersedes_id uuid REFERENCES analytics_consent_receipts(id) ON DELETE SET NULL,
|
|
799
|
+
created_at timestamptz NOT NULL DEFAULT now()
|
|
800
|
+
)
|
|
801
|
+
`),await t.query("ALTER TABLE analytics_consent_receipts ADD COLUMN IF NOT EXISTS presented_at timestamptz"),await t.query(`
|
|
802
|
+
DO $$
|
|
803
|
+
BEGIN
|
|
804
|
+
IF NOT EXISTS (
|
|
805
|
+
SELECT 1 FROM pg_constraint
|
|
806
|
+
WHERE conrelid = 'analytics_consent_receipts'::regclass
|
|
807
|
+
AND conname = 'analytics_consent_receipts_presentation_order_check'
|
|
808
|
+
) THEN
|
|
809
|
+
ALTER TABLE analytics_consent_receipts
|
|
810
|
+
ADD CONSTRAINT analytics_consent_receipts_presentation_order_check
|
|
811
|
+
CHECK (presented_at IS NULL OR presented_at <= occurred_at);
|
|
812
|
+
END IF;
|
|
813
|
+
END $$
|
|
814
|
+
`),await t.query(`
|
|
815
|
+
CREATE TABLE IF NOT EXISTS analytics_consent_current_state (
|
|
816
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
817
|
+
subject_ref text NOT NULL,
|
|
818
|
+
latest_receipt_id uuid NOT NULL REFERENCES analytics_consent_receipts(id) ON DELETE CASCADE,
|
|
819
|
+
policy_version integer NOT NULL CHECK (policy_version > 0),
|
|
820
|
+
jurisdiction_profile text NOT NULL,
|
|
821
|
+
necessary boolean NOT NULL DEFAULT true CHECK (necessary),
|
|
822
|
+
site_analytics boolean NOT NULL DEFAULT false,
|
|
823
|
+
enhanced_matching boolean NOT NULL DEFAULT false,
|
|
824
|
+
advertising_measurement boolean NOT NULL DEFAULT false,
|
|
825
|
+
gpc boolean NOT NULL DEFAULT false,
|
|
826
|
+
effective_at timestamptz NOT NULL,
|
|
827
|
+
expires_at timestamptz,
|
|
828
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
829
|
+
PRIMARY KEY(site_id, subject_ref)
|
|
830
|
+
)
|
|
831
|
+
`),await t.query("ALTER TABLE analytics_restricted_data ADD COLUMN IF NOT EXISTS consent_receipt_id uuid REFERENCES analytics_consent_receipts(id) ON DELETE CASCADE"),await t.query(`
|
|
832
|
+
CREATE TABLE IF NOT EXISTS analytics_identity_namespaces (
|
|
833
|
+
id uuid PRIMARY KEY,
|
|
834
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
835
|
+
name text NOT NULL,
|
|
836
|
+
status text NOT NULL DEFAULT 'draft' CHECK (status IN ('draft','approved','disabled','archived')),
|
|
837
|
+
approved_hosts jsonb NOT NULL DEFAULT '[]'::jsonb CHECK (jsonb_typeof(approved_hosts) = 'array'),
|
|
838
|
+
approved_sources jsonb NOT NULL DEFAULT '[]'::jsonb CHECK (jsonb_typeof(approved_sources) = 'array'),
|
|
839
|
+
version integer NOT NULL DEFAULT 1 CHECK (version > 0),
|
|
840
|
+
approved_by_user_id bigint,
|
|
841
|
+
approved_at timestamptz,
|
|
842
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
843
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
844
|
+
UNIQUE(site_id, name, version),
|
|
845
|
+
UNIQUE(id, site_id)
|
|
846
|
+
)
|
|
847
|
+
`),await t.query(`
|
|
848
|
+
CREATE TABLE IF NOT EXISTS analytics_identity_sources (
|
|
849
|
+
id uuid PRIMARY KEY,
|
|
850
|
+
namespace_id uuid NOT NULL,
|
|
851
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
852
|
+
source_kind text NOT NULL CHECK (source_kind IN ('pixel','host','form','crm','call','server','import')),
|
|
853
|
+
source_ref text NOT NULL,
|
|
854
|
+
status text NOT NULL DEFAULT 'approved' CHECK (status IN ('pending','approved','revoked')),
|
|
855
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
856
|
+
revoked_at timestamptz,
|
|
857
|
+
FOREIGN KEY(namespace_id, site_id) REFERENCES analytics_identity_namespaces(id, site_id) ON DELETE CASCADE,
|
|
858
|
+
UNIQUE(namespace_id, source_kind, source_ref)
|
|
859
|
+
)
|
|
860
|
+
`),await t.query("ALTER TABLE analytics_people ADD COLUMN IF NOT EXISTS namespace_id uuid"),await t.query("ALTER TABLE analytics_identity_nodes ADD COLUMN IF NOT EXISTS namespace_id uuid"),await t.query("ALTER TABLE analytics_identity_edges ADD COLUMN IF NOT EXISTS namespace_id uuid"),await t.query("CREATE UNIQUE INDEX IF NOT EXISTS analytics_people_site_ref ON analytics_people(id, site_id)"),await t.query("CREATE UNIQUE INDEX IF NOT EXISTS analytics_identity_nodes_site_ref ON analytics_identity_nodes(id, site_id)"),await t.query("CREATE UNIQUE INDEX IF NOT EXISTS analytics_identity_nodes_namespace_ref ON analytics_identity_nodes(id, site_id, namespace_id)"),await t.query("CREATE UNIQUE INDEX IF NOT EXISTS analytics_people_namespace_ref ON analytics_people(id, site_id, namespace_id)"),await t.query(`
|
|
861
|
+
DO $$
|
|
862
|
+
BEGIN
|
|
863
|
+
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname='analytics_people_namespace_site_fkey') THEN
|
|
864
|
+
ALTER TABLE analytics_people ADD CONSTRAINT analytics_people_namespace_site_fkey
|
|
865
|
+
FOREIGN KEY(namespace_id, site_id) REFERENCES analytics_identity_namespaces(id, site_id)
|
|
866
|
+
ON DELETE RESTRICT NOT VALID;
|
|
867
|
+
END IF;
|
|
868
|
+
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname='analytics_identity_nodes_namespace_site_fkey') THEN
|
|
869
|
+
ALTER TABLE analytics_identity_nodes ADD CONSTRAINT analytics_identity_nodes_namespace_site_fkey
|
|
870
|
+
FOREIGN KEY(namespace_id, site_id) REFERENCES analytics_identity_namespaces(id, site_id)
|
|
871
|
+
ON DELETE RESTRICT NOT VALID;
|
|
872
|
+
END IF;
|
|
873
|
+
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname='analytics_identity_edges_person_site_fkey') THEN
|
|
874
|
+
ALTER TABLE analytics_identity_edges ADD CONSTRAINT analytics_identity_edges_person_site_fkey
|
|
875
|
+
FOREIGN KEY(person_id, site_id) REFERENCES analytics_people(id, site_id)
|
|
876
|
+
ON DELETE CASCADE NOT VALID;
|
|
877
|
+
END IF;
|
|
878
|
+
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname='analytics_identity_edges_node_site_fkey') THEN
|
|
879
|
+
ALTER TABLE analytics_identity_edges ADD CONSTRAINT analytics_identity_edges_node_site_fkey
|
|
880
|
+
FOREIGN KEY(identity_node_id, site_id) REFERENCES analytics_identity_nodes(id, site_id)
|
|
881
|
+
ON DELETE CASCADE NOT VALID;
|
|
882
|
+
END IF;
|
|
883
|
+
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname='analytics_identity_edges_namespace_site_fkey') THEN
|
|
884
|
+
ALTER TABLE analytics_identity_edges ADD CONSTRAINT analytics_identity_edges_namespace_site_fkey
|
|
885
|
+
FOREIGN KEY(namespace_id, site_id) REFERENCES analytics_identity_namespaces(id, site_id)
|
|
886
|
+
ON DELETE RESTRICT NOT VALID;
|
|
887
|
+
END IF;
|
|
888
|
+
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname='analytics_identity_edges_person_namespace_fkey') THEN
|
|
889
|
+
ALTER TABLE analytics_identity_edges ADD CONSTRAINT analytics_identity_edges_person_namespace_fkey
|
|
890
|
+
FOREIGN KEY(person_id, site_id, namespace_id) REFERENCES analytics_people(id, site_id, namespace_id)
|
|
891
|
+
ON DELETE CASCADE NOT VALID;
|
|
892
|
+
END IF;
|
|
893
|
+
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname='analytics_identity_edges_node_namespace_fkey') THEN
|
|
894
|
+
ALTER TABLE analytics_identity_edges ADD CONSTRAINT analytics_identity_edges_node_namespace_fkey
|
|
895
|
+
FOREIGN KEY(identity_node_id, site_id, namespace_id) REFERENCES analytics_identity_nodes(id, site_id, namespace_id)
|
|
896
|
+
ON DELETE CASCADE NOT VALID;
|
|
897
|
+
END IF;
|
|
898
|
+
END $$
|
|
899
|
+
`);for(let e of[["analytics_people","analytics_people_namespace_site_fkey"],["analytics_identity_nodes","analytics_identity_nodes_namespace_site_fkey"],["analytics_identity_edges","analytics_identity_edges_person_site_fkey"],["analytics_identity_edges","analytics_identity_edges_node_site_fkey"],["analytics_identity_edges","analytics_identity_edges_namespace_site_fkey"],["analytics_identity_edges","analytics_identity_edges_person_namespace_fkey"],["analytics_identity_edges","analytics_identity_edges_node_namespace_fkey"]])await t.query(`ALTER TABLE ${e[0]} VALIDATE CONSTRAINT ${e[1]}`);await t.query("ALTER TABLE analytics_people DROP CONSTRAINT IF EXISTS analytics_people_namespace_id_fkey"),await t.query("ALTER TABLE analytics_identity_nodes DROP CONSTRAINT IF EXISTS analytics_identity_nodes_namespace_id_fkey"),await t.query("ALTER TABLE analytics_identity_edges DROP CONSTRAINT IF EXISTS analytics_identity_edges_namespace_id_fkey"),await t.query("ALTER TABLE analytics_identity_edges DROP CONSTRAINT IF EXISTS analytics_identity_edges_person_id_fkey"),await t.query("ALTER TABLE analytics_identity_edges DROP CONSTRAINT IF EXISTS analytics_identity_edges_identity_node_id_fkey"),await t.query(`
|
|
900
|
+
CREATE TABLE IF NOT EXISTS analytics_candidate_associations (
|
|
901
|
+
id uuid PRIMARY KEY,
|
|
902
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
903
|
+
namespace_id uuid NOT NULL,
|
|
904
|
+
left_identity_node_id uuid NOT NULL,
|
|
905
|
+
right_identity_node_id uuid NOT NULL,
|
|
906
|
+
confidence numeric(5,4) NOT NULL CHECK (confidence >= 0 AND confidence <= 1),
|
|
907
|
+
confidence_band text NOT NULL CHECK (confidence_band IN ('high','medium','low','ineligible')),
|
|
908
|
+
status text NOT NULL DEFAULT 'active' CHECK (status IN ('active','expired','rejected','promoted','deleted')),
|
|
909
|
+
consent_receipt_id uuid NOT NULL REFERENCES analytics_consent_receipts(id) ON DELETE RESTRICT,
|
|
910
|
+
model_version text NOT NULL,
|
|
911
|
+
first_observed_at timestamptz NOT NULL,
|
|
912
|
+
last_observed_at timestamptz NOT NULL,
|
|
913
|
+
expires_at timestamptz NOT NULL,
|
|
914
|
+
promoted_person_id uuid,
|
|
915
|
+
promoted_at timestamptz,
|
|
916
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
917
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
918
|
+
CHECK (left_identity_node_id <> right_identity_node_id),
|
|
919
|
+
FOREIGN KEY(namespace_id, site_id) REFERENCES analytics_identity_namespaces(id, site_id) ON DELETE CASCADE,
|
|
920
|
+
FOREIGN KEY(left_identity_node_id, site_id, namespace_id) REFERENCES analytics_identity_nodes(id, site_id, namespace_id) ON DELETE CASCADE,
|
|
921
|
+
FOREIGN KEY(right_identity_node_id, site_id, namespace_id) REFERENCES analytics_identity_nodes(id, site_id, namespace_id) ON DELETE CASCADE,
|
|
922
|
+
FOREIGN KEY(promoted_person_id, site_id, namespace_id) REFERENCES analytics_people(id, site_id, namespace_id) ON DELETE RESTRICT,
|
|
923
|
+
UNIQUE(namespace_id, left_identity_node_id, right_identity_node_id, model_version),
|
|
924
|
+
UNIQUE(id, namespace_id)
|
|
925
|
+
)
|
|
926
|
+
`),await t.query(`
|
|
927
|
+
CREATE TABLE IF NOT EXISTS analytics_candidate_evidence (
|
|
928
|
+
id uuid PRIMARY KEY,
|
|
929
|
+
association_id uuid NOT NULL,
|
|
930
|
+
namespace_id uuid NOT NULL,
|
|
931
|
+
feature_kind text NOT NULL CHECK (feature_kind IN ('device_bucket','browser_bucket','coarse_geo','network_bucket','temporal_pattern','journey_pattern')),
|
|
932
|
+
feature_hmac text NOT NULL,
|
|
933
|
+
contribution numeric(6,5) NOT NULL CHECK (contribution >= -1 AND contribution <= 1),
|
|
934
|
+
observed_at timestamptz NOT NULL,
|
|
935
|
+
expires_at timestamptz NOT NULL,
|
|
936
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
937
|
+
FOREIGN KEY(association_id, namespace_id) REFERENCES analytics_candidate_associations(id, namespace_id) ON DELETE CASCADE,
|
|
938
|
+
UNIQUE(association_id, feature_kind, feature_hmac)
|
|
939
|
+
)
|
|
940
|
+
`),await t.query(`
|
|
941
|
+
CREATE TABLE IF NOT EXISTS analytics_candidate_promotions (
|
|
942
|
+
id uuid PRIMARY KEY,
|
|
943
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
944
|
+
namespace_id uuid NOT NULL,
|
|
945
|
+
association_id uuid NOT NULL,
|
|
946
|
+
person_id uuid NOT NULL,
|
|
947
|
+
evidence_kind text NOT NULL CHECK (evidence_kind IN ('verified_email','verified_phone','crm_contact','signed_linker','authenticated_user')),
|
|
948
|
+
evidence_ref_hmac text NOT NULL,
|
|
949
|
+
promoted_by text NOT NULL CHECK (promoted_by IN ('system','user')),
|
|
950
|
+
promoted_by_user_id bigint,
|
|
951
|
+
promoted_at timestamptz NOT NULL DEFAULT now(),
|
|
952
|
+
FOREIGN KEY(namespace_id, site_id) REFERENCES analytics_identity_namespaces(id, site_id) ON DELETE CASCADE,
|
|
953
|
+
FOREIGN KEY(association_id, namespace_id) REFERENCES analytics_candidate_associations(id, namespace_id) ON DELETE CASCADE,
|
|
954
|
+
FOREIGN KEY(person_id, site_id, namespace_id) REFERENCES analytics_people(id, site_id, namespace_id) ON DELETE CASCADE,
|
|
955
|
+
UNIQUE(association_id, person_id)
|
|
956
|
+
)
|
|
957
|
+
`),await t.query(`
|
|
958
|
+
CREATE TABLE IF NOT EXISTS analytics_identity_promotion_receipts (
|
|
959
|
+
id uuid PRIMARY KEY,
|
|
960
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
961
|
+
namespace_id uuid NOT NULL,
|
|
962
|
+
canonical_person_id uuid NOT NULL,
|
|
963
|
+
canonical_person_ref_hmac text NOT NULL,
|
|
964
|
+
evidence_kind text NOT NULL,
|
|
965
|
+
evidence_ref_hmac text NOT NULL,
|
|
966
|
+
prior_person_ids uuid[] NOT NULL DEFAULT '{}',
|
|
967
|
+
prior_identity_node_ids uuid[] NOT NULL DEFAULT '{}',
|
|
968
|
+
candidate_association_ids uuid[] NOT NULL DEFAULT '{}',
|
|
969
|
+
affected_history_counts jsonb NOT NULL DEFAULT '{}'::jsonb CHECK (jsonb_typeof(affected_history_counts)='object'),
|
|
970
|
+
schema_version text NOT NULL DEFAULT 'xray.identity-promotion.v1',
|
|
971
|
+
idempotency_key text NOT NULL,
|
|
972
|
+
promoted_at timestamptz NOT NULL,
|
|
973
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
974
|
+
FOREIGN KEY(namespace_id, site_id) REFERENCES analytics_identity_namespaces(id, site_id) ON DELETE CASCADE,
|
|
975
|
+
UNIQUE(site_id, idempotency_key)
|
|
976
|
+
)
|
|
977
|
+
`),await t.query("CREATE UNIQUE INDEX IF NOT EXISTS analytics_touches_site_ref ON analytics_attribution_touches(id, site_id)"),await t.query(`
|
|
978
|
+
CREATE TABLE IF NOT EXISTS analytics_candidate_attribution_touches (
|
|
979
|
+
id uuid PRIMARY KEY,
|
|
980
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
981
|
+
namespace_id uuid NOT NULL,
|
|
982
|
+
association_id uuid NOT NULL,
|
|
983
|
+
touch_id uuid NOT NULL,
|
|
984
|
+
eligibility text NOT NULL DEFAULT 'overlay_only' CHECK (eligibility IN ('overlay_only','review','ineligible','expired')),
|
|
985
|
+
association_confidence numeric(5,4) NOT NULL CHECK (association_confidence >= 0 AND association_confidence <= 1),
|
|
986
|
+
model_version text NOT NULL,
|
|
987
|
+
linked_at timestamptz NOT NULL DEFAULT now(),
|
|
988
|
+
expires_at timestamptz NOT NULL,
|
|
989
|
+
FOREIGN KEY(namespace_id, site_id) REFERENCES analytics_identity_namespaces(id, site_id) ON DELETE CASCADE,
|
|
990
|
+
FOREIGN KEY(association_id, namespace_id) REFERENCES analytics_candidate_associations(id, namespace_id) ON DELETE CASCADE,
|
|
991
|
+
FOREIGN KEY(touch_id, site_id) REFERENCES analytics_attribution_touches(id, site_id) ON DELETE CASCADE,
|
|
992
|
+
UNIQUE(association_id, touch_id, model_version)
|
|
993
|
+
)
|
|
994
|
+
`),await t.query(`
|
|
995
|
+
CREATE TABLE IF NOT EXISTS analytics_event_value_definitions (
|
|
996
|
+
id uuid PRIMARY KEY,
|
|
997
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
998
|
+
event_definition_id uuid NOT NULL REFERENCES analytics_event_definitions(id) ON DELETE CASCADE,
|
|
999
|
+
version integer NOT NULL CHECK (version > 0),
|
|
1000
|
+
base_mode text NOT NULL DEFAULT 'none' CHECK (base_mode IN ('none','fixed','dynamic')),
|
|
1001
|
+
fixed_value_minor bigint,
|
|
1002
|
+
currency text NOT NULL DEFAULT 'USD',
|
|
1003
|
+
dynamic_source jsonb,
|
|
1004
|
+
conditional_event_definition_id uuid REFERENCES analytics_event_definitions(id) ON DELETE SET NULL,
|
|
1005
|
+
conditional_window_days integer CHECK (conditional_window_days IS NULL OR conditional_window_days > 0),
|
|
1006
|
+
conditional_value_minor bigint,
|
|
1007
|
+
status text NOT NULL DEFAULT 'active' CHECK (status IN ('active','disabled','archived')),
|
|
1008
|
+
created_by_user_id bigint NOT NULL,
|
|
1009
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
1010
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
1011
|
+
CHECK ((base_mode = 'fixed') = (fixed_value_minor IS NOT NULL)),
|
|
1012
|
+
CHECK ((conditional_event_definition_id IS NULL) = (conditional_value_minor IS NULL)),
|
|
1013
|
+
UNIQUE(site_id, event_definition_id, version)
|
|
1014
|
+
)
|
|
1015
|
+
`),await t.query(`
|
|
1016
|
+
CREATE TABLE IF NOT EXISTS analytics_event_value_evaluations (
|
|
1017
|
+
id uuid PRIMARY KEY,
|
|
1018
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
1019
|
+
source_event_id uuid NOT NULL REFERENCES analytics_events(id) ON DELETE CASCADE,
|
|
1020
|
+
definition_id uuid REFERENCES analytics_event_value_definitions(id) ON DELETE SET NULL,
|
|
1021
|
+
definition_version integer,
|
|
1022
|
+
value_minor bigint,
|
|
1023
|
+
currency text,
|
|
1024
|
+
value_source text NOT NULL CHECK (value_source IN ('absent','default_zero','fixed','dynamic','conditional_override','provider','verified_revenue','restatement')),
|
|
1025
|
+
matched_event_id uuid REFERENCES analytics_events(id) ON DELETE SET NULL,
|
|
1026
|
+
provenance jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
1027
|
+
evaluated_at timestamptz NOT NULL DEFAULT now(),
|
|
1028
|
+
UNIQUE(site_id, source_event_id, definition_version)
|
|
1029
|
+
)
|
|
1030
|
+
`),await t.query(`
|
|
1031
|
+
CREATE TABLE IF NOT EXISTS analytics_data_class_policies (
|
|
1032
|
+
id uuid PRIMARY KEY,
|
|
1033
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
1034
|
+
data_class text NOT NULL,
|
|
1035
|
+
purpose text NOT NULL,
|
|
1036
|
+
jurisdiction_profile text NOT NULL,
|
|
1037
|
+
retention_days integer CHECK (retention_days IS NULL OR retention_days > 0),
|
|
1038
|
+
storage_kind text NOT NULL CHECK (storage_kind IN ('ordinary','pseudonymous','encrypted_restricted','ephemeral')),
|
|
1039
|
+
withdrawal_action text NOT NULL CHECK (withdrawal_action IN ('stop_future','expire','delete','retain_necessary')),
|
|
1040
|
+
deletion_action text NOT NULL CHECK (deletion_action IN ('delete','anonymize','retain_legal')),
|
|
1041
|
+
read_surface text NOT NULL,
|
|
1042
|
+
version integer NOT NULL DEFAULT 1 CHECK (version > 0),
|
|
1043
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
1044
|
+
UNIQUE(site_id, data_class, jurisdiction_profile, version)
|
|
1045
|
+
)
|
|
1046
|
+
`),await t.query(`
|
|
1047
|
+
CREATE TABLE IF NOT EXISTS analytics_privacy_audits (
|
|
1048
|
+
id uuid PRIMARY KEY,
|
|
1049
|
+
site_id uuid REFERENCES analytics_sites(id) ON DELETE SET NULL,
|
|
1050
|
+
namespace_ref text,
|
|
1051
|
+
subject_ref text,
|
|
1052
|
+
action text NOT NULL CHECK (action IN ('export_requested','export_completed','deletion_requested','deletion_completed','withdrawal_applied','retention_expired','candidate_promoted')),
|
|
1053
|
+
idempotency_key text NOT NULL,
|
|
1054
|
+
actor_user_id bigint,
|
|
1055
|
+
result_code text NOT NULL,
|
|
1056
|
+
affected_counts jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
1057
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
1058
|
+
completed_at timestamptz,
|
|
1059
|
+
UNIQUE(site_id, idempotency_key)
|
|
1060
|
+
)
|
|
1061
|
+
`),await t.query("ALTER TABLE analytics_privacy_audits ADD COLUMN IF NOT EXISTS request_fingerprint text"),await t.query(`
|
|
1062
|
+
CREATE TABLE IF NOT EXISTS analytics_crm_provider_capabilities (
|
|
1063
|
+
provider text NOT NULL CHECK (provider IN ('hubspot','salesforce','highlevel','zoho','pipedrive','keap')),
|
|
1064
|
+
contract_version integer NOT NULL CHECK (contract_version > 0),
|
|
1065
|
+
capabilities jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
1066
|
+
documentation_refs jsonb NOT NULL DEFAULT '[]'::jsonb,
|
|
1067
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
1068
|
+
retired_at timestamptz,
|
|
1069
|
+
PRIMARY KEY(provider, contract_version)
|
|
1070
|
+
)
|
|
1071
|
+
`),await t.query(`
|
|
1072
|
+
CREATE TABLE IF NOT EXISTS analytics_crm_provisioning_plans (
|
|
1073
|
+
id uuid PRIMARY KEY,
|
|
1074
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
1075
|
+
connection_id uuid NOT NULL REFERENCES analytics_connections(id) ON DELETE CASCADE,
|
|
1076
|
+
provider text NOT NULL CHECK (provider IN ('hubspot','salesforce','highlevel','zoho','pipedrive','keap')),
|
|
1077
|
+
contract_version integer NOT NULL CHECK (contract_version > 0),
|
|
1078
|
+
tenant_schema_version text,
|
|
1079
|
+
plan jsonb NOT NULL,
|
|
1080
|
+
status text NOT NULL DEFAULT 'preview' CHECK (status IN ('preview','approved','applying','verified','failed','reverted')),
|
|
1081
|
+
approved_by_user_id bigint,
|
|
1082
|
+
approved_at timestamptz,
|
|
1083
|
+
created_by_user_id bigint NOT NULL,
|
|
1084
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
1085
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
1086
|
+
)
|
|
1087
|
+
`),await t.query(`
|
|
1088
|
+
CREATE TABLE IF NOT EXISTS analytics_crm_provisioning_receipts (
|
|
1089
|
+
id uuid PRIMARY KEY,
|
|
1090
|
+
plan_id uuid NOT NULL REFERENCES analytics_crm_provisioning_plans(id) ON DELETE CASCADE,
|
|
1091
|
+
operation text NOT NULL CHECK (operation IN ('describe','discover','create_field','test_write','revert_test','verify')),
|
|
1092
|
+
status text NOT NULL CHECK (status IN ('accepted','rejected','failed','reverted')),
|
|
1093
|
+
external_object_ref text,
|
|
1094
|
+
safe_error_code text,
|
|
1095
|
+
warnings jsonb NOT NULL DEFAULT '[]'::jsonb,
|
|
1096
|
+
created_at timestamptz NOT NULL DEFAULT now()
|
|
1097
|
+
)
|
|
1098
|
+
`),await t.query(`
|
|
1099
|
+
CREATE TABLE IF NOT EXISTS analytics_crm_person_mappings (
|
|
1100
|
+
id uuid PRIMARY KEY,
|
|
1101
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
1102
|
+
namespace_id uuid NOT NULL,
|
|
1103
|
+
connection_id uuid NOT NULL REFERENCES analytics_connections(id) ON DELETE CASCADE,
|
|
1104
|
+
person_id uuid NOT NULL,
|
|
1105
|
+
provider_person_ref text NOT NULL,
|
|
1106
|
+
mapping_version integer NOT NULL DEFAULT 1 CHECK (mapping_version > 0),
|
|
1107
|
+
last_synced_at timestamptz,
|
|
1108
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
1109
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
1110
|
+
FOREIGN KEY(namespace_id, site_id) REFERENCES analytics_identity_namespaces(id, site_id) ON DELETE CASCADE,
|
|
1111
|
+
FOREIGN KEY(person_id, site_id, namespace_id) REFERENCES analytics_people(id, site_id, namespace_id) ON DELETE CASCADE,
|
|
1112
|
+
UNIQUE(connection_id, person_id),
|
|
1113
|
+
UNIQUE(connection_id, provider_person_ref)
|
|
1114
|
+
)
|
|
1115
|
+
`),await t.query(`
|
|
1116
|
+
CREATE TABLE IF NOT EXISTS analytics_crm_pipeline_mappings (
|
|
1117
|
+
id uuid PRIMARY KEY,
|
|
1118
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
1119
|
+
connection_id uuid NOT NULL REFERENCES analytics_connections(id) ON DELETE CASCADE,
|
|
1120
|
+
funnel_definition_id uuid REFERENCES analytics_funnel_definitions(id) ON DELETE SET NULL,
|
|
1121
|
+
version integer NOT NULL DEFAULT 1 CHECK (version > 0),
|
|
1122
|
+
mapping jsonb NOT NULL,
|
|
1123
|
+
status text NOT NULL DEFAULT 'draft' CHECK (status IN ('draft','active','disabled','archived')),
|
|
1124
|
+
created_by_user_id bigint NOT NULL,
|
|
1125
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
1126
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
1127
|
+
UNIQUE(connection_id, version)
|
|
1128
|
+
)
|
|
1129
|
+
`),await t.query(`
|
|
1130
|
+
CREATE TABLE IF NOT EXISTS analytics_crm_sync_state (
|
|
1131
|
+
connection_id uuid PRIMARY KEY REFERENCES analytics_connections(id) ON DELETE CASCADE,
|
|
1132
|
+
provisioning_plan_id uuid REFERENCES analytics_crm_provisioning_plans(id) ON DELETE SET NULL,
|
|
1133
|
+
cursor jsonb,
|
|
1134
|
+
direction text NOT NULL DEFAULT 'bidirectional' CHECK (direction IN ('inbound','outbound','bidirectional','webhook_only')),
|
|
1135
|
+
status text NOT NULL DEFAULT 'idle' CHECK (status IN ('idle','pending','running','healthy','degraded','disabled')),
|
|
1136
|
+
last_started_at timestamptz,
|
|
1137
|
+
last_completed_at timestamptz,
|
|
1138
|
+
last_success_at timestamptz,
|
|
1139
|
+
last_error_code text,
|
|
1140
|
+
next_sync_at timestamptz,
|
|
1141
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
1142
|
+
)
|
|
1143
|
+
`),await t.query(`
|
|
1144
|
+
CREATE TABLE IF NOT EXISTS analytics_crm_sync_receipts (
|
|
1145
|
+
id uuid PRIMARY KEY,
|
|
1146
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
1147
|
+
connection_id uuid NOT NULL REFERENCES analytics_connections(id) ON DELETE CASCADE,
|
|
1148
|
+
direction text NOT NULL CHECK (direction IN ('inbound','outbound')),
|
|
1149
|
+
operation text NOT NULL CHECK (operation IN ('person_upsert','activity_append','pipeline_update','schema_read','historical_pull')),
|
|
1150
|
+
subject_ref text,
|
|
1151
|
+
external_receipt_id text,
|
|
1152
|
+
status text NOT NULL CHECK (status IN ('accepted','rejected','failed','retrying')),
|
|
1153
|
+
safe_error_code text,
|
|
1154
|
+
occurred_at timestamptz NOT NULL DEFAULT now()
|
|
1155
|
+
)
|
|
1156
|
+
`),await t.query("ALTER TABLE analytics_crm_provisioning_plans ADD COLUMN IF NOT EXISTS public_plan_id text"),await t.query("ALTER TABLE analytics_crm_provisioning_plans ADD COLUMN IF NOT EXISTS schema_fingerprint text"),await t.query("ALTER TABLE analytics_crm_provisioning_plans ADD COLUMN IF NOT EXISTS request_fingerprint text"),await t.query("ALTER TABLE analytics_crm_provisioning_plans ADD COLUMN IF NOT EXISTS approved_field_ids jsonb NOT NULL DEFAULT '[]'::jsonb"),await t.query("ALTER TABLE analytics_crm_provisioning_receipts ADD COLUMN IF NOT EXISTS public_receipt_id text"),await t.query("ALTER TABLE analytics_crm_provisioning_receipts ADD COLUMN IF NOT EXISTS receipt jsonb NOT NULL DEFAULT '{}'::jsonb"),await t.query("CREATE UNIQUE INDEX IF NOT EXISTS analytics_crm_plan_public_scope ON analytics_crm_provisioning_plans(site_id, public_plan_id) WHERE public_plan_id IS NOT NULL"),await t.query("CREATE UNIQUE INDEX IF NOT EXISTS analytics_crm_receipt_public_id ON analytics_crm_provisioning_receipts(public_receipt_id) WHERE public_receipt_id IS NOT NULL"),await t.query(`
|
|
1157
|
+
CREATE TABLE IF NOT EXISTS analytics_crm_connection_capabilities (
|
|
1158
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
1159
|
+
connection_id uuid NOT NULL REFERENCES analytics_connections(id) ON DELETE CASCADE,
|
|
1160
|
+
provider text NOT NULL CHECK (provider IN ('hubspot','salesforce','highlevel','zoho','pipedrive','keap')),
|
|
1161
|
+
contract_version integer NOT NULL DEFAULT 1 CHECK (contract_version > 0),
|
|
1162
|
+
mode text NOT NULL CHECK (mode IN ('incremental_and_webhook','incremental_only','webhook_only','manual_backfill','unavailable')),
|
|
1163
|
+
schema_fingerprint text,
|
|
1164
|
+
allowed_operations jsonb NOT NULL DEFAULT '[]'::jsonb,
|
|
1165
|
+
evidence jsonb NOT NULL,
|
|
1166
|
+
verified_at timestamptz NOT NULL,
|
|
1167
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
1168
|
+
PRIMARY KEY(connection_id, contract_version),
|
|
1169
|
+
UNIQUE(site_id, connection_id, provider, contract_version)
|
|
1170
|
+
)
|
|
1171
|
+
`),await t.query(`
|
|
1172
|
+
CREATE TABLE IF NOT EXISTS analytics_crm_sync_runs (
|
|
1173
|
+
sync_id text PRIMARY KEY,
|
|
1174
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
1175
|
+
connection_id uuid NOT NULL REFERENCES analytics_connections(id) ON DELETE CASCADE,
|
|
1176
|
+
provider text NOT NULL CHECK (provider IN ('hubspot','salesforce','highlevel','zoho','pipedrive','keap')),
|
|
1177
|
+
mode text NOT NULL CHECK (mode IN ('initial','incremental','webhook')),
|
|
1178
|
+
state text NOT NULL DEFAULT 'queued' CHECK (state IN ('queued','running','completed','degraded','cancelled','webhook_only')),
|
|
1179
|
+
mapping jsonb NOT NULL,
|
|
1180
|
+
mapping_version integer NOT NULL DEFAULT 1 CHECK (mapping_version > 0),
|
|
1181
|
+
capability_evidence jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
1182
|
+
cursor jsonb,
|
|
1183
|
+
idempotency_key text NOT NULL,
|
|
1184
|
+
request_fingerprint text NOT NULL,
|
|
1185
|
+
last_started_at timestamptz,
|
|
1186
|
+
last_success_at timestamptz,
|
|
1187
|
+
next_run_at timestamptz,
|
|
1188
|
+
interval_seconds integer CHECK (interval_seconds IS NULL OR (interval_seconds >= 900 AND interval_seconds <= 604800)),
|
|
1189
|
+
imported integer NOT NULL DEFAULT 0 CHECK (imported >= 0),
|
|
1190
|
+
deduplicated integer NOT NULL DEFAULT 0 CHECK (deduplicated >= 0),
|
|
1191
|
+
rejected integer NOT NULL DEFAULT 0 CHECK (rejected >= 0),
|
|
1192
|
+
last_error_code text,
|
|
1193
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
1194
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
1195
|
+
UNIQUE(site_id, idempotency_key)
|
|
1196
|
+
)
|
|
1197
|
+
`),await t.query("ALTER TABLE analytics_crm_sync_runs ADD COLUMN IF NOT EXISTS interval_seconds integer CHECK (interval_seconds IS NULL OR (interval_seconds >= 900 AND interval_seconds <= 604800))"),await t.query("CREATE INDEX IF NOT EXISTS analytics_crm_sync_runs_due ON analytics_crm_sync_runs(state, next_run_at) WHERE next_run_at IS NOT NULL"),await t.query(`
|
|
1198
|
+
CREATE TABLE IF NOT EXISTS analytics_crm_outbound_policies (
|
|
1199
|
+
policy_id text PRIMARY KEY,
|
|
1200
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
1201
|
+
connection_id uuid NOT NULL REFERENCES analytics_connections(id) ON DELETE CASCADE,
|
|
1202
|
+
provider text NOT NULL CHECK (provider IN ('hubspot','salesforce','highlevel','zoho','pipedrive','keap')),
|
|
1203
|
+
kind text NOT NULL CHECK (kind IN ('person_summary','pipeline_event')),
|
|
1204
|
+
enabled boolean NOT NULL DEFAULT false,
|
|
1205
|
+
version integer NOT NULL CHECK (version > 0),
|
|
1206
|
+
provisioning_receipt_id text NOT NULL,
|
|
1207
|
+
schema_fingerprint text NOT NULL,
|
|
1208
|
+
mapping_version integer CHECK (mapping_version > 0),
|
|
1209
|
+
mapping jsonb,
|
|
1210
|
+
allowed_fields jsonb NOT NULL DEFAULT '[]'::jsonb CHECK (jsonb_typeof(allowed_fields) = 'array'),
|
|
1211
|
+
selected_events jsonb NOT NULL DEFAULT '[]'::jsonb CHECK (jsonb_typeof(selected_events) = 'array'),
|
|
1212
|
+
purpose text NOT NULL CHECK (purpose IN ('necessary_form_fulfillment','site_analytics','advertising_measurement')),
|
|
1213
|
+
created_by_user_id bigint NOT NULL,
|
|
1214
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
1215
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
1216
|
+
UNIQUE(site_id, connection_id, kind, version),
|
|
1217
|
+
CHECK ((kind='person_summary' AND mapping_version IS NULL AND mapping IS NULL) OR
|
|
1218
|
+
(kind='pipeline_event' AND mapping_version IS NOT NULL AND mapping IS NOT NULL))
|
|
1219
|
+
)
|
|
1220
|
+
`),await t.query(`
|
|
1221
|
+
CREATE TABLE IF NOT EXISTS analytics_crm_outbound_jobs (
|
|
1222
|
+
job_id text PRIMARY KEY,
|
|
1223
|
+
policy_id text NOT NULL REFERENCES analytics_crm_outbound_policies(policy_id) ON DELETE CASCADE,
|
|
1224
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
1225
|
+
person_id uuid NOT NULL REFERENCES analytics_people(id) ON DELETE CASCADE,
|
|
1226
|
+
kind text NOT NULL CHECK (kind IN ('person_summary','pipeline_event')),
|
|
1227
|
+
state text NOT NULL DEFAULT 'pending' CHECK (state IN ('pending','running','retrying','delivered','dead_letter','cancelled')),
|
|
1228
|
+
policy_version integer NOT NULL CHECK (policy_version > 0),
|
|
1229
|
+
mapping_version integer CHECK (mapping_version > 0),
|
|
1230
|
+
source_event_id text,
|
|
1231
|
+
payload_ciphertext text NOT NULL,
|
|
1232
|
+
idempotency_key text NOT NULL,
|
|
1233
|
+
request_fingerprint text NOT NULL,
|
|
1234
|
+
attempts integer NOT NULL DEFAULT 0 CHECK (attempts >= 0),
|
|
1235
|
+
max_attempts integer NOT NULL DEFAULT 5 CHECK (max_attempts BETWEEN 1 AND 20),
|
|
1236
|
+
next_attempt_at timestamptz,
|
|
1237
|
+
lease_expires_at timestamptz,
|
|
1238
|
+
safe_error_code text,
|
|
1239
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
1240
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
1241
|
+
delivered_at timestamptz,
|
|
1242
|
+
cancelled_at timestamptz,
|
|
1243
|
+
UNIQUE(site_id, idempotency_key)
|
|
1244
|
+
)
|
|
1245
|
+
`),await t.query(`
|
|
1246
|
+
CREATE TABLE IF NOT EXISTS analytics_crm_outbound_attempts (
|
|
1247
|
+
id uuid PRIMARY KEY,
|
|
1248
|
+
job_id text NOT NULL REFERENCES analytics_crm_outbound_jobs(job_id) ON DELETE CASCADE,
|
|
1249
|
+
attempt integer NOT NULL CHECK (attempt > 0),
|
|
1250
|
+
status text NOT NULL CHECK (status IN ('started','accepted','retrying','failed','cancelled')),
|
|
1251
|
+
safe_error_code text,
|
|
1252
|
+
started_at timestamptz NOT NULL DEFAULT now(),
|
|
1253
|
+
completed_at timestamptz,
|
|
1254
|
+
UNIQUE(job_id, attempt)
|
|
1255
|
+
)
|
|
1256
|
+
`),await t.query(`
|
|
1257
|
+
CREATE TABLE IF NOT EXISTS analytics_crm_outbound_receipts (
|
|
1258
|
+
receipt_id text PRIMARY KEY,
|
|
1259
|
+
job_id text NOT NULL REFERENCES analytics_crm_outbound_jobs(job_id) ON DELETE CASCADE,
|
|
1260
|
+
status text NOT NULL CHECK (status IN ('accepted','rejected','failed','retrying')),
|
|
1261
|
+
external_receipt_id text,
|
|
1262
|
+
safe_error_code text,
|
|
1263
|
+
occurred_at timestamptz NOT NULL DEFAULT now()
|
|
1264
|
+
)
|
|
1265
|
+
`),await t.query(`
|
|
1266
|
+
CREATE TABLE IF NOT EXISTS analytics_crm_outbound_worker_state (
|
|
1267
|
+
worker_key text PRIMARY KEY,
|
|
1268
|
+
last_started_at timestamptz,
|
|
1269
|
+
last_completed_at timestamptz,
|
|
1270
|
+
last_success_at timestamptz,
|
|
1271
|
+
last_error_code text,
|
|
1272
|
+
last_claimed integer NOT NULL DEFAULT 0,
|
|
1273
|
+
last_remaining_due integer NOT NULL DEFAULT 0,
|
|
1274
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
1275
|
+
)
|
|
1276
|
+
`),await t.query("CREATE INDEX IF NOT EXISTS analytics_crm_outbound_jobs_due ON analytics_crm_outbound_jobs(state,next_attempt_at) WHERE state IN ('pending','retrying')"),await t.query("CREATE INDEX IF NOT EXISTS analytics_crm_outbound_jobs_person ON analytics_crm_outbound_jobs(site_id,person_id,created_at DESC)"),await t.query("CREATE INDEX IF NOT EXISTS analytics_crm_outbound_receipts_job ON analytics_crm_outbound_receipts(job_id,occurred_at DESC)"),await t.query(`
|
|
1277
|
+
CREATE TABLE IF NOT EXISTS analytics_score_definitions (
|
|
1278
|
+
id uuid PRIMARY KEY,
|
|
1279
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
1280
|
+
name text NOT NULL,
|
|
1281
|
+
score_kind text NOT NULL CHECK (score_kind IN ('fit','engagement','combined')),
|
|
1282
|
+
rules jsonb NOT NULL CHECK (jsonb_typeof(rules) = 'array'),
|
|
1283
|
+
thresholds jsonb NOT NULL DEFAULT '[]'::jsonb CHECK (jsonb_typeof(thresholds) = 'array'),
|
|
1284
|
+
minimum_score integer NOT NULL DEFAULT 0,
|
|
1285
|
+
maximum_score integer NOT NULL DEFAULT 100 CHECK (maximum_score >= minimum_score),
|
|
1286
|
+
version integer NOT NULL DEFAULT 1 CHECK (version > 0),
|
|
1287
|
+
status text NOT NULL DEFAULT 'active' CHECK (status IN ('active','disabled','archived')),
|
|
1288
|
+
created_by_user_id bigint NOT NULL,
|
|
1289
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
1290
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
1291
|
+
UNIQUE(site_id, name, version)
|
|
1292
|
+
)
|
|
1293
|
+
`),await t.query(`
|
|
1294
|
+
CREATE TABLE IF NOT EXISTS analytics_score_evaluations (
|
|
1295
|
+
id uuid PRIMARY KEY,
|
|
1296
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
1297
|
+
namespace_id uuid NOT NULL,
|
|
1298
|
+
definition_id uuid NOT NULL REFERENCES analytics_score_definitions(id) ON DELETE CASCADE,
|
|
1299
|
+
definition_version integer NOT NULL CHECK (definition_version > 0),
|
|
1300
|
+
person_id uuid,
|
|
1301
|
+
candidate_association_id uuid,
|
|
1302
|
+
score integer NOT NULL,
|
|
1303
|
+
band text,
|
|
1304
|
+
contributions jsonb NOT NULL DEFAULT '[]'::jsonb,
|
|
1305
|
+
evaluated_at timestamptz NOT NULL DEFAULT now(),
|
|
1306
|
+
FOREIGN KEY(namespace_id, site_id) REFERENCES analytics_identity_namespaces(id, site_id) ON DELETE CASCADE,
|
|
1307
|
+
FOREIGN KEY(person_id, site_id, namespace_id) REFERENCES analytics_people(id, site_id, namespace_id) ON DELETE CASCADE,
|
|
1308
|
+
FOREIGN KEY(candidate_association_id, namespace_id) REFERENCES analytics_candidate_associations(id, namespace_id) ON DELETE CASCADE,
|
|
1309
|
+
CHECK ((person_id IS NOT NULL)::integer + (candidate_association_id IS NOT NULL)::integer = 1)
|
|
1310
|
+
)
|
|
1311
|
+
`),await t.query(`
|
|
1312
|
+
CREATE TABLE IF NOT EXISTS analytics_prediction_targets (
|
|
1313
|
+
id uuid PRIMARY KEY,
|
|
1314
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
1315
|
+
namespace_id uuid NOT NULL,
|
|
1316
|
+
name text NOT NULL,
|
|
1317
|
+
target_event_definition_id uuid NOT NULL REFERENCES analytics_event_definitions(id) ON DELETE RESTRICT,
|
|
1318
|
+
horizon_days integer NOT NULL CHECK (horizon_days > 0),
|
|
1319
|
+
minimum_confirmed_people integer NOT NULL CHECK (minimum_confirmed_people > 0),
|
|
1320
|
+
minimum_positive_outcomes integer NOT NULL CHECK (minimum_positive_outcomes > 0),
|
|
1321
|
+
status text NOT NULL DEFAULT 'draft' CHECK (status IN ('draft','eligible','training','active','degraded','disabled')),
|
|
1322
|
+
created_by_user_id bigint NOT NULL,
|
|
1323
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
1324
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
1325
|
+
FOREIGN KEY(namespace_id, site_id) REFERENCES analytics_identity_namespaces(id, site_id) ON DELETE CASCADE,
|
|
1326
|
+
UNIQUE(id, site_id, namespace_id),
|
|
1327
|
+
UNIQUE(namespace_id, name)
|
|
1328
|
+
)
|
|
1329
|
+
`),await t.query(`
|
|
1330
|
+
CREATE TABLE IF NOT EXISTS analytics_prediction_manifests (
|
|
1331
|
+
id uuid PRIMARY KEY,
|
|
1332
|
+
target_id uuid NOT NULL REFERENCES analytics_prediction_targets(id) ON DELETE CASCADE,
|
|
1333
|
+
model_version text NOT NULL,
|
|
1334
|
+
training_window_start timestamptz NOT NULL,
|
|
1335
|
+
training_window_end timestamptz NOT NULL,
|
|
1336
|
+
confirmed_people_count integer NOT NULL CHECK (confirmed_people_count >= 0),
|
|
1337
|
+
positive_outcome_count integer NOT NULL CHECK (positive_outcome_count >= 0),
|
|
1338
|
+
calibration jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
1339
|
+
feature_manifest jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
1340
|
+
status text NOT NULL CHECK (status IN ('ineligible','training','validated','active','degraded','retired')),
|
|
1341
|
+
trained_at timestamptz,
|
|
1342
|
+
activated_at timestamptz,
|
|
1343
|
+
retired_at timestamptz,
|
|
1344
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
1345
|
+
UNIQUE(target_id, model_version),
|
|
1346
|
+
UNIQUE(id, target_id)
|
|
1347
|
+
)
|
|
1348
|
+
`),await t.query(`
|
|
1349
|
+
CREATE TABLE IF NOT EXISTS analytics_prediction_results (
|
|
1350
|
+
id uuid PRIMARY KEY,
|
|
1351
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
1352
|
+
namespace_id uuid NOT NULL,
|
|
1353
|
+
target_id uuid NOT NULL,
|
|
1354
|
+
manifest_id uuid NOT NULL,
|
|
1355
|
+
person_id uuid NOT NULL,
|
|
1356
|
+
probability numeric(7,6) NOT NULL CHECK (probability >= 0 AND probability <= 1),
|
|
1357
|
+
expected_value_minor bigint,
|
|
1358
|
+
currency text,
|
|
1359
|
+
status text NOT NULL DEFAULT 'available' CHECK (status IN ('available','stale','superseded','deleted')),
|
|
1360
|
+
evaluated_at timestamptz NOT NULL DEFAULT now(),
|
|
1361
|
+
expires_at timestamptz NOT NULL,
|
|
1362
|
+
FOREIGN KEY(namespace_id, site_id) REFERENCES analytics_identity_namespaces(id, site_id) ON DELETE CASCADE,
|
|
1363
|
+
FOREIGN KEY(target_id, site_id, namespace_id) REFERENCES analytics_prediction_targets(id, site_id, namespace_id) ON DELETE CASCADE,
|
|
1364
|
+
FOREIGN KEY(manifest_id, target_id) REFERENCES analytics_prediction_manifests(id, target_id) ON DELETE CASCADE,
|
|
1365
|
+
FOREIGN KEY(person_id, site_id, namespace_id) REFERENCES analytics_people(id, site_id, namespace_id) ON DELETE CASCADE,
|
|
1366
|
+
UNIQUE(manifest_id, person_id)
|
|
1367
|
+
)
|
|
1368
|
+
`),await t.query(`
|
|
1369
|
+
CREATE TABLE IF NOT EXISTS analytics_ad_spend_facts (
|
|
1370
|
+
id uuid PRIMARY KEY,
|
|
1371
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
1372
|
+
connection_id uuid REFERENCES analytics_connections(id) ON DELETE SET NULL,
|
|
1373
|
+
bucket date NOT NULL,
|
|
1374
|
+
platform text NOT NULL,
|
|
1375
|
+
account_id text,
|
|
1376
|
+
campaign_id text,
|
|
1377
|
+
campaign_name text,
|
|
1378
|
+
ad_set_id text,
|
|
1379
|
+
ad_set_name text,
|
|
1380
|
+
ad_id text,
|
|
1381
|
+
ad_name text,
|
|
1382
|
+
creative_id text,
|
|
1383
|
+
placement text,
|
|
1384
|
+
spend_minor bigint NOT NULL CHECK (spend_minor >= 0),
|
|
1385
|
+
currency text NOT NULL,
|
|
1386
|
+
source_ref text NOT NULL,
|
|
1387
|
+
source_updated_at timestamptz,
|
|
1388
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
1389
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
1390
|
+
UNIQUE(site_id, platform, bucket, source_ref)
|
|
1391
|
+
)
|
|
1392
|
+
`),await t.query(`CREATE UNIQUE INDEX IF NOT EXISTS analytics_exports_owner_idempotency
|
|
1393
|
+
ON analytics_exports(requested_by_user_id, idempotency_key) WHERE idempotency_key IS NOT NULL`),await t.query("CREATE INDEX IF NOT EXISTS analytics_events_site_time ON analytics_events(site_id, occurred_at DESC)"),await t.query("CREATE INDEX IF NOT EXISTS analytics_events_site_session_hmac_time ON analytics_events(site_id, session_hmac, occurred_at DESC)"),await t.query("CREATE INDEX IF NOT EXISTS analytics_events_site_visitor_hmac_time ON analytics_events(site_id, visitor_hmac, occurred_at DESC)"),await t.query("CREATE INDEX IF NOT EXISTS analytics_conversions_site_session_hmac_time ON analytics_conversions(site_id, session_hmac, occurred_at DESC)"),await t.query("CREATE INDEX IF NOT EXISTS analytics_events_pixel_time ON analytics_events(pixel_id, occurred_at DESC)"),await t.query("CREATE INDEX IF NOT EXISTS analytics_events_site_hostname_time ON analytics_events(site_id, hostname, occurred_at DESC)"),await t.query("CREATE INDEX IF NOT EXISTS analytics_events_acquisition ON analytics_events(site_id, source, medium, campaign, occurred_at DESC)"),await t.query("CREATE INDEX IF NOT EXISTS analytics_conversions_site_time ON analytics_conversions(site_id, occurred_at DESC)"),await t.query("CREATE INDEX IF NOT EXISTS analytics_sessions_site_visitor_time ON analytics_sessions(site_id, visitor_hmac, started_at DESC)"),await t.query("CREATE INDEX IF NOT EXISTS analytics_touches_site_person_time ON analytics_attribution_touches(site_id, person_id, occurred_at)"),await t.query("CREATE INDEX IF NOT EXISTS analytics_external_events_site_time ON analytics_external_events(site_id, occurred_at DESC)"),await t.query("CREATE INDEX IF NOT EXISTS analytics_webhook_receipts_reconcile ON analytics_webhook_receipts(connection_id, received_at DESC)"),await t.query("CREATE INDEX IF NOT EXISTS analytics_reconciliation_jobs_due ON analytics_reconciliation_jobs(status, next_attempt_at, lease_until)"),await t.query("CREATE INDEX IF NOT EXISTS analytics_activation_jobs_due ON analytics_activation_jobs(status, next_attempt_at) WHERE status = 'pending'"),await t.query("CREATE INDEX IF NOT EXISTS analytics_activation_tests_destination_time ON analytics_activation_destination_tests(destination_id, created_at DESC)"),await t.query("CREATE INDEX IF NOT EXISTS analytics_activation_attempts_destination_time ON analytics_activation_delivery_attempts(destination_id, created_at DESC)"),await t.query("CREATE INDEX IF NOT EXISTS analytics_activation_tests_diagnostics_due ON analytics_activation_destination_tests(next_diagnostic_at) WHERE next_diagnostic_at IS NOT NULL"),await t.query("CREATE INDEX IF NOT EXISTS analytics_restricted_data_expiry ON analytics_restricted_data(expires_at)"),await t.query("CREATE INDEX IF NOT EXISTS analytics_linker_tokens_expiry ON analytics_linker_tokens(expires_at) WHERE consumed_at IS NULL"),await t.query("CREATE UNIQUE INDEX IF NOT EXISTS analytics_sessions_site_public_ref ON analytics_sessions(site_id, public_ref) WHERE public_ref IS NOT NULL"),await t.query("CREATE UNIQUE INDEX IF NOT EXISTS analytics_people_site_public_ref ON analytics_people(site_id, public_ref) WHERE public_ref IS NOT NULL"),await t.query("CREATE INDEX IF NOT EXISTS analytics_sessions_keyset ON analytics_sessions(site_id, started_at DESC, id DESC)"),await t.query("CREATE INDEX IF NOT EXISTS analytics_events_keyset ON analytics_events(site_id, occurred_at DESC, id DESC)"),await t.query("CREATE UNIQUE INDEX IF NOT EXISTS analytics_funnels_one_default ON analytics_funnel_definitions(site_id) WHERE is_default AND status = 'active'"),await t.query("CREATE UNIQUE INDEX IF NOT EXISTS analytics_saved_views_one_default ON analytics_saved_views(site_id, owner_user_id, report_type) WHERE is_default"),await t.query("CREATE INDEX IF NOT EXISTS analytics_product_milestones_site_time ON analytics_product_milestones(site_id, occurred_at DESC)"),await t.query("CREATE INDEX IF NOT EXISTS analytics_scheduled_occurrences_due ON analytics_scheduled_occurrences(status,next_attempt_at,scheduled_for) WHERE status IN ('pending','retrying')"),await t.query("CREATE INDEX IF NOT EXISTS analytics_scheduled_occurrences_lease ON analytics_scheduled_occurrences(lease_until) WHERE status='running'"),await t.query("CREATE INDEX IF NOT EXISTS analytics_consent_receipts_subject_time ON analytics_consent_receipts(site_id, subject_ref, occurred_at DESC)"),await t.query("CREATE INDEX IF NOT EXISTS analytics_consent_receipts_expiry ON analytics_consent_receipts(expires_at) WHERE expires_at IS NOT NULL"),await t.query("CREATE INDEX IF NOT EXISTS analytics_consent_current_expiry ON analytics_consent_current_state(expires_at) WHERE expires_at IS NOT NULL"),await t.query("CREATE INDEX IF NOT EXISTS analytics_identity_sources_site ON analytics_identity_sources(site_id, namespace_id, status)"),await t.query("CREATE INDEX IF NOT EXISTS analytics_candidates_active_expiry ON analytics_candidate_associations(namespace_id, expires_at) WHERE status = 'active'"),await t.query("CREATE INDEX IF NOT EXISTS analytics_candidates_review ON analytics_candidate_associations(namespace_id, confidence_band, updated_at DESC) WHERE status = 'active'"),await t.query("CREATE INDEX IF NOT EXISTS analytics_candidate_evidence_expiry ON analytics_candidate_evidence(expires_at)"),await t.query("CREATE INDEX IF NOT EXISTS analytics_candidate_promotions_person_time ON analytics_candidate_promotions(site_id, person_id, promoted_at DESC)"),await t.query("CREATE INDEX IF NOT EXISTS analytics_identity_promotion_receipts_person_time ON analytics_identity_promotion_receipts(site_id, canonical_person_id, promoted_at DESC)"),await t.query("CREATE INDEX IF NOT EXISTS analytics_candidate_touches_overlay ON analytics_candidate_attribution_touches(site_id, namespace_id, linked_at DESC) WHERE eligibility = 'overlay_only'"),await t.query("CREATE INDEX IF NOT EXISTS analytics_candidate_touches_expiry ON analytics_candidate_attribution_touches(expires_at) WHERE eligibility IN ('overlay_only','review')"),await t.query("CREATE INDEX IF NOT EXISTS analytics_value_evaluations_event ON analytics_event_value_evaluations(site_id, source_event_id)"),await t.query("CREATE INDEX IF NOT EXISTS analytics_privacy_audits_site_time ON analytics_privacy_audits(site_id, created_at DESC)"),await t.query("CREATE INDEX IF NOT EXISTS analytics_crm_provisioning_connection_time ON analytics_crm_provisioning_plans(connection_id, created_at DESC)"),await t.query("CREATE INDEX IF NOT EXISTS analytics_crm_sync_due ON analytics_crm_sync_state(status, next_sync_at) WHERE next_sync_at IS NOT NULL"),await t.query("CREATE INDEX IF NOT EXISTS analytics_crm_sync_receipts_connection_time ON analytics_crm_sync_receipts(connection_id, occurred_at DESC)"),await t.query("CREATE INDEX IF NOT EXISTS analytics_scores_person_time ON analytics_score_evaluations(site_id, person_id, evaluated_at DESC) WHERE person_id IS NOT NULL"),await t.query("CREATE INDEX IF NOT EXISTS analytics_scores_candidate_time ON analytics_score_evaluations(site_id, candidate_association_id, evaluated_at DESC) WHERE candidate_association_id IS NOT NULL"),await t.query("CREATE INDEX IF NOT EXISTS analytics_prediction_results_person_time ON analytics_prediction_results(site_id, person_id, evaluated_at DESC)"),await t.query("CREATE INDEX IF NOT EXISTS analytics_prediction_results_expiry ON analytics_prediction_results(expires_at) WHERE status = 'available'"),await t.query("CREATE INDEX IF NOT EXISTS analytics_touches_hierarchy ON analytics_attribution_touches(site_id, platform, account_id, campaign_id, ad_set_id, ad_id, occurred_at DESC)"),await t.query("CREATE INDEX IF NOT EXISTS analytics_spend_facts_hierarchy ON analytics_ad_spend_facts(site_id, platform, account_id, campaign_id, ad_set_id, ad_id, bucket DESC)"),await t.query(`
|
|
1394
|
+
CREATE TABLE IF NOT EXISTS analytics_runtime_manifests (
|
|
1395
|
+
manifest_id uuid PRIMARY KEY,
|
|
1396
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
1397
|
+
revision integer NOT NULL CHECK (revision > 0),
|
|
1398
|
+
digest text NOT NULL CHECK (digest ~ '^[a-f0-9]{64}$'),
|
|
1399
|
+
manifest jsonb NOT NULL,
|
|
1400
|
+
is_current boolean NOT NULL DEFAULT false,
|
|
1401
|
+
created_by_user_id bigint NOT NULL,
|
|
1402
|
+
expires_at timestamptz NOT NULL,
|
|
1403
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
1404
|
+
UNIQUE(site_id, revision)
|
|
1405
|
+
)
|
|
1406
|
+
`),await t.query("CREATE UNIQUE INDEX IF NOT EXISTS analytics_runtime_manifests_one_current ON analytics_runtime_manifests(site_id) WHERE is_current"),await t.query("CREATE INDEX IF NOT EXISTS analytics_runtime_manifests_site_created ON analytics_runtime_manifests(site_id, created_at DESC)"),await t.query(`
|
|
1407
|
+
CREATE TABLE IF NOT EXISTS analytics_runtime_manifest_publications (
|
|
1408
|
+
publication_id uuid PRIMARY KEY,
|
|
1409
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
1410
|
+
manifest_id uuid NOT NULL REFERENCES analytics_runtime_manifests(manifest_id) ON DELETE CASCADE,
|
|
1411
|
+
setup_id uuid,
|
|
1412
|
+
setup_revision integer,
|
|
1413
|
+
idempotency_key text NOT NULL,
|
|
1414
|
+
request_fingerprint text NOT NULL CHECK (request_fingerprint ~ '^[a-f0-9]{64}$'),
|
|
1415
|
+
manifest jsonb NOT NULL,
|
|
1416
|
+
published_at timestamptz NOT NULL DEFAULT now(),
|
|
1417
|
+
UNIQUE(site_id, idempotency_key)
|
|
1418
|
+
)
|
|
1419
|
+
`),await t.query(`
|
|
1420
|
+
CREATE TABLE IF NOT EXISTS analytics_runtime_rule_health (
|
|
1421
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
1422
|
+
manifest_id uuid NOT NULL REFERENCES analytics_runtime_manifests(manifest_id) ON DELETE CASCADE,
|
|
1423
|
+
rule_id uuid NOT NULL,
|
|
1424
|
+
rule_version integer NOT NULL,
|
|
1425
|
+
route_path text NOT NULL,
|
|
1426
|
+
selector_quality text NOT NULL CHECK (selector_quality IN ('portable','stable','fragile')),
|
|
1427
|
+
match_count integer NOT NULL CHECK (match_count BETWEEN 0 AND 100),
|
|
1428
|
+
structural_fingerprint text NOT NULL CHECK (structural_fingerprint ~ '^[a-f0-9]{64}$'),
|
|
1429
|
+
observed_at timestamptz NOT NULL,
|
|
1430
|
+
PRIMARY KEY(manifest_id, rule_id, route_path)
|
|
1431
|
+
)
|
|
1432
|
+
`),await t.query(`
|
|
1433
|
+
CREATE TABLE IF NOT EXISTS analytics_experiments (
|
|
1434
|
+
experiment_id uuid PRIMARY KEY,
|
|
1435
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
1436
|
+
current_revision integer NOT NULL CHECK (current_revision > 0),
|
|
1437
|
+
state text NOT NULL CHECK (state IN ('draft','approved','canary','active','paused','killed','archived')),
|
|
1438
|
+
created_by_user_id bigint NOT NULL,
|
|
1439
|
+
create_idempotency_key text,
|
|
1440
|
+
create_request_fingerprint text,
|
|
1441
|
+
started_at timestamptz,
|
|
1442
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
1443
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
1444
|
+
)
|
|
1445
|
+
`),await t.query("ALTER TABLE analytics_experiments ADD COLUMN IF NOT EXISTS create_idempotency_key text"),await t.query("ALTER TABLE analytics_experiments ADD COLUMN IF NOT EXISTS create_request_fingerprint text"),await t.query("ALTER TABLE analytics_experiments ADD COLUMN IF NOT EXISTS started_at timestamptz"),await t.query("CREATE UNIQUE INDEX IF NOT EXISTS analytics_experiments_site_create_idempotency ON analytics_experiments(site_id, create_idempotency_key) WHERE create_idempotency_key IS NOT NULL"),await t.query(`
|
|
1446
|
+
CREATE TABLE IF NOT EXISTS analytics_experiment_revisions (
|
|
1447
|
+
experiment_id uuid NOT NULL REFERENCES analytics_experiments(experiment_id) ON DELETE CASCADE,
|
|
1448
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
1449
|
+
revision integer NOT NULL CHECK (revision > 0),
|
|
1450
|
+
plan jsonb NOT NULL,
|
|
1451
|
+
approved_by_user_id bigint,
|
|
1452
|
+
approved_at timestamptz,
|
|
1453
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
1454
|
+
PRIMARY KEY(experiment_id, revision)
|
|
1455
|
+
)
|
|
1456
|
+
`),await t.query(`
|
|
1457
|
+
CREATE TABLE IF NOT EXISTS analytics_experiment_receipts (
|
|
1458
|
+
receipt_id uuid PRIMARY KEY,
|
|
1459
|
+
experiment_id uuid NOT NULL REFERENCES analytics_experiments(experiment_id) ON DELETE CASCADE,
|
|
1460
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
1461
|
+
revision integer NOT NULL,
|
|
1462
|
+
operation text NOT NULL,
|
|
1463
|
+
idempotency_key text,
|
|
1464
|
+
receipt jsonb NOT NULL,
|
|
1465
|
+
occurred_at timestamptz NOT NULL DEFAULT now(),
|
|
1466
|
+
UNIQUE(experiment_id, idempotency_key)
|
|
1467
|
+
)
|
|
1468
|
+
`),await t.query("CREATE INDEX IF NOT EXISTS analytics_experiments_site_updated ON analytics_experiments(site_id, updated_at DESC)"),await t.query(`
|
|
1469
|
+
CREATE TABLE IF NOT EXISTS analytics_site_setups (
|
|
1470
|
+
setup_id uuid PRIMARY KEY,
|
|
1471
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
1472
|
+
owner_user_id bigint NOT NULL,
|
|
1473
|
+
revision integer NOT NULL CHECK (revision > 0),
|
|
1474
|
+
status text NOT NULL,
|
|
1475
|
+
start_url text NOT NULL,
|
|
1476
|
+
state jsonb NOT NULL,
|
|
1477
|
+
expires_at timestamptz NOT NULL,
|
|
1478
|
+
created_at timestamptz NOT NULL,
|
|
1479
|
+
updated_at timestamptz NOT NULL
|
|
1480
|
+
)
|
|
1481
|
+
`),await t.query(`
|
|
1482
|
+
CREATE TABLE IF NOT EXISTS analytics_site_setup_revisions (
|
|
1483
|
+
setup_id uuid NOT NULL REFERENCES analytics_site_setups(setup_id) ON DELETE CASCADE,
|
|
1484
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
1485
|
+
owner_user_id bigint NOT NULL,
|
|
1486
|
+
revision integer NOT NULL CHECK (revision > 0),
|
|
1487
|
+
status text NOT NULL,
|
|
1488
|
+
state jsonb NOT NULL,
|
|
1489
|
+
created_at timestamptz NOT NULL,
|
|
1490
|
+
PRIMARY KEY(setup_id, revision)
|
|
1491
|
+
)
|
|
1492
|
+
`),await t.query(`
|
|
1493
|
+
CREATE TABLE IF NOT EXISTS analytics_site_setup_receipts (
|
|
1494
|
+
receipt_id uuid PRIMARY KEY,
|
|
1495
|
+
setup_id uuid NOT NULL REFERENCES analytics_site_setups(setup_id) ON DELETE CASCADE,
|
|
1496
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
1497
|
+
owner_user_id bigint NOT NULL,
|
|
1498
|
+
revision integer NOT NULL,
|
|
1499
|
+
operation text NOT NULL,
|
|
1500
|
+
status text NOT NULL,
|
|
1501
|
+
receipt jsonb NOT NULL,
|
|
1502
|
+
occurred_at timestamptz NOT NULL
|
|
1503
|
+
)
|
|
1504
|
+
`),await t.query("CREATE INDEX IF NOT EXISTS analytics_site_setups_owner_updated ON analytics_site_setups(owner_user_id, updated_at DESC)"),await t.query("CREATE INDEX IF NOT EXISTS analytics_site_setups_site_updated ON analytics_site_setups(site_id, updated_at DESC)"),await t.query("CREATE INDEX IF NOT EXISTS analytics_site_setup_receipts_setup_revision ON analytics_site_setup_receipts(setup_id, revision, occurred_at)"),await t.query("ALTER TABLE analytics_conversions ADD COLUMN IF NOT EXISTS authority_key text"),await t.query("ALTER TABLE analytics_conversions ADD COLUMN IF NOT EXISTS authority_id uuid"),await t.query("ALTER TABLE analytics_conversions ADD COLUMN IF NOT EXISTS authority_version_id uuid"),await t.query("ALTER TABLE analytics_conversions ADD COLUMN IF NOT EXISTS restated_at timestamptz"),await t.query(`CREATE UNIQUE INDEX IF NOT EXISTS analytics_conversions_site_authority_key
|
|
1505
|
+
ON analytics_conversions(site_id, authority_key) WHERE authority_key IS NOT NULL`),await t.query(`
|
|
1506
|
+
CREATE TABLE IF NOT EXISTS analytics_revenue_authorities (
|
|
1507
|
+
authority_id uuid PRIMARY KEY,
|
|
1508
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
1509
|
+
provider text NOT NULL,
|
|
1510
|
+
source_account_ref text NOT NULL,
|
|
1511
|
+
connection_id uuid REFERENCES analytics_connections(id) ON DELETE SET NULL,
|
|
1512
|
+
outcome_family text NOT NULL,
|
|
1513
|
+
authority_role text NOT NULL CHECK (authority_role IN ('primary','observation')),
|
|
1514
|
+
state text NOT NULL CHECK (state IN ('configured_unverified','verified','live','degraded','disabled','blocked')),
|
|
1515
|
+
current_revision integer NOT NULL DEFAULT 1 CHECK (current_revision > 0),
|
|
1516
|
+
created_by_user_id bigint NOT NULL,
|
|
1517
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
1518
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
1519
|
+
disabled_at timestamptz,
|
|
1520
|
+
UNIQUE(site_id, provider, source_account_ref, outcome_family)
|
|
1521
|
+
)
|
|
1522
|
+
`),await t.query(`CREATE UNIQUE INDEX IF NOT EXISTS analytics_revenue_authorities_one_primary
|
|
1523
|
+
ON analytics_revenue_authorities(site_id, outcome_family)
|
|
1524
|
+
WHERE authority_role='primary' AND state <> 'disabled'`),await t.query(`
|
|
1525
|
+
CREATE TABLE IF NOT EXISTS analytics_provider_customer_bindings (
|
|
1526
|
+
binding_id uuid PRIMARY KEY,
|
|
1527
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
1528
|
+
authority_id uuid NOT NULL REFERENCES analytics_revenue_authorities(authority_id) ON DELETE CASCADE,
|
|
1529
|
+
provider_customer_ref_hmac text NOT NULL CHECK (provider_customer_ref_hmac ~ '^[a-f0-9]{64}$'),
|
|
1530
|
+
person_id uuid NOT NULL REFERENCES analytics_people(id) ON DELETE CASCADE,
|
|
1531
|
+
verification_status text NOT NULL CHECK (verification_status IN ('verified','revoked')),
|
|
1532
|
+
evidence_ref text NOT NULL,
|
|
1533
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
1534
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
1535
|
+
revoked_at timestamptz,
|
|
1536
|
+
UNIQUE(authority_id, provider_customer_ref_hmac),
|
|
1537
|
+
UNIQUE(authority_id, person_id)
|
|
1538
|
+
)
|
|
1539
|
+
`),await t.query(`
|
|
1540
|
+
CREATE TABLE IF NOT EXISTS analytics_authoritative_outcomes (
|
|
1541
|
+
outcome_id uuid PRIMARY KEY,
|
|
1542
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
1543
|
+
authority_id uuid NOT NULL REFERENCES analytics_revenue_authorities(authority_id) ON DELETE RESTRICT,
|
|
1544
|
+
authority_key text NOT NULL,
|
|
1545
|
+
provider_object_kind text NOT NULL,
|
|
1546
|
+
provider_object_ref_hmac text NOT NULL,
|
|
1547
|
+
provider_customer_ref_hmac text,
|
|
1548
|
+
outcome_kind text NOT NULL,
|
|
1549
|
+
current_version integer NOT NULL DEFAULT 0 CHECK (current_version >= 0),
|
|
1550
|
+
conversion_id uuid REFERENCES analytics_conversions(id) ON DELETE SET NULL,
|
|
1551
|
+
person_id uuid REFERENCES analytics_people(id) ON DELETE SET NULL,
|
|
1552
|
+
first_observed_at timestamptz NOT NULL,
|
|
1553
|
+
last_observed_at timestamptz NOT NULL,
|
|
1554
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
1555
|
+
UNIQUE(site_id, authority_key)
|
|
1556
|
+
)
|
|
1557
|
+
`),await t.query(`
|
|
1558
|
+
CREATE TABLE IF NOT EXISTS analytics_authoritative_outcome_versions (
|
|
1559
|
+
version_id uuid NOT NULL UNIQUE,
|
|
1560
|
+
outcome_id uuid NOT NULL REFERENCES analytics_authoritative_outcomes(outcome_id) ON DELETE CASCADE,
|
|
1561
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
1562
|
+
version integer NOT NULL CHECK (version > 0),
|
|
1563
|
+
transport_event_id text NOT NULL,
|
|
1564
|
+
status text NOT NULL,
|
|
1565
|
+
value_minor bigint NOT NULL,
|
|
1566
|
+
currency text NOT NULL CHECK (currency ~ '^[A-Z]{3}$'),
|
|
1567
|
+
occurred_at timestamptz NOT NULL,
|
|
1568
|
+
observed_at timestamptz NOT NULL,
|
|
1569
|
+
provenance jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
1570
|
+
receipt_ref text NOT NULL,
|
|
1571
|
+
request_fingerprint text NOT NULL CHECK (request_fingerprint ~ '^[a-f0-9]{64}$'),
|
|
1572
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
1573
|
+
PRIMARY KEY(outcome_id, version),
|
|
1574
|
+
UNIQUE(site_id, transport_event_id)
|
|
1575
|
+
)
|
|
1576
|
+
`),await t.query(`
|
|
1577
|
+
CREATE TABLE IF NOT EXISTS analytics_revenue_setup_revisions (
|
|
1578
|
+
setup_id uuid NOT NULL,
|
|
1579
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
1580
|
+
revision integer NOT NULL CHECK (revision > 0),
|
|
1581
|
+
authority_id uuid REFERENCES analytics_revenue_authorities(authority_id) ON DELETE SET NULL,
|
|
1582
|
+
state text NOT NULL,
|
|
1583
|
+
plan jsonb NOT NULL,
|
|
1584
|
+
request_fingerprint text NOT NULL CHECK (request_fingerprint ~ '^[a-f0-9]{64}$'),
|
|
1585
|
+
created_by_user_id bigint NOT NULL,
|
|
1586
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
1587
|
+
PRIMARY KEY(setup_id, revision)
|
|
1588
|
+
)
|
|
1589
|
+
`),await t.query(`
|
|
1590
|
+
CREATE TABLE IF NOT EXISTS analytics_revenue_setup_receipts (
|
|
1591
|
+
receipt_id uuid PRIMARY KEY,
|
|
1592
|
+
setup_id uuid NOT NULL,
|
|
1593
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
1594
|
+
revision integer NOT NULL,
|
|
1595
|
+
operation text NOT NULL,
|
|
1596
|
+
status text NOT NULL,
|
|
1597
|
+
idempotency_key text NOT NULL,
|
|
1598
|
+
request_fingerprint text NOT NULL CHECK (request_fingerprint ~ '^[a-f0-9]{64}$'),
|
|
1599
|
+
receipt jsonb NOT NULL,
|
|
1600
|
+
occurred_at timestamptz NOT NULL DEFAULT now(),
|
|
1601
|
+
FOREIGN KEY(setup_id, revision) REFERENCES analytics_revenue_setup_revisions(setup_id, revision) ON DELETE CASCADE,
|
|
1602
|
+
UNIQUE(site_id, idempotency_key)
|
|
1603
|
+
)
|
|
1604
|
+
`),await t.query(`
|
|
1605
|
+
CREATE TABLE IF NOT EXISTS analytics_post_purchase_surveys (
|
|
1606
|
+
survey_id uuid PRIMARY KEY,
|
|
1607
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
1608
|
+
current_revision integer NOT NULL DEFAULT 1 CHECK (current_revision > 0),
|
|
1609
|
+
state text NOT NULL CHECK (state IN ('draft','published','paused','archived')),
|
|
1610
|
+
name text NOT NULL,
|
|
1611
|
+
question text NOT NULL,
|
|
1612
|
+
selection_mode text NOT NULL CHECK (selection_mode IN ('single','multiple')),
|
|
1613
|
+
eligible_outcome_families text[] NOT NULL,
|
|
1614
|
+
exchange_ttl_seconds integer NOT NULL CHECK (exchange_ttl_seconds BETWEEN 60 AND 86400),
|
|
1615
|
+
session_ttl_seconds integer NOT NULL CHECK (session_ttl_seconds BETWEEN 60 AND 86400),
|
|
1616
|
+
approved_copy_id text,
|
|
1617
|
+
create_idempotency_key text,
|
|
1618
|
+
create_request_fingerprint text,
|
|
1619
|
+
state_transition_receipts jsonb NOT NULL DEFAULT '{}'::jsonb CHECK (jsonb_typeof(state_transition_receipts)='object'),
|
|
1620
|
+
created_by_user_id bigint NOT NULL,
|
|
1621
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
1622
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
1623
|
+
archived_at timestamptz,
|
|
1624
|
+
UNIQUE(site_id, create_idempotency_key)
|
|
1625
|
+
)
|
|
1626
|
+
`),await t.query(`ALTER TABLE analytics_post_purchase_surveys
|
|
1627
|
+
ADD COLUMN IF NOT EXISTS state_transition_receipts jsonb NOT NULL DEFAULT '{}'::jsonb`),await t.query(`ALTER TABLE analytics_post_purchase_surveys
|
|
1628
|
+
DROP CONSTRAINT IF EXISTS analytics_post_purchase_surveys_state_transition_receipts_check`),await t.query(`ALTER TABLE analytics_post_purchase_surveys
|
|
1629
|
+
ADD CONSTRAINT analytics_post_purchase_surveys_state_transition_receipts_check
|
|
1630
|
+
CHECK (jsonb_typeof(state_transition_receipts)='object')`),await t.query(`
|
|
1631
|
+
CREATE TABLE IF NOT EXISTS analytics_post_purchase_survey_options (
|
|
1632
|
+
survey_id uuid NOT NULL REFERENCES analytics_post_purchase_surveys(survey_id) ON DELETE CASCADE,
|
|
1633
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
1634
|
+
revision integer NOT NULL CHECK (revision > 0),
|
|
1635
|
+
option_id text NOT NULL,
|
|
1636
|
+
label text NOT NULL,
|
|
1637
|
+
position integer NOT NULL CHECK (position >= 0),
|
|
1638
|
+
active boolean NOT NULL DEFAULT true,
|
|
1639
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
1640
|
+
PRIMARY KEY(survey_id, revision, option_id),
|
|
1641
|
+
UNIQUE(survey_id, revision, position)
|
|
1642
|
+
)
|
|
1643
|
+
`),await t.query(`
|
|
1644
|
+
CREATE TABLE IF NOT EXISTS analytics_post_purchase_survey_invites (
|
|
1645
|
+
invite_id uuid PRIMARY KEY,
|
|
1646
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
1647
|
+
survey_id uuid NOT NULL REFERENCES analytics_post_purchase_surveys(survey_id) ON DELETE CASCADE,
|
|
1648
|
+
survey_revision integer NOT NULL CHECK (survey_revision > 0),
|
|
1649
|
+
conversion_id uuid NOT NULL REFERENCES analytics_conversions(id) ON DELETE CASCADE,
|
|
1650
|
+
invitation_token_digest text NOT NULL CHECK (invitation_token_digest ~ '^[a-f0-9]{64}$'),
|
|
1651
|
+
purpose text NOT NULL DEFAULT 'customer_reported_measurement' CHECK (purpose='customer_reported_measurement'),
|
|
1652
|
+
approved_copy_id text NOT NULL,
|
|
1653
|
+
exchange_expires_at timestamptz NOT NULL,
|
|
1654
|
+
exchanged_at timestamptz,
|
|
1655
|
+
session_token_digest text CHECK (session_token_digest IS NULL OR session_token_digest ~ '^[a-f0-9]{64}$'),
|
|
1656
|
+
session_expires_at timestamptz,
|
|
1657
|
+
response_id uuid,
|
|
1658
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
1659
|
+
UNIQUE(site_id, invitation_token_digest),
|
|
1660
|
+
UNIQUE(site_id, session_token_digest),
|
|
1661
|
+
UNIQUE(survey_id, conversion_id)
|
|
1662
|
+
)
|
|
1663
|
+
`),await t.query(`
|
|
1664
|
+
CREATE TABLE IF NOT EXISTS analytics_post_purchase_survey_responses (
|
|
1665
|
+
response_id uuid PRIMARY KEY,
|
|
1666
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
1667
|
+
survey_id uuid NOT NULL REFERENCES analytics_post_purchase_surveys(survey_id) ON DELETE CASCADE,
|
|
1668
|
+
survey_revision integer NOT NULL CHECK (survey_revision > 0),
|
|
1669
|
+
conversion_id uuid NOT NULL REFERENCES analytics_conversions(id) ON DELETE CASCADE,
|
|
1670
|
+
invite_id uuid NOT NULL REFERENCES analytics_post_purchase_survey_invites(invite_id) ON DELETE RESTRICT,
|
|
1671
|
+
body_fingerprint text NOT NULL CHECK (body_fingerprint ~ '^[a-f0-9]{64}$'),
|
|
1672
|
+
submitted_at timestamptz NOT NULL DEFAULT now(),
|
|
1673
|
+
UNIQUE(survey_id, conversion_id),
|
|
1674
|
+
UNIQUE(invite_id)
|
|
1675
|
+
)
|
|
1676
|
+
`),await t.query(`
|
|
1677
|
+
CREATE TABLE IF NOT EXISTS analytics_reported_influences (
|
|
1678
|
+
evidence_id uuid PRIMARY KEY,
|
|
1679
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
1680
|
+
response_id uuid NOT NULL REFERENCES analytics_post_purchase_survey_responses(response_id) ON DELETE CASCADE,
|
|
1681
|
+
conversion_id uuid NOT NULL REFERENCES analytics_conversions(id) ON DELETE CASCADE,
|
|
1682
|
+
survey_id uuid NOT NULL REFERENCES analytics_post_purchase_surveys(survey_id) ON DELETE CASCADE,
|
|
1683
|
+
survey_revision integer NOT NULL CHECK (survey_revision > 0),
|
|
1684
|
+
option_id text NOT NULL,
|
|
1685
|
+
option_label text NOT NULL,
|
|
1686
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
1687
|
+
UNIQUE(response_id, option_id)
|
|
1688
|
+
)
|
|
1689
|
+
`),await t.query(`
|
|
1690
|
+
CREATE TABLE IF NOT EXISTS analytics_view_evidence_receipts (
|
|
1691
|
+
receipt_id uuid PRIMARY KEY,
|
|
1692
|
+
site_id uuid NOT NULL REFERENCES analytics_sites(id) ON DELETE CASCADE,
|
|
1693
|
+
provider text NOT NULL,
|
|
1694
|
+
source_account_ref text NOT NULL,
|
|
1695
|
+
provider_event_id text NOT NULL,
|
|
1696
|
+
evidence_granularity text NOT NULL CHECK (evidence_granularity='event_level'),
|
|
1697
|
+
verification_status text NOT NULL CHECK (verification_status='verified'),
|
|
1698
|
+
subject_kind text NOT NULL CHECK (subject_kind IN ('person','session','visitor')),
|
|
1699
|
+
subject_ref_hmac text NOT NULL,
|
|
1700
|
+
person_id uuid REFERENCES analytics_people(id) ON DELETE SET NULL,
|
|
1701
|
+
session_id uuid REFERENCES analytics_sessions(id) ON DELETE SET NULL,
|
|
1702
|
+
provider_receipt_ref text NOT NULL,
|
|
1703
|
+
occurred_at timestamptz NOT NULL,
|
|
1704
|
+
retention_expires_at timestamptz NOT NULL,
|
|
1705
|
+
touch_id uuid REFERENCES analytics_attribution_touches(id) ON DELETE SET NULL,
|
|
1706
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
1707
|
+
UNIQUE(site_id, provider, source_account_ref, provider_event_id)
|
|
1708
|
+
)
|
|
1709
|
+
`),await t.query("ALTER TABLE analytics_conversions DROP CONSTRAINT IF EXISTS analytics_conversions_authority_id_fkey"),await t.query(`ALTER TABLE analytics_conversions ADD CONSTRAINT analytics_conversions_authority_id_fkey
|
|
1710
|
+
FOREIGN KEY(authority_id) REFERENCES analytics_revenue_authorities(authority_id) ON DELETE SET NULL`),await t.query("ALTER TABLE analytics_conversions DROP CONSTRAINT IF EXISTS analytics_conversions_authority_version_id_fkey"),await t.query(`ALTER TABLE analytics_conversions ADD CONSTRAINT analytics_conversions_authority_version_id_fkey
|
|
1711
|
+
FOREIGN KEY(authority_version_id) REFERENCES analytics_authoritative_outcome_versions(version_id) ON DELETE SET NULL`),await t.query(`CREATE INDEX IF NOT EXISTS analytics_authoritative_outcomes_person
|
|
1712
|
+
ON analytics_authoritative_outcomes(site_id, person_id, last_observed_at DESC)`),await t.query(`CREATE INDEX IF NOT EXISTS analytics_reported_influences_conversion
|
|
1713
|
+
ON analytics_reported_influences(site_id, conversion_id, created_at)`),await t.query(`CREATE INDEX IF NOT EXISTS analytics_view_evidence_subject
|
|
1714
|
+
ON analytics_view_evidence_receipts(site_id, subject_ref_hmac, occurred_at DESC)`),await t.query(`
|
|
1715
|
+
INSERT INTO analytics_schema_migrations(version)
|
|
1716
|
+
VALUES ('2026-08-05.1'), ('2026-08-05.2'), ('2026-08-05.3'), ('2026-08-05.4'), ('2026-08-05.5'), ('2026-08-05.6'), ('2026-08-05.7'), ('2026-08-05.8'), ('2026-08-05.9'), ('2026-08-05.10'), ('2026-08-05.11'), ('2026-08-05.12'), ('2026-08-05.13'), ('2026-08-05.14'), ('2026-08-26.1'), ('2026-08-26.2'), ('2026-08-26.3'), ('2026-08-26.4'), ('2026-08-27.1'), ('2026-08-28.1'), ('2026-08-28.2'), ('2026-08-28.3'), ('2026-08-28.4'), ('2026-08-31.1'), ('2026-09-01.1'), ('2026-09-01.2')
|
|
1717
|
+
ON CONFLICT (version) DO NOTHING
|
|
1718
|
+
`)}catch(e){throw e}finally{t.release()}}export{_ as a};
|