Refactor database schema for roles and visibility management

- Updated the roles table to use `code` as the primary key, enhancing readability and simplifying junction tables.
- Modified `user_roles` and `product_role_visibility` tables to reference `role_code` instead of `role_id`, maintaining many-to-many relationships.
- Added foreign key constraints and created new indexes to optimize queries.
- Removed the previous migration file that was no longer needed, ensuring a cleaner migration history.
- Updated related documentation to reflect the new schema changes and their benefits.
This commit is contained in:
Bastian Masanek
2025-11-03 10:54:58 +01:00
parent 14fc293ebe
commit d6404434a3
5 changed files with 99 additions and 123 deletions

View File

@@ -1,29 +1,20 @@
CREATE TYPE "public"."role_code" AS ENUM('private', 'educator', 'company');--> statement-breakpoint
CREATE TYPE "public"."role_request_status" AS ENUM('pending', 'approved', 'rejected');--> statement-breakpoint
CREATE TABLE "product_role_visibility" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"product_id" uuid NOT NULL,
"role_id" uuid NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "roles" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"code" "role_code" NOT NULL,
"code" "role_code" PRIMARY KEY NOT NULL,
"display_name" text NOT NULL,
"description" text NOT NULL,
"requires_approval" boolean DEFAULT false NOT NULL,
"sort_order" integer DEFAULT 0 NOT NULL,
"active" boolean DEFAULT true NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL,
CONSTRAINT "roles_code_unique" UNIQUE("code")
"updated_at" timestamp DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "user_roles" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"user_id" uuid NOT NULL,
"role_id" uuid NOT NULL,
"role_code" "role_code" NOT NULL,
"status" "role_request_status" DEFAULT 'pending' NOT NULL,
"organization_name" text,
"admin_notes" text,
@@ -32,12 +23,21 @@ CREATE TABLE "user_roles" (
"updated_at" timestamp DEFAULT now() NOT NULL
);
--> statement-breakpoint
ALTER TABLE "product_role_visibility" ADD CONSTRAINT "product_role_visibility_product_id_products_id_fk" FOREIGN KEY ("product_id") REFERENCES "public"."products"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "product_role_visibility" ADD CONSTRAINT "product_role_visibility_role_id_roles_id_fk" FOREIGN KEY ("role_id") REFERENCES "public"."roles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE TABLE "product_role_visibility" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"product_id" uuid NOT NULL,
"role_code" "role_code" NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL
);
--> statement-breakpoint
ALTER TABLE "user_roles" ADD CONSTRAINT "user_roles_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "user_roles" ADD CONSTRAINT "user_roles_role_id_roles_id_fk" FOREIGN KEY ("role_id") REFERENCES "public"."roles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE INDEX "product_role_visibility_product_id_role_id_unique" ON "product_role_visibility" USING btree ("product_id","role_id");--> statement-breakpoint
CREATE INDEX "product_role_visibility_product_id_idx" ON "product_role_visibility" USING btree ("product_id");--> statement-breakpoint
CREATE INDEX "user_roles_user_id_role_id_unique" ON "user_roles" USING btree ("user_id","role_id");--> statement-breakpoint
ALTER TABLE "user_roles" ADD CONSTRAINT "user_roles_role_code_roles_code_fk" FOREIGN KEY ("role_code") REFERENCES "public"."roles"("code") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "product_role_visibility" ADD CONSTRAINT "product_role_visibility_product_id_products_id_fk" FOREIGN KEY ("product_id") REFERENCES "public"."products"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "product_role_visibility" ADD CONSTRAINT "product_role_visibility_role_code_roles_code_fk" FOREIGN KEY ("role_code") REFERENCES "public"."roles"("code") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE INDEX "user_roles_user_id_role_code_unique" ON "user_roles" USING btree ("user_id","role_code");--> statement-breakpoint
CREATE INDEX "user_roles_user_id_idx" ON "user_roles" USING btree ("user_id");--> statement-breakpoint
CREATE INDEX "user_roles_status_idx" ON "user_roles" USING btree ("status");
CREATE INDEX "user_roles_role_code_idx" ON "user_roles" USING btree ("role_code");--> statement-breakpoint
CREATE INDEX "user_roles_status_idx" ON "user_roles" USING btree ("status");--> statement-breakpoint
CREATE INDEX "product_role_visibility_product_id_role_code_unique" ON "product_role_visibility" USING btree ("product_id","role_code");--> statement-breakpoint
CREATE INDEX "product_role_visibility_product_id_idx" ON "product_role_visibility" USING btree ("product_id");--> statement-breakpoint
CREATE INDEX "product_role_visibility_role_code_idx" ON "product_role_visibility" USING btree ("role_code");

