Add ConnectPlain to open the DB without the custom search_path and switch migration and CLI flows to run on that plain connection. Wrap multi-statement goose migrations with StatementBegin/End to ensure statements are executed atomically. Move Stripe price outbox seeding into a dedicated stripe migration.
129 lines
5.4 KiB
SQL
129 lines
5.4 KiB
SQL
-- +goose Up
|
|
-- +goose StatementBegin
|
|
|
|
CREATE TABLE organization.organizations (
|
|
org_id UUID PRIMARY KEY DEFAULT uuidv7(),
|
|
name VARCHAR(255) NOT NULL,
|
|
slug VARCHAR(100) UNIQUE NOT NULL,
|
|
org_type VARCHAR(20) NOT NULL,
|
|
owner_person_id UUID NOT NULL REFERENCES identity.persons(person_id),
|
|
status VARCHAR(20) NOT NULL DEFAULT 'active',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE TABLE organization.roles (
|
|
role_id UUID PRIMARY KEY DEFAULT uuidv7(),
|
|
org_id UUID REFERENCES organization.organizations(org_id),
|
|
role_name VARCHAR(100) NOT NULL,
|
|
display_name VARCHAR(255) NOT NULL,
|
|
description TEXT,
|
|
is_system BOOLEAN NOT NULL DEFAULT FALSE,
|
|
permissions TEXT[] NOT NULL,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE TABLE organization.org_members (
|
|
org_member_id UUID PRIMARY KEY DEFAULT uuidv7(),
|
|
org_id UUID NOT NULL REFERENCES organization.organizations(org_id),
|
|
person_id UUID NOT NULL REFERENCES identity.persons(person_id),
|
|
role_id UUID NOT NULL REFERENCES organization.roles(role_id),
|
|
status VARCHAR(20) NOT NULL DEFAULT 'active',
|
|
joined_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
removed_at TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
UNIQUE (org_id, person_id)
|
|
);
|
|
|
|
CREATE TABLE organization.workspaces (
|
|
workspace_id UUID PRIMARY KEY DEFAULT uuidv7(),
|
|
org_id UUID NOT NULL REFERENCES organization.organizations(org_id),
|
|
name VARCHAR(255) NOT NULL,
|
|
slug VARCHAR(100) NOT NULL,
|
|
description TEXT,
|
|
status VARCHAR(20) NOT NULL DEFAULT 'active',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
UNIQUE (org_id, slug)
|
|
);
|
|
|
|
CREATE TABLE organization.role_assignments (
|
|
assignment_id UUID PRIMARY KEY DEFAULT uuidv7(),
|
|
role_id UUID NOT NULL REFERENCES organization.roles(role_id),
|
|
person_id UUID NOT NULL REFERENCES identity.persons(person_id),
|
|
org_id UUID NOT NULL REFERENCES organization.organizations(org_id),
|
|
scope_type VARCHAR(20) NOT NULL,
|
|
scope_id UUID NOT NULL,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
UNIQUE (role_id, person_id, org_id, scope_type, scope_id)
|
|
);
|
|
|
|
-- Indexes
|
|
CREATE INDEX idx_organizations_slug ON organization.organizations(slug);
|
|
CREATE INDEX idx_organizations_owner ON organization.organizations(owner_person_id);
|
|
CREATE INDEX idx_org_members_org_id ON organization.org_members(org_id);
|
|
CREATE INDEX idx_org_members_person_id ON organization.org_members(person_id);
|
|
CREATE INDEX idx_workspaces_org_id ON organization.workspaces(org_id);
|
|
CREATE INDEX idx_roles_org_id ON organization.roles(org_id);
|
|
CREATE INDEX idx_role_assignments_person_id ON organization.role_assignments(person_id);
|
|
CREATE INDEX idx_role_assignments_org_id ON organization.role_assignments(org_id);
|
|
|
|
-- Updated-at triggers (reuses update_updated_at_column from db/00001_init.sql).
|
|
CREATE TRIGGER trigger_organizations_updated_at
|
|
BEFORE UPDATE ON organization.organizations
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION update_updated_at_column();
|
|
|
|
CREATE TRIGGER trigger_roles_updated_at
|
|
BEFORE UPDATE ON organization.roles
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION update_updated_at_column();
|
|
|
|
CREATE TRIGGER trigger_org_members_updated_at
|
|
BEFORE UPDATE ON organization.org_members
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION update_updated_at_column();
|
|
|
|
CREATE TRIGGER trigger_workspaces_updated_at
|
|
BEFORE UPDATE ON organization.workspaces
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION update_updated_at_column();
|
|
|
|
CREATE TRIGGER trigger_role_assignments_updated_at
|
|
BEFORE UPDATE ON organization.role_assignments
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION update_updated_at_column();
|
|
|
|
-- Per-schema role grants (Decision 115).
|
|
GRANT ALL ON ALL TABLES IN SCHEMA organization TO organization_owner;
|
|
GRANT ALL ON ALL TABLES IN SCHEMA organization TO organization_writer;
|
|
GRANT SELECT ON ALL TABLES IN SCHEMA organization TO organization_reader;
|
|
|
|
-- +goose StatementEnd
|
|
|
|
-- +goose Down
|
|
-- +goose StatementBegin
|
|
DROP TRIGGER IF EXISTS trigger_role_assignments_updated_at ON organization.role_assignments;
|
|
DROP TRIGGER IF EXISTS trigger_workspaces_updated_at ON organization.workspaces;
|
|
DROP TRIGGER IF EXISTS trigger_org_members_updated_at ON organization.org_members;
|
|
DROP TRIGGER IF EXISTS trigger_roles_updated_at ON organization.roles;
|
|
DROP TRIGGER IF EXISTS trigger_organizations_updated_at ON organization.organizations;
|
|
DROP INDEX IF EXISTS organization.idx_role_assignments_org_id;
|
|
DROP INDEX IF EXISTS organization.idx_role_assignments_person_id;
|
|
DROP INDEX IF EXISTS organization.idx_roles_org_id;
|
|
DROP INDEX IF EXISTS organization.idx_workspaces_org_id;
|
|
DROP INDEX IF EXISTS organization.idx_org_members_person_id;
|
|
DROP INDEX IF EXISTS organization.idx_org_members_org_id;
|
|
DROP INDEX IF EXISTS organization.idx_organizations_owner;
|
|
DROP INDEX IF EXISTS organization.idx_organizations_slug;
|
|
DROP TABLE IF EXISTS organization.role_assignments;
|
|
DROP TABLE IF EXISTS organization.workspaces;
|
|
DROP TABLE IF EXISTS organization.org_members;
|
|
DROP TABLE IF EXISTS organization.roles;
|
|
DROP TABLE IF EXISTS organization.organizations;
|
|
|
|
-- +goose StatementEnd
|