0027_migration_requests.sql57 lines · main
1-- 0027_migration_requests — customer-initiated migration intake.
2-- The dashboard wizard at /dashboard/projects/new/migrate writes one
3-- row per source-platform import request. During beta, every row is
4-- triaged by an operator via /dashboard/admin/migrations and either
5-- handled by the (forthcoming) automated adapter pipeline or migrated
6-- by hand for free. Once the adapter for a source ships, status
7-- transitions through `scheduled` → `in_progress` → `completed`
8-- without an operator in the loop.
9
10CREATE TABLE IF NOT EXISTS "migration_requests" (
11 "id" text PRIMARY KEY NOT NULL,
12 -- Requester. user_id is always set; org_id is the org the migrated
13 -- project should land in, defaulting to the user's personal org.
14 "user_id" text NOT NULL,
15 "org_id" text,
16 -- Source platform. Open vocabulary so we can add adapters without a
17 -- migration; the wizard restricts the picker to known sources.
18 "source" text NOT NULL,
19 "source_url" text,
20 "source_notes" text NOT NULL DEFAULT '',
21 -- Rough scale signals the operator uses to triage queue order.
22 "estimated_tables" integer,
23 "estimated_rows" bigint,
24 "estimated_functions" integer,
25 -- Urgency lets the customer self-report their timeline so we don't
26 -- have to ask twice. Values: exploring | this_week | this_month | this_quarter.
27 "urgency" text NOT NULL DEFAULT 'exploring',
28 -- Lifecycle. new → contacted → scheduled → in_progress → completed
29 -- (terminal) | cancelled (terminal).
30 "status" text NOT NULL DEFAULT 'new',
31 "contact_email" text NOT NULL,
32 -- Operator-only fields. Customer never sees these.
33 "assigned_to" text,
34 "operator_notes" text NOT NULL DEFAULT '',
35 "created_at" timestamp with time zone DEFAULT now() NOT NULL,
36 "updated_at" timestamp with time zone DEFAULT now() NOT NULL,
37 CONSTRAINT "migration_requests_user_fk"
38 FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE,
39 CONSTRAINT "migration_requests_org_fk"
40 FOREIGN KEY ("org_id") REFERENCES "organizations"("id") ON DELETE SET NULL,
41 CONSTRAINT "migration_requests_assigned_fk"
42 FOREIGN KEY ("assigned_to") REFERENCES "users"("id") ON DELETE SET NULL
43);
44
45-- Triage queue: operator opens the admin page sorted newest-first.
46CREATE INDEX IF NOT EXISTS "migration_requests_created_idx"
47 ON "migration_requests" USING btree ("created_at" DESC);
48
49-- Hot path for the customer's own list ("my migration requests").
50CREATE INDEX IF NOT EXISTS "migration_requests_user_idx"
51 ON "migration_requests" USING btree ("user_id", "created_at" DESC);
52
53-- "How many open requests do we have?" — drives the admin nav badge
54-- and the operator's at-a-glance load.
55CREATE INDEX IF NOT EXISTS "migration_requests_open_idx"
56 ON "migration_requests" USING btree ("created_at" DESC)
57 WHERE "status" NOT IN ('completed', 'cancelled');