-- Visitor journey reconstruction. Two tables, two retention policies. -- -- session_events: every analytics event the SvelteKit /api/track endpoint -- receives, keyed by the anon_id cookie set in hooks.server.ts. This buffer -- is the source for journey reconstruction when a visitor submits the -- booking form. It is pruned aggressively (24h TTL) so we don't hoard -- behavioural data for visitors who never enquire. -- -- submission_journeys: when a visitor submits the booking/contact form, -- /api/track/promote copies their recent session_events into this table -- linked to the submission email. These rows are kept so the owner can -- review the journey from the CP dashboard. -- -- The promotion step is the only place where an anonymous browsing record -- becomes linked to a named person, and it only happens because that -- person chose to submit a form. The privacy policy covers this. create table if not exists session_events ( id bigserial primary key, anon_id text not null, event_name text not null, page_path text, params jsonb not null default '{}'::jsonb, created_at timestamptz not null default now() ); create index if not exists session_events_anon_idx on session_events (anon_id, created_at); -- Used by the probabilistic prune in /api/track to find rows past TTL. create index if not exists session_events_created_idx on session_events (created_at); create table if not exists submission_journeys ( id bigserial primary key, email text not null, anon_id text, -- Snapshot of session_events rows at promotion time (server-captured). events jsonb not null default '[]'::jsonb, -- Client-side sessionStorage buffer sent with the promote request, as a -- fallback for events that never reached /api/track (e.g. blocked at the -- network layer alongside gtag). client_events jsonb not null default '[]'::jsonb, created_at timestamptz not null default now() ); create index if not exists submission_journeys_email_idx on submission_journeys (email, created_at desc);