feat(database): add member_role table and update user role management

- Introduced a new "member_role" table to define user roles with associated permissions.
- Implemented default roles (owner, admin, member) for each organization during migration.
- Updated the "users" table and other related tables to reflect changes in role management.
- Enhanced SQL migration scripts to ensure data integrity and consistency across the database.
This commit is contained in:
Mauricio Siu
2025-07-13 00:15:24 -06:00
parent e8475730fa
commit e1773a8f8b
4 changed files with 39 additions and 43 deletions

View File

@@ -8,8 +8,10 @@ CREATE TABLE "member_role" (
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL,
"organizationId" text NOT NULL,
CONSTRAINT "member_role_name_unique" UNIQUE("name")
CONSTRAINT "member_role_name_unique" UNIQUE("name"),
CONSTRAINT "role_name_unique" UNIQUE("name","organizationId")
);
-- Create default roles for each organization
DO $$
DECLARE
@@ -18,7 +20,7 @@ BEGIN
FOR org IN SELECT id FROM "organization"
LOOP
-- Insert owner role
INSERT INTO "organization_role" ("roleId", "name", "description", "canDelete", "is_system", "permissions", "created_at", "updated_at", "organizationId")
INSERT INTO "member_role" ("roleId", "name", "description", "canDelete", "is_system", "permissions", "created_at", "updated_at", "organizationId")
VALUES (
org.id || '_owner',
'owner',
@@ -32,7 +34,7 @@ BEGIN
);
-- Insert admin role
INSERT INTO "organization_role" ("roleId", "name", "description", "canDelete", "is_system", "permissions", "created_at", "updated_at", "organizationId")
INSERT INTO "member_role" ("roleId", "name", "description", "canDelete", "is_system", "permissions", "created_at", "updated_at", "organizationId")
VALUES (
org.id || '_admin',
'admin',
@@ -46,7 +48,7 @@ BEGIN
);
-- Insert member role
INSERT INTO "organization_role" ("roleId", "name", "description", "canDelete", "is_system", "permissions", "created_at", "updated_at", "organizationId")
INSERT INTO "member_role" ("roleId", "name", "description", "canDelete", "is_system", "permissions", "created_at", "updated_at", "organizationId")
VALUES (
org.id || '_member',
'member',
@@ -63,7 +65,6 @@ END $$;
--> statement-breakpoint
ALTER TABLE "user_temp" RENAME TO "users";--> statement-breakpoint
ALTER TABLE "users" DROP CONSTRAINT "user_temp_email_unique";--> statement-breakpoint
@@ -97,28 +98,10 @@ ALTER TABLE "apikey" ADD CONSTRAINT "apikey_user_id_users_id_fk" FOREIGN KEY ("u
ALTER TABLE "invitation" ADD CONSTRAINT "invitation_inviter_id_users_id_fk" FOREIGN KEY ("inviter_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "member" ADD CONSTRAINT "member_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "member" ADD CONSTRAINT "member_roleId_member_role_roleId_fk" FOREIGN KEY ("roleId") REFERENCES "public"."member_role"("roleId") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
-- Update existing members with corresponding roles based on their current role type
DO $$
DECLARE
mem RECORD;
BEGIN
FOR mem IN SELECT m.id, m.organization_id, m.role as role_type FROM "member" m
LOOP
UPDATE "member"
SET "roleId" = mem.organization_id || '_' || mem.role_type
WHERE id = mem.id;
END LOOP;
END $$;
ALTER TABLE "member" ALTER COLUMN "roleId" SET NOT NULL;
ALTER TABLE "organization" ADD CONSTRAINT "organization_owner_id_users_id_fk" FOREIGN KEY ("owner_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "two_factor" ADD CONSTRAINT "two_factor_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "schedule" ADD CONSTRAINT "schedule_userId_users_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
--> statement-breakpoint
CREATE TABLE "web_server" (
"webServerId" text PRIMARY KEY NOT NULL,
@@ -133,7 +116,6 @@ CREATE TABLE "web_server" (
"metricsConfig" jsonb DEFAULT '{"server":{"type":"Dokploy","refreshRate":60,"port":4500,"token":"","retentionDays":2,"cronJob":"","urlCallback":"","thresholds":{"cpu":0,"memory":0}},"containers":{"refreshRate":60,"services":{"include":[],"exclude":[]}}}'::jsonb NOT NULL
);
-- Migrar datos del usuario owner único hacia web_server
INSERT INTO "web_server" (
"webServerId",
"serverIp",

View File

@@ -1,5 +1,5 @@
{
"id": "6f7f4450-7a3f-4827-981d-1a609d1091e0",
"id": "56c5008e-c689-4a20-9f3d-06a06e9a5e39",
"prevId": "218e3c9b-ef86-4665-98af-56d65282b73b",
"version": "7",
"dialect": "postgresql",
@@ -4738,6 +4738,14 @@
"columns": [
"name"
]
},
"role_name_unique": {
"name": "role_name_unique",
"nullsNotDistinct": false,
"columns": [
"name",
"organizationId"
]
}
},
"policies": {},

View File

@@ -726,8 +726,8 @@
{
"idx": 103,
"version": "7",
"when": 1752386222325,
"tag": "0103_living_hemingway",
"when": 1752387187927,
"tag": "0103_swift_christian_walker",
"breakpoints": true
}
]

View File

@@ -1,5 +1,5 @@
import { relations } from "drizzle-orm";
import { pgTable, text, timestamp, boolean } from "drizzle-orm/pg-core";
import { pgTable, text, timestamp, boolean, unique } from "drizzle-orm/pg-core";
import { nanoid } from "nanoid";
import { organization, member } from "./account";
import { createInsertSchema } from "drizzle-zod";
@@ -110,21 +110,27 @@ export const defaultPermissions = [
},
] as const;
export const role = pgTable("member_role", {
roleId: text("roleId")
.primaryKey()
.$defaultFn(() => nanoid()),
name: text("name").notNull().unique(),
description: text("description"),
canDelete: boolean("canDelete").notNull().default(true),
isSystem: boolean("is_system").default(false),
permissions: text("permissions").array(),
createdAt: timestamp("created_at").notNull().defaultNow(),
updatedAt: timestamp("updated_at").notNull().defaultNow(),
organizationId: text("organizationId")
.notNull()
.references(() => organization.id, { onDelete: "cascade" }),
});
export const role = pgTable(
"member_role",
{
roleId: text("roleId")
.primaryKey()
.$defaultFn(() => nanoid()),
name: text("name").notNull().unique(),
description: text("description"),
canDelete: boolean("canDelete").notNull().default(true),
isSystem: boolean("is_system").default(false),
permissions: text("permissions").array(),
createdAt: timestamp("created_at").notNull().defaultNow(),
updatedAt: timestamp("updated_at").notNull().defaultNow(),
organizationId: text("organizationId")
.notNull()
.references(() => organization.id, { onDelete: "cascade" }),
},
(table) => ({
roleName: unique("role_name_unique").on(table.name, table.organizationId),
}),
);
export const roleRelations = relations(role, ({ one, many }) => ({
organization: one(organization, {