feat: customisable workspace role permissions (#345)
* feat: setup schema for workspace roles * chore: regen migration * feat: add publicId to workspace roles * feat: setup default permissions * feat: add repo funcs * feat: setup basic router interactions * feat: add card permissions * feat: assert permissions for lists * feat: assert board permissions * feat: assert permission for remaining routes * feat: add permissions page to settings * feat: enable updating member roles * feat: order members by role and createdAt * feat: allow editing individual permissions * feat: reset role defaults * feat: clear all permission overrides * feat: allow users to delete entities they have created * feat: set roleId when inviting new members * feat: disable UI elements if user does not have permissions * feat: allow admins to assign the admin role to other users * feat: allow delete:list as default * refactor: centre permissions modal * chore: translations
This commit is contained in:
@@ -12,3 +12,4 @@ export * from "./integrations";
|
||||
export * from "./workspaces";
|
||||
export * from "./subscriptions";
|
||||
export * from "./workspaceInviteLinks";
|
||||
export * from "./permissions";
|
||||
|
||||
98
packages/db/src/schema/permissions.ts
Normal file
98
packages/db/src/schema/permissions.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { relations } from "drizzle-orm";
|
||||
import {
|
||||
bigint,
|
||||
bigserial,
|
||||
boolean,
|
||||
index,
|
||||
integer,
|
||||
pgTable,
|
||||
timestamp,
|
||||
uniqueIndex,
|
||||
varchar,
|
||||
} from "drizzle-orm/pg-core";
|
||||
|
||||
import { workspaces } from "./workspaces";
|
||||
|
||||
export const workspaceRoles = pgTable(
|
||||
"workspace_roles",
|
||||
{
|
||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
|
||||
workspaceId: bigint("workspaceId", { mode: "number" })
|
||||
.notNull()
|
||||
.references(() => workspaces.id, { onDelete: "cascade" }),
|
||||
name: varchar("name", { length: 64 }).notNull(),
|
||||
description: varchar("description", { length: 255 }),
|
||||
hierarchyLevel: integer("hierarchyLevel").notNull(),
|
||||
isSystem: boolean("isSystem").notNull().default(false),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updatedAt"),
|
||||
},
|
||||
(table) => [
|
||||
uniqueIndex("unique_role_per_workspace").on(table.workspaceId, table.name),
|
||||
index("workspace_roles_workspace_idx").on(table.workspaceId),
|
||||
],
|
||||
).enableRLS();
|
||||
|
||||
export const workspaceRolesRelations = relations(
|
||||
workspaceRoles,
|
||||
({ one, many }) => ({
|
||||
workspace: one(workspaces, {
|
||||
fields: [workspaceRoles.workspaceId],
|
||||
references: [workspaces.id],
|
||||
relationName: "workspaceRoles",
|
||||
}),
|
||||
permissions: many(workspaceRolePermissions),
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
export const workspaceRolePermissions = pgTable(
|
||||
"workspace_role_permissions",
|
||||
{
|
||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||
workspaceRoleId: bigint("workspaceRoleId", { mode: "number" })
|
||||
.notNull()
|
||||
.references(() => workspaceRoles.id, { onDelete: "cascade" }),
|
||||
permission: varchar("permission", { length: 64 }).notNull(),
|
||||
granted: boolean("granted").notNull().default(true),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
},
|
||||
(table) => [
|
||||
uniqueIndex("unique_role_permission").on(
|
||||
table.workspaceRoleId,
|
||||
table.permission,
|
||||
),
|
||||
index("role_permissions_role_idx").on(table.workspaceRoleId),
|
||||
],
|
||||
).enableRLS();
|
||||
|
||||
export const workspaceRolePermissionsRelations = relations(
|
||||
workspaceRolePermissions,
|
||||
({ one }) => ({
|
||||
role: one(workspaceRoles, {
|
||||
fields: [workspaceRolePermissions.workspaceRoleId],
|
||||
references: [workspaceRoles.id],
|
||||
relationName: "rolePermissions",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
export const workspaceMemberPermissions = pgTable(
|
||||
"workspace_member_permissions",
|
||||
{
|
||||
id: bigserial("id", { mode: "number" }).primaryKey(),
|
||||
workspaceMemberId: bigint("workspaceMemberId", { mode: "number" }).notNull(),
|
||||
permission: varchar("permission", { length: 64 }).notNull(),
|
||||
granted: boolean("granted").notNull().default(true),
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updatedAt"),
|
||||
},
|
||||
(table) => [
|
||||
uniqueIndex("unique_member_permission").on(
|
||||
table.workspaceMemberId,
|
||||
table.permission,
|
||||
),
|
||||
index("permission_member_idx").on(table.workspaceMemberId),
|
||||
],
|
||||
).enableRLS();
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
} from "drizzle-orm/pg-core";
|
||||
|
||||
import { boards } from "./boards";
|
||||
import { workspaceMemberPermissions, workspaceRoles } from "./permissions";
|
||||
import { subscription } from "./subscriptions";
|
||||
import { users } from "./users";
|
||||
|
||||
@@ -69,6 +70,7 @@ export const workspaceRelations = relations(workspaces, ({ one, many }) => ({
|
||||
members: many(workspaceMembers),
|
||||
boards: many(boards),
|
||||
subscriptions: many(subscription),
|
||||
roles: many(workspaceRoles),
|
||||
}));
|
||||
|
||||
export const workspaceMembers = pgTable("workspace_members", {
|
||||
@@ -86,13 +88,18 @@ export const workspaceMembers = pgTable("workspace_members", {
|
||||
deletedBy: uuid("deletedBy").references(() => users.id, {
|
||||
onDelete: "set null",
|
||||
}),
|
||||
// Legacy role enum
|
||||
role: memberRoleEnum("role").notNull(),
|
||||
roleId: bigint("roleId", { mode: "number" }).references(
|
||||
() => workspaceRoles.id,
|
||||
{ onDelete: "restrict" },
|
||||
),
|
||||
status: memberStatusEnum("status").default("invited").notNull(),
|
||||
}).enableRLS();
|
||||
|
||||
export const workspaceMembersRelations = relations(
|
||||
workspaceMembers,
|
||||
({ one }) => ({
|
||||
({ one, many }) => ({
|
||||
user: one(users, {
|
||||
fields: [workspaceMembers.userId],
|
||||
references: [users.id],
|
||||
@@ -103,6 +110,23 @@ export const workspaceMembersRelations = relations(
|
||||
references: [workspaces.id],
|
||||
relationName: "workspaceMembersWorkspace",
|
||||
}),
|
||||
workspaceRole: one(workspaceRoles, {
|
||||
fields: [workspaceMembers.roleId],
|
||||
references: [workspaceRoles.id],
|
||||
relationName: "workspaceMemberRole",
|
||||
}),
|
||||
permissions: many(workspaceMemberPermissions),
|
||||
}),
|
||||
);
|
||||
|
||||
export const workspaceMemberPermissionsRelations = relations(
|
||||
workspaceMemberPermissions,
|
||||
({ one }) => ({
|
||||
member: one(workspaceMembers, {
|
||||
fields: [workspaceMemberPermissions.workspaceMemberId],
|
||||
references: [workspaceMembers.id],
|
||||
relationName: "memberPermissions",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user