View File

@@ -1,58 +0,0 @@
-- Migration: Refactor roles table to use code as primary key
-- This migration drops and recreates the role-related tables with the new schema
-- Drop existing tables (CASCADE removes dependent foreign keys)
DROP TABLE IF EXISTS "product_role_visibility" CASCADE;--> statement-breakpoint
DROP TABLE IF EXISTS "user_roles" CASCADE;--> statement-breakpoint
DROP TABLE IF EXISTS "roles" CASCADE;--> statement-breakpoint
-- Recreate roles table with code as primary key
CREATE TABLE "roles" (
"code" "role_code" PRIMARY KEY NOT NULL,
"display_name" text NOT NULL,
"description" text NOT NULL,
"requires_approval" boolean DEFAULT false NOT NULL,
"sort_order" integer DEFAULT 0 NOT NULL,
"active" boolean DEFAULT true NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL
);
--> statement-breakpoint
-- Recreate user_roles table with roleCode instead of roleId
CREATE TABLE "user_roles" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"user_id" uuid NOT NULL,
"role_code" "role_code" NOT NULL,
"status" "role_request_status" DEFAULT 'pending' NOT NULL,
"organization_name" text,
"admin_notes" text,
"status_history" jsonb DEFAULT '[]' NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL
);
--> statement-breakpoint
-- Recreate product_role_visibility table with roleCode instead of roleId
CREATE TABLE "product_role_visibility" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"product_id" uuid NOT NULL,
"role_code" "role_code" NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL
);
--> statement-breakpoint
-- Add foreign key constraints
ALTER TABLE "user_roles" ADD CONSTRAINT "user_roles_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "user_roles" ADD CONSTRAINT "user_roles_role_code_roles_code_fk" FOREIGN KEY ("role_code") REFERENCES "public"."roles"("code") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "product_role_visibility" ADD CONSTRAINT "product_role_visibility_product_id_products_id_fk" FOREIGN KEY ("product_id") REFERENCES "public"."products"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "product_role_visibility" ADD CONSTRAINT "product_role_visibility_role_code_roles_code_fk" FOREIGN KEY ("role_code") REFERENCES "public"."roles"("code") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
-- Create indexes
CREATE INDEX "user_roles_user_id_role_code_unique" ON "user_roles" USING btree ("user_id","role_code");--> statement-breakpoint
CREATE INDEX "user_roles_user_id_idx" ON "user_roles" USING btree ("user_id");--> statement-breakpoint
CREATE INDEX "user_roles_role_code_idx" ON "user_roles" USING btree ("role_code");--> statement-breakpoint
CREATE INDEX "user_roles_status_idx" ON "user_roles" USING btree ("status");--> statement-breakpoint
CREATE INDEX "product_role_visibility_product_id_role_code_unique" ON "product_role_visibility" USING btree ("product_id","role_code");--> statement-breakpoint
CREATE INDEX "product_role_visibility_product_id_idx" ON "product_role_visibility" USING btree ("product_id");--> statement-breakpoint
CREATE INDEX "product_role_visibility_role_code_idx" ON "product_role_visibility" USING btree ("role_code");

View File

