306 lines
15 KiB
C#
306 lines
15 KiB
C#
using Microsoft.EntityFrameworkCore.Migrations;
|
|
|
|
namespace Tiku.Infrastructure.Persistence.Migrations;
|
|
|
|
internal static class MigrationBuilderDatabaseObjectExtensions
|
|
{
|
|
public static void EnsureAuthorizationCacheInvalidationTriggers(this MigrationBuilder migrationBuilder)
|
|
{
|
|
migrationBuilder.Sql("""
|
|
INSERT INTO authorization_scope_versions
|
|
(id, realm, tenant_id, version, created_at, updated_at)
|
|
VALUES
|
|
(gen_random_uuid(), 'platform', NULL, 1, now(), now())
|
|
ON CONFLICT DO NOTHING;
|
|
|
|
INSERT INTO authorization_scope_versions
|
|
(id, realm, tenant_id, version, created_at, updated_at)
|
|
SELECT gen_random_uuid(), 'tenant', id, 1, now(), now()
|
|
FROM tenants
|
|
ON CONFLICT DO NOTHING;
|
|
|
|
CREATE OR REPLACE FUNCTION tiku_bump_tenant_authorization_version()
|
|
RETURNS trigger
|
|
LANGUAGE plpgsql
|
|
AS $$
|
|
DECLARE
|
|
scope_tenant_id uuid;
|
|
next_version bigint;
|
|
BEGIN
|
|
IF TG_OP = 'DELETE' THEN
|
|
scope_tenant_id := OLD.tenant_id;
|
|
ELSE
|
|
scope_tenant_id := NEW.tenant_id;
|
|
END IF;
|
|
|
|
UPDATE authorization_scope_versions
|
|
SET version = version + 1, updated_at = now()
|
|
WHERE realm = 'tenant' AND tenant_id = scope_tenant_id
|
|
RETURNING version INTO next_version;
|
|
|
|
IF next_version IS NOT NULL THEN
|
|
INSERT INTO authorization_cache_invalidations
|
|
(id, target_type, tenant_id, realm, version, attempt_count, created_at, updated_at)
|
|
VALUES
|
|
(gen_random_uuid(), 'scope', scope_tenant_id, 'tenant', next_version, 0, now(), now());
|
|
END IF;
|
|
IF TG_OP = 'DELETE' THEN RETURN OLD; END IF;
|
|
RETURN NEW;
|
|
END;
|
|
$$;
|
|
|
|
CREATE OR REPLACE FUNCTION tiku_bump_platform_authorization_version()
|
|
RETURNS trigger
|
|
LANGUAGE plpgsql
|
|
AS $$
|
|
DECLARE next_version bigint;
|
|
BEGIN
|
|
UPDATE authorization_scope_versions
|
|
SET version = version + 1, updated_at = now()
|
|
WHERE realm = 'platform' AND tenant_id IS NULL
|
|
RETURNING version INTO next_version;
|
|
INSERT INTO authorization_cache_invalidations
|
|
(id, target_type, realm, version, attempt_count, created_at, updated_at)
|
|
VALUES
|
|
(gen_random_uuid(), 'scope', 'platform', next_version, 0, now(), now());
|
|
IF TG_OP = 'DELETE' THEN RETURN OLD; END IF;
|
|
RETURN NEW;
|
|
END;
|
|
$$;
|
|
|
|
CREATE OR REPLACE FUNCTION tiku_bump_all_authorization_versions()
|
|
RETURNS trigger
|
|
LANGUAGE plpgsql
|
|
AS $$
|
|
BEGIN
|
|
UPDATE authorization_scope_versions SET version = version + 1, updated_at = now();
|
|
INSERT INTO authorization_cache_invalidations
|
|
(id, target_type, tenant_id, realm, version, attempt_count, created_at, updated_at)
|
|
SELECT gen_random_uuid(), 'scope', tenant_id, realm, version, 0, now(), now()
|
|
FROM authorization_scope_versions;
|
|
IF TG_OP = 'DELETE' THEN RETURN OLD; END IF;
|
|
RETURN NEW;
|
|
END;
|
|
$$;
|
|
|
|
DROP TRIGGER IF EXISTS trg_tenant_backend_roles_authorization_version ON tenant_backend_roles;
|
|
DROP TRIGGER IF EXISTS trg_tenant_backend_role_permissions_authorization_version ON tenant_backend_role_permissions;
|
|
DROP TRIGGER IF EXISTS trg_tenant_backend_user_roles_authorization_version ON tenant_backend_user_roles;
|
|
DROP TRIGGER IF EXISTS trg_platform_backend_roles_authorization_version ON platform_backend_roles;
|
|
DROP TRIGGER IF EXISTS trg_platform_backend_role_permissions_authorization_version ON platform_backend_role_permissions;
|
|
DROP TRIGGER IF EXISTS trg_platform_backend_user_roles_authorization_version ON platform_backend_user_roles;
|
|
DROP TRIGGER IF EXISTS trg_backend_permissions_authorization_version ON backend_permissions;
|
|
|
|
CREATE TRIGGER trg_tenant_backend_roles_authorization_version
|
|
AFTER INSERT OR UPDATE OR DELETE ON tenant_backend_roles
|
|
FOR EACH ROW EXECUTE FUNCTION tiku_bump_tenant_authorization_version();
|
|
CREATE TRIGGER trg_tenant_backend_role_permissions_authorization_version
|
|
AFTER INSERT OR UPDATE OR DELETE ON tenant_backend_role_permissions
|
|
FOR EACH ROW EXECUTE FUNCTION tiku_bump_tenant_authorization_version();
|
|
CREATE TRIGGER trg_tenant_backend_user_roles_authorization_version
|
|
AFTER INSERT OR UPDATE OR DELETE ON tenant_backend_user_roles
|
|
FOR EACH ROW EXECUTE FUNCTION tiku_bump_tenant_authorization_version();
|
|
|
|
CREATE TRIGGER trg_platform_backend_roles_authorization_version
|
|
AFTER INSERT OR UPDATE OR DELETE ON platform_backend_roles
|
|
FOR EACH ROW EXECUTE FUNCTION tiku_bump_platform_authorization_version();
|
|
CREATE TRIGGER trg_platform_backend_role_permissions_authorization_version
|
|
AFTER INSERT OR UPDATE OR DELETE ON platform_backend_role_permissions
|
|
FOR EACH ROW EXECUTE FUNCTION tiku_bump_platform_authorization_version();
|
|
CREATE TRIGGER trg_platform_backend_user_roles_authorization_version
|
|
AFTER INSERT OR UPDATE OR DELETE ON platform_backend_user_roles
|
|
FOR EACH ROW EXECUTE FUNCTION tiku_bump_platform_authorization_version();
|
|
|
|
CREATE TRIGGER trg_backend_permissions_authorization_version
|
|
AFTER INSERT OR UPDATE OR DELETE ON backend_permissions
|
|
FOR EACH STATEMENT EXECUTE FUNCTION tiku_bump_all_authorization_versions();
|
|
""");
|
|
}
|
|
|
|
public static void DropAuthorizationCacheInvalidationTriggers(this MigrationBuilder migrationBuilder)
|
|
{
|
|
migrationBuilder.Sql("""
|
|
DROP FUNCTION IF EXISTS tiku_bump_tenant_authorization_version() CASCADE;
|
|
DROP FUNCTION IF EXISTS tiku_bump_platform_authorization_version() CASCADE;
|
|
DROP FUNCTION IF EXISTS tiku_bump_all_authorization_versions() CASCADE;
|
|
""");
|
|
}
|
|
|
|
public static void EnsureQueryOptimizationIndexes(this MigrationBuilder migrationBuilder)
|
|
{
|
|
migrationBuilder.Sql("""
|
|
CREATE INDEX IF NOT EXISTS ix_scoreline_records_tenant_id_year_school_name_major_name_id
|
|
ON scoreline_records (tenant_id, year DESC, school_name, major_name, id);
|
|
CREATE INDEX IF NOT EXISTS ix_scoreline_records_field_values_jsonb_path
|
|
ON scoreline_records USING gin (field_values jsonb_path_ops);
|
|
CREATE INDEX IF NOT EXISTS ix_scoreline_records_school_name_trgm
|
|
ON scoreline_records USING gin (school_name gin_trgm_ops) WHERE school_name IS NOT NULL;
|
|
CREATE INDEX IF NOT EXISTS ix_scoreline_records_major_name_trgm
|
|
ON scoreline_records USING gin (major_name gin_trgm_ops) WHERE major_name IS NOT NULL;
|
|
CREATE INDEX IF NOT EXISTS ix_background_jobs_pending_due
|
|
ON background_jobs (run_after, created_at, id) WHERE status = 'pending';
|
|
CREATE INDEX IF NOT EXISTS ix_background_jobs_processing_lease
|
|
ON background_jobs (lock_expires_at, created_at, id) WHERE status = 'processing';
|
|
""");
|
|
}
|
|
|
|
public static void DropQueryOptimizationIndexes(this MigrationBuilder migrationBuilder)
|
|
{
|
|
migrationBuilder.Sql("""
|
|
DROP INDEX IF EXISTS ix_background_jobs_processing_lease;
|
|
DROP INDEX IF EXISTS ix_background_jobs_pending_due;
|
|
DROP INDEX IF EXISTS ix_scoreline_records_major_name_trgm;
|
|
DROP INDEX IF EXISTS ix_scoreline_records_school_name_trgm;
|
|
DROP INDEX IF EXISTS ix_scoreline_records_field_values_jsonb_path;
|
|
DROP INDEX IF EXISTS ix_scoreline_records_tenant_id_year_school_name_major_name_id;
|
|
""");
|
|
}
|
|
|
|
public static void EnsureContentV2ImmutabilityGuards(this MigrationBuilder migrationBuilder)
|
|
{
|
|
migrationBuilder.Sql("""
|
|
CREATE OR REPLACE FUNCTION tiku_guard_question_revision_immutable()
|
|
RETURNS trigger
|
|
LANGUAGE plpgsql
|
|
AS $$
|
|
BEGIN
|
|
RAISE EXCEPTION 'question revisions are immutable';
|
|
END;
|
|
$$;
|
|
|
|
DROP TRIGGER IF EXISTS trg_question_revisions_immutable ON question_revisions;
|
|
CREATE TRIGGER trg_question_revisions_immutable
|
|
BEFORE UPDATE OR DELETE ON question_revisions
|
|
FOR EACH ROW EXECUTE FUNCTION tiku_guard_question_revision_immutable();
|
|
|
|
CREATE OR REPLACE FUNCTION tiku_guard_published_manifest_version_immutable()
|
|
RETURNS trigger
|
|
LANGUAGE plpgsql
|
|
AS $$
|
|
BEGIN
|
|
IF OLD.status = 'published' THEN
|
|
RAISE EXCEPTION 'published product access manifest versions are immutable';
|
|
END IF;
|
|
IF TG_OP = 'DELETE' THEN RETURN OLD; END IF;
|
|
RETURN NEW;
|
|
END;
|
|
$$;
|
|
|
|
DROP TRIGGER IF EXISTS trg_product_manifest_versions_published_immutable
|
|
ON product_access_manifest_versions;
|
|
CREATE TRIGGER trg_product_manifest_versions_published_immutable
|
|
BEFORE UPDATE OR DELETE ON product_access_manifest_versions
|
|
FOR EACH ROW EXECUTE FUNCTION tiku_guard_published_manifest_version_immutable();
|
|
|
|
CREATE OR REPLACE FUNCTION tiku_guard_published_manifest_child_immutable()
|
|
RETURNS trigger
|
|
LANGUAGE plpgsql
|
|
AS $$
|
|
DECLARE
|
|
parent_tenant_id uuid;
|
|
parent_version_id uuid;
|
|
parent_status text;
|
|
BEGIN
|
|
IF TG_OP = 'DELETE' THEN
|
|
parent_tenant_id := OLD.tenant_id;
|
|
parent_version_id := OLD.product_access_manifest_version_id;
|
|
ELSE
|
|
parent_tenant_id := NEW.tenant_id;
|
|
parent_version_id := NEW.product_access_manifest_version_id;
|
|
END IF;
|
|
SELECT status INTO parent_status
|
|
FROM product_access_manifest_versions
|
|
WHERE tenant_id = parent_tenant_id AND id = parent_version_id;
|
|
IF parent_status = 'published' THEN
|
|
RAISE EXCEPTION 'children of a published product access manifest version are immutable';
|
|
END IF;
|
|
IF TG_OP = 'DELETE' THEN RETURN OLD; END IF;
|
|
RETURN NEW;
|
|
END;
|
|
$$;
|
|
|
|
DROP TRIGGER IF EXISTS trg_product_manifest_releases_published_immutable ON product_manifest_releases;
|
|
DROP TRIGGER IF EXISTS trg_product_manifest_targets_published_immutable ON product_manifest_targets;
|
|
DROP TRIGGER IF EXISTS trg_product_manifest_resources_published_immutable ON product_manifest_resources;
|
|
CREATE TRIGGER trg_product_manifest_releases_published_immutable
|
|
BEFORE INSERT OR UPDATE OR DELETE ON product_manifest_releases
|
|
FOR EACH ROW EXECUTE FUNCTION tiku_guard_published_manifest_child_immutable();
|
|
CREATE TRIGGER trg_product_manifest_targets_published_immutable
|
|
BEFORE INSERT OR UPDATE OR DELETE ON product_manifest_targets
|
|
FOR EACH ROW EXECUTE FUNCTION tiku_guard_published_manifest_child_immutable();
|
|
CREATE TRIGGER trg_product_manifest_resources_published_immutable
|
|
BEFORE INSERT OR UPDATE OR DELETE ON product_manifest_resources
|
|
FOR EACH ROW EXECUTE FUNCTION tiku_guard_published_manifest_child_immutable();
|
|
|
|
CREATE OR REPLACE FUNCTION tiku_guard_published_content_release_immutable()
|
|
RETURNS trigger
|
|
LANGUAGE plpgsql
|
|
AS $$
|
|
BEGIN
|
|
IF OLD.status = 'published' THEN
|
|
RAISE EXCEPTION 'published content releases are immutable';
|
|
END IF;
|
|
IF TG_OP = 'DELETE' THEN RETURN OLD; END IF;
|
|
RETURN NEW;
|
|
END;
|
|
$$;
|
|
|
|
DROP TRIGGER IF EXISTS trg_content_releases_published_immutable ON content_releases;
|
|
CREATE TRIGGER trg_content_releases_published_immutable
|
|
BEFORE UPDATE OR DELETE ON content_releases
|
|
FOR EACH ROW EXECUTE FUNCTION tiku_guard_published_content_release_immutable();
|
|
|
|
CREATE OR REPLACE FUNCTION tiku_guard_published_content_release_child_immutable()
|
|
RETURNS trigger
|
|
LANGUAGE plpgsql
|
|
AS $$
|
|
DECLARE
|
|
parent_tenant_id uuid;
|
|
parent_release_id uuid;
|
|
parent_status text;
|
|
BEGIN
|
|
IF TG_OP = 'DELETE' THEN
|
|
parent_tenant_id := OLD.tenant_id;
|
|
parent_release_id := OLD.content_release_id;
|
|
ELSE
|
|
parent_tenant_id := NEW.tenant_id;
|
|
parent_release_id := NEW.content_release_id;
|
|
END IF;
|
|
SELECT status INTO parent_status
|
|
FROM content_releases
|
|
WHERE tenant_id = parent_tenant_id AND id = parent_release_id;
|
|
IF parent_status = 'published' THEN
|
|
RAISE EXCEPTION 'children of a published content release are immutable';
|
|
END IF;
|
|
IF TG_OP = 'DELETE' THEN RETURN OLD; END IF;
|
|
RETURN NEW;
|
|
END;
|
|
$$;
|
|
|
|
DROP TRIGGER IF EXISTS trg_audience_segments_release_immutable ON audience_segments;
|
|
DROP TRIGGER IF EXISTS trg_audience_segment_members_release_immutable ON audience_segment_members;
|
|
DROP TRIGGER IF EXISTS trg_content_release_questions_release_immutable ON content_release_questions;
|
|
CREATE TRIGGER trg_audience_segments_release_immutable
|
|
BEFORE INSERT OR UPDATE OR DELETE ON audience_segments
|
|
FOR EACH ROW EXECUTE FUNCTION tiku_guard_published_content_release_child_immutable();
|
|
CREATE TRIGGER trg_audience_segment_members_release_immutable
|
|
BEFORE INSERT OR UPDATE OR DELETE ON audience_segment_members
|
|
FOR EACH ROW EXECUTE FUNCTION tiku_guard_published_content_release_child_immutable();
|
|
CREATE TRIGGER trg_content_release_questions_release_immutable
|
|
BEFORE INSERT OR UPDATE OR DELETE ON content_release_questions
|
|
FOR EACH ROW EXECUTE FUNCTION tiku_guard_published_content_release_child_immutable();
|
|
""");
|
|
}
|
|
|
|
public static void DropContentV2ImmutabilityGuards(this MigrationBuilder migrationBuilder)
|
|
{
|
|
migrationBuilder.Sql("""
|
|
DROP FUNCTION IF EXISTS tiku_guard_published_content_release_child_immutable() CASCADE;
|
|
DROP FUNCTION IF EXISTS tiku_guard_published_content_release_immutable() CASCADE;
|
|
DROP FUNCTION IF EXISTS tiku_guard_published_manifest_child_immutable() CASCADE;
|
|
DROP FUNCTION IF EXISTS tiku_guard_published_manifest_version_immutable() CASCADE;
|
|
DROP FUNCTION IF EXISTS tiku_guard_question_revision_immutable() CASCADE;
|
|
""");
|
|
}
|
|
}
|