37 lines
1.6 KiB
MySQL
37 lines
1.6 KiB
MySQL
|
|
-- Phase 4c — tracked redirect links for email click-through tracking.
|
||
|
|
-- A short URL at /q/<slug> redirects to the target and records the
|
||
|
|
-- click against the originating send. Cross-posted to Umami as a
|
||
|
|
-- `link-clicked` event.
|
||
|
|
|
||
|
|
CREATE TABLE IF NOT EXISTS "tracked_links" (
|
||
|
|
"id" text PRIMARY KEY,
|
||
|
|
"port_id" text NOT NULL REFERENCES "ports"("id"),
|
||
|
|
"slug" text NOT NULL,
|
||
|
|
"target_url" text NOT NULL,
|
||
|
|
"send_id" text REFERENCES "document_sends"("id") ON DELETE SET NULL,
|
||
|
|
"click_count" integer NOT NULL DEFAULT 0,
|
||
|
|
"first_clicked_at" timestamptz,
|
||
|
|
"last_clicked_at" timestamptz,
|
||
|
|
"created_at" timestamptz NOT NULL DEFAULT now(),
|
||
|
|
"created_by_user_id" text REFERENCES "user"("id") ON DELETE SET NULL
|
||
|
|
);
|
||
|
|
|
||
|
|
-- Slugs are scoped to a port; an admin can rotate them per-port. Global
|
||
|
|
-- uniqueness isn't required because /q/<slug> is gated by tenancy in
|
||
|
|
-- the route handler.
|
||
|
|
CREATE UNIQUE INDEX IF NOT EXISTS "uniq_tracked_links_slug" ON "tracked_links" ("slug");
|
||
|
|
CREATE INDEX IF NOT EXISTS "idx_tracked_links_send" ON "tracked_links" ("send_id");
|
||
|
|
CREATE INDEX IF NOT EXISTS "idx_tracked_links_port" ON "tracked_links" ("port_id", "created_at" DESC);
|
||
|
|
|
||
|
|
CREATE TABLE IF NOT EXISTS "tracked_link_clicks" (
|
||
|
|
"id" text PRIMARY KEY,
|
||
|
|
"tracked_link_id" text NOT NULL REFERENCES "tracked_links"("id") ON DELETE CASCADE,
|
||
|
|
"port_id" text NOT NULL REFERENCES "ports"("id"),
|
||
|
|
"clicked_at" timestamptz NOT NULL DEFAULT now(),
|
||
|
|
"user_agent" text,
|
||
|
|
"referer" text
|
||
|
|
);
|
||
|
|
|
||
|
|
CREATE INDEX IF NOT EXISTS "idx_tlc_link" ON "tracked_link_clicks" ("tracked_link_id", "clicked_at" DESC);
|
||
|
|
CREATE INDEX IF NOT EXISTS "idx_tlc_port" ON "tracked_link_clicks" ("port_id", "clicked_at" DESC);
|