@@ -385,9 +385,10 @@
"primaryKey": false,
"notNull": true
},
"role_id": {
"name": "role_id",
"type": "uuid",
"role_code": {
"name": "role_code",
"type": "role_code",
"typeSchema": "public",
"primaryKey": false,
"notNull": true
},
@@ -400,8 +401,8 @@
}
},
"indexes": {
"product_role_visibility_product_id_role_id_unique": {
"name": "product_role_visibility_product_id_role_id_unique",
"product_role_visibility_product_id_role_code_unique": {
"name": "product_role_visibility_product_id_role_code_unique",
"columns": [
{
"expression": "product_id",
@@ -410,7 +411,7 @@
"nulls": "last"
},
{
"expression": "role_id",
"expression": "role_code",
"isExpression": false,
"asc": true,
"nulls": "last"
@@ -435,6 +436,21 @@
"concurrently": false,
"method": "btree",
"with": {}
},
"product_role_visibility_role_code_idx": {
"name": "product_role_visibility_role_code_idx",
"columns": [
{
"expression": "role_code",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
@@ -451,15 +467,15 @@
"onDelete": "cascade",
"onUpdate": "no action"
},
"product_role_visibility_role_id_roles_id_fk": {
"name": "product_role_visibility_role_id_roles_id_fk",
"product_role_visibility_role_code_roles_code_fk": {
"name": "product_role_visibility_role_code_roles_code_fk",
"tableFrom": "product_role_visibility",
"tableTo": "roles",
"columnsFrom": [
"role_id"
"role_code"
],
"columnsTo": [
"id"
"code"
],
"onDelete": "cascade",
"onUpdate": "no action"
@@ -607,18 +623,11 @@
"name": "roles",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"code": {
"name": "code",
"type": "role_code",
"typeSchema": "public",
"primaryKey": false,
"primaryKey": true,
"notNull": true
},
"display_name": {
@@ -672,15 +681,7 @@
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"roles_code_unique": {
"name": "roles_code_unique",
"nullsNotDistinct": false,
"columns": [
"code"
]
}
},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
@@ -702,9 +703,10 @@
"primaryKey": false,
"notNull": true
},
"role_id": {
"name": "role_id",
"type": "uuid",
"role_code": {
"name": "role_code",
"type": "role_code",
"typeSchema": "public",
"primaryKey": false,
"notNull": true
},
@@ -751,8 +753,8 @@
}
},
"indexes": {
"user_roles_user_id_role_id_unique": {
"name": "user_roles_user_id_role_id_unique",
"user_roles_user_id_role_code_unique": {
"name": "user_roles_user_id_role_code_unique",
"columns": [
{
"expression": "user_id",
@@ -761,7 +763,7 @@
"nulls": "last"
},
{
"expression": "role_id",
"expression": "role_code",
"isExpression": false,
"asc": true,
"nulls": "last"
@@ -787,6 +789,21 @@
"method": "btree",
"with": {}
},
"user_roles_role_code_idx": {
"name": "user_roles_role_code_idx",
"columns": [
{
"expression": "role_code",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"user_roles_status_idx": {
"name": "user_roles_status_idx",
"columns": [
@@ -817,15 +834,15 @@
"onDelete": "cascade",
"onUpdate": "no action"
},
"user_roles_role_id_roles_id_fk": {
"name": "user_roles_role_id_roles_id_fk",
"user_roles_role_code_roles_code_fk": {
"name": "user_roles_role_code_roles_code_fk",
"tableFrom": "user_roles",
"tableTo": "roles",
"columnsFrom": [
"role_id"
"role_code"
],
"columnsTo": [
"id"
"code"
],
"onDelete": "cascade",
"onUpdate": "no action"

View File

@@ -15,13 +15,6 @@
"when": 1762074397305,
"tag": "0001_clammy_bulldozer",
"breakpoints": true
},
{
"idx": 2,
"version": "7",
"when": 1762157410000,
"tag": "0002_refactor_roles_primary_key",
"breakpoints": true
}
]
}