|
| 1 | +-- Identity Service Schema |
| 2 | +-- Uses UNQUALIFIED table names to support multi-organization routing via search_path. |
| 3 | +-- For local dev: search_path routes to default schema |
| 4 | +-- For multi-org: org schemas created by provisioning, search_path routes to org schema |
| 5 | + |
| 6 | +-- Create "identity" table (singular, unqualified - uses search_path for schema routing) |
| 7 | +CREATE TABLE "identity" ( |
| 8 | + "id" uuid NOT NULL DEFAULT gen_random_uuid(), |
| 9 | + "email" character varying(255) NOT NULL, |
| 10 | + "status" character varying(30) NOT NULL DEFAULT 'PENDING_INVITE', |
| 11 | + "password_hash" character varying(255) NOT NULL DEFAULT '', |
| 12 | + "external_idp" character varying(100) NOT NULL DEFAULT '', |
| 13 | + "external_sub" character varying(255) NOT NULL DEFAULT '', |
| 14 | + "failed_attempts" bigint NOT NULL DEFAULT 0, |
| 15 | + "version" bigint NOT NULL DEFAULT 1, |
| 16 | + "created_at" timestamptz NOT NULL DEFAULT now(), |
| 17 | + "updated_at" timestamptz NOT NULL DEFAULT now(), |
| 18 | + "deleted_at" timestamptz NULL, |
| 19 | + PRIMARY KEY ("id"), |
| 20 | + CONSTRAINT "chk_identity_status" CHECK (status IN ('PENDING_INVITE', 'ACTIVE', 'SUSPENDED', 'LOCKED')) |
| 21 | +); |
| 22 | +-- Indexes for identity |
| 23 | +CREATE UNIQUE INDEX "idx_identity_email" ON "identity" ("email") WHERE (deleted_at IS NULL); |
| 24 | +CREATE INDEX "idx_identity_deleted_at" ON "identity" ("deleted_at"); |
| 25 | + |
| 26 | +-- Create "role_assignment" table |
| 27 | +CREATE TABLE "role_assignment" ( |
| 28 | + "id" uuid NOT NULL DEFAULT gen_random_uuid(), |
| 29 | + "identity_id" uuid NOT NULL, |
| 30 | + "granted_by" uuid NOT NULL, |
| 31 | + "role" character varying(50) NOT NULL, |
| 32 | + "expires_at" timestamptz NULL, |
| 33 | + "revoked_at" timestamptz NULL, |
| 34 | + "revoked_by" uuid NULL, |
| 35 | + "created_at" timestamptz NOT NULL DEFAULT now(), |
| 36 | + "updated_at" timestamptz NOT NULL DEFAULT now(), |
| 37 | + PRIMARY KEY ("id"), |
| 38 | + CONSTRAINT "chk_role_assignment_role" CHECK (role IN ('VIEWER', 'OPERATOR', 'ADMIN', 'TENANT_OWNER', 'PLATFORM')), |
| 39 | + CONSTRAINT "fk_role_assignment_identity" FOREIGN KEY ("identity_id") REFERENCES "identity" ("id") ON UPDATE NO ACTION ON DELETE RESTRICT, |
| 40 | + CONSTRAINT "fk_role_assignment_granted_by" FOREIGN KEY ("granted_by") REFERENCES "identity" ("id") ON UPDATE NO ACTION ON DELETE RESTRICT, |
| 41 | + CONSTRAINT "fk_role_assignment_revoked_by" FOREIGN KEY ("revoked_by") REFERENCES "identity" ("id") ON UPDATE NO ACTION ON DELETE RESTRICT |
| 42 | +); |
| 43 | +-- Indexes for role_assignment |
| 44 | +CREATE INDEX "idx_role_assignment_identity" ON "role_assignment" ("identity_id"); |
| 45 | +-- Partial unique index enforces one active role per (identity, role) pair. |
| 46 | +-- CockroachDB: partial index on existing columns in a separate statement is safe. |
| 47 | +CREATE UNIQUE INDEX "idx_role_assignment_active" ON "role_assignment" ("identity_id", "role") WHERE (revoked_at IS NULL); |
| 48 | + |
| 49 | +-- Create "invitation" table |
| 50 | +CREATE TABLE "invitation" ( |
| 51 | + "id" uuid NOT NULL DEFAULT gen_random_uuid(), |
| 52 | + "identity_id" uuid NOT NULL, |
| 53 | + "invited_by" uuid NOT NULL, |
| 54 | + "token_hash" character varying(64) NOT NULL, |
| 55 | + "expires_at" timestamptz NOT NULL, |
| 56 | + "status" character varying(20) NOT NULL DEFAULT 'PENDING', |
| 57 | + "created_at" timestamptz NOT NULL DEFAULT now(), |
| 58 | + "updated_at" timestamptz NOT NULL DEFAULT now(), |
| 59 | + PRIMARY KEY ("id"), |
| 60 | + CONSTRAINT "chk_invitation_status" CHECK (status IN ('PENDING', 'ACCEPTED')), |
| 61 | + CONSTRAINT "fk_invitation_identity" FOREIGN KEY ("identity_id") REFERENCES "identity" ("id") ON UPDATE NO ACTION ON DELETE RESTRICT, |
| 62 | + CONSTRAINT "fk_invitation_invited_by" FOREIGN KEY ("invited_by") REFERENCES "identity" ("id") ON UPDATE NO ACTION ON DELETE RESTRICT |
| 63 | +); |
| 64 | +-- Indexes for invitation |
| 65 | +CREATE INDEX "idx_invitation_identity" ON "invitation" ("identity_id"); |
| 66 | +CREATE UNIQUE INDEX "idx_invitation_token_hash" ON "invitation" ("token_hash"); |
0 commit comments