Files
tiku-backend.net/Tiku.Infrastructure/Persistence/Migrations/20260801033749_AddAuthorizationCaching.cs

202 lines
10 KiB
C#

using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Tiku.Infrastructure.Persistence.Migrations
{
/// <inheritdoc />
public partial class AddAuthorizationCaching : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "authorization_cache_invalidations",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"),
target_type = table.Column<string>(type: "character varying(40)", maxLength: 40, nullable: false),
tenant_id = table.Column<Guid>(type: "uuid", nullable: true),
user_id = table.Column<Guid>(type: "uuid", nullable: true),
session_id = table.Column<Guid>(type: "uuid", nullable: true),
realm = table.Column<string>(type: "text", nullable: true),
version = table.Column<long>(type: "bigint", nullable: true),
processed_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
attempt_count = table.Column<int>(type: "integer", nullable: false),
last_error = table.Column<string>(type: "character varying(2000)", maxLength: 2000, nullable: true),
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"),
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()")
},
constraints: table =>
{
table.PrimaryKey("pk_authorization_cache_invalidations", x => x.id);
});
migrationBuilder.CreateTable(
name: "authorization_scope_versions",
columns: table => new
{
id = table.Column<Guid>(type: "uuid", nullable: false, defaultValueSql: "gen_random_uuid()"),
realm = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
tenant_id = table.Column<Guid>(type: "uuid", nullable: true),
version = table.Column<long>(type: "bigint", nullable: false, defaultValue: 1L),
created_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()"),
updated_at = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "now()")
},
constraints: table =>
{
table.PrimaryKey("pk_authorization_scope_versions", x => x.id);
table.CheckConstraint("ck_authorization_scope_versions_realm_tenant", "(realm = 'tenant' and tenant_id is not null) or (realm = 'platform' and tenant_id is null)");
table.ForeignKey(
name: "fk_authorization_scope_versions_tenants_tenant_id",
column: x => x.tenant_id,
principalTable: "tenants",
principalColumn: "id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "ix_authorization_cache_invalidations_processed_at_created_at",
table: "authorization_cache_invalidations",
columns: new[] { "processed_at", "created_at" });
migrationBuilder.CreateIndex(
name: "ix_authorization_cache_invalidations_target_type_tenant_id_use~",
table: "authorization_cache_invalidations",
columns: new[] { "target_type", "tenant_id", "user_id", "session_id", "version" });
migrationBuilder.CreateIndex(
name: "ix_authorization_scope_versions_realm",
table: "authorization_scope_versions",
column: "realm",
unique: true,
filter: "realm = 'platform'");
migrationBuilder.CreateIndex(
name: "ix_authorization_scope_versions_tenant_id",
table: "authorization_scope_versions",
column: "tenant_id",
unique: true,
filter: "tenant_id is not null");
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());
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;
""");
migrationBuilder.Sql("""
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;
$$;
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();
""");
}
/// <inheritdoc />
protected override void Down(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;
""");
migrationBuilder.DropTable(
name: "authorization_cache_invalidations");
migrationBuilder.DropTable(
name: "authorization_scope_versions");
}
}
}