feat: board access group permissions (#422)

* fix: cache is not exportet from react

* fix: format issue

* wip: add usage of group permissions

* feat: show inherited groups and add manage group

* refactor: improve board access management

* chore: address pull request feedback

* fix: type issues

* fix: migrations

* test: add unit tests for board permissions, permissions and board router

* test: add unit tests for board router and get current user permissions method

* fix: format issues

* fix: deepsource issue
This commit is contained in:
Meier Lukas
2024-05-04 18:34:41 +02:00
committed by GitHub
parent ca49a01352
commit b1e065f1da
42 changed files with 2375 additions and 423 deletions

View File

@@ -22,11 +22,18 @@ CREATE TABLE `app` (
CONSTRAINT `app_id` PRIMARY KEY(`id`)
);
--> statement-breakpoint
CREATE TABLE `boardPermission` (
CREATE TABLE `boardGroupPermission` (
`board_id` text NOT NULL,
`group_id` text NOT NULL,
`permission` text NOT NULL,
CONSTRAINT `boardGroupPermission_board_id_group_id_permission_pk` PRIMARY KEY(`board_id`,`group_id`,`permission`)
);
--> statement-breakpoint
CREATE TABLE `boardUserPermission` (
`board_id` text NOT NULL,
`user_id` text NOT NULL,
`permission` text NOT NULL,
CONSTRAINT `boardPermission_board_id_user_id_permission_pk` PRIMARY KEY(`board_id`,`user_id`,`permission`)
CONSTRAINT `boardUserPermission_board_id_user_id_permission_pk` PRIMARY KEY(`board_id`,`user_id`,`permission`)
);
--> statement-breakpoint
CREATE TABLE `board` (
@@ -152,8 +159,10 @@ CREATE INDEX `integration_secret__updated_at_idx` ON `integrationSecret` (`updat
CREATE INDEX `integration__kind_idx` ON `integration` (`kind`);--> statement-breakpoint
CREATE INDEX `user_id_idx` ON `session` (`userId`);--> statement-breakpoint
ALTER TABLE `account` ADD CONSTRAINT `account_userId_user_id_fk` FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `boardPermission` ADD CONSTRAINT `boardPermission_board_id_board_id_fk` FOREIGN KEY (`board_id`) REFERENCES `board`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `boardPermission` ADD CONSTRAINT `boardPermission_user_id_user_id_fk` FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `boardGroupPermission` ADD CONSTRAINT `boardGroupPermission_board_id_board_id_fk` FOREIGN KEY (`board_id`) REFERENCES `board`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `boardGroupPermission` ADD CONSTRAINT `boardGroupPermission_group_id_group_id_fk` FOREIGN KEY (`group_id`) REFERENCES `group`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `boardUserPermission` ADD CONSTRAINT `boardUserPermission_board_id_board_id_fk` FOREIGN KEY (`board_id`) REFERENCES `board`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `boardUserPermission` ADD CONSTRAINT `boardUserPermission_user_id_user_id_fk` FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `board` ADD CONSTRAINT `board_creator_id_user_id_fk` FOREIGN KEY (`creator_id`) REFERENCES `user`(`id`) ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `groupMember` ADD CONSTRAINT `groupMember_groupId_group_id_fk` FOREIGN KEY (`groupId`) REFERENCES `group`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE `groupMember` ADD CONSTRAINT `groupMember_userId_user_id_fk` FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON DELETE cascade ON UPDATE no action;--> statement-breakpoint

View File

@@ -1,7 +1,7 @@
{
"version": "5",
"dialect": "mysql",
"id": "d0a05e9e-107f-4bed-ac54-a4a41369f0da",
"id": "47dc6887-a308-480d-8125-183412fe7fa7",
"prevId": "00000000-0000-0000-0000-000000000000",
"tables": {
"account": {
@@ -160,8 +160,62 @@
},
"uniqueConstraints": {}
},
"boardPermission": {
"name": "boardPermission",
"boardGroupPermission": {
"name": "boardGroupPermission",
"columns": {
"board_id": {
"name": "board_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"group_id": {
"name": "group_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"permission": {
"name": "permission",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {
"boardGroupPermission_board_id_board_id_fk": {
"name": "boardGroupPermission_board_id_board_id_fk",
"tableFrom": "boardGroupPermission",
"tableTo": "board",
"columnsFrom": ["board_id"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onUpdate": "no action"
},
"boardGroupPermission_group_id_group_id_fk": {
"name": "boardGroupPermission_group_id_group_id_fk",
"tableFrom": "boardGroupPermission",
"tableTo": "group",
"columnsFrom": ["group_id"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"boardGroupPermission_board_id_group_id_permission_pk": {
"name": "boardGroupPermission_board_id_group_id_permission_pk",
"columns": ["board_id", "group_id", "permission"]
}
},
"uniqueConstraints": {}
},
"boardUserPermission": {
"name": "boardUserPermission",
"columns": {
"board_id": {
"name": "board_id",
@@ -187,18 +241,18 @@
},
"indexes": {},
"foreignKeys": {
"boardPermission_board_id_board_id_fk": {
"name": "boardPermission_board_id_board_id_fk",
"tableFrom": "boardPermission",
"boardUserPermission_board_id_board_id_fk": {
"name": "boardUserPermission_board_id_board_id_fk",
"tableFrom": "boardUserPermission",
"tableTo": "board",
"columnsFrom": ["board_id"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onUpdate": "no action"
},
"boardPermission_user_id_user_id_fk": {
"name": "boardPermission_user_id_user_id_fk",
"tableFrom": "boardPermission",
"boardUserPermission_user_id_user_id_fk": {
"name": "boardUserPermission_user_id_user_id_fk",
"tableFrom": "boardUserPermission",
"tableTo": "user",
"columnsFrom": ["user_id"],
"columnsTo": ["id"],
@@ -207,8 +261,8 @@
}
},
"compositePrimaryKeys": {
"boardPermission_board_id_user_id_permission_pk": {
"name": "boardPermission_board_id_user_id_permission_pk",
"boardUserPermission_board_id_user_id_permission_pk": {
"name": "boardUserPermission_board_id_user_id_permission_pk",
"columns": ["board_id", "user_id", "permission"]
}
},

View File

@@ -5,8 +5,8 @@
{
"idx": 0,
"version": "5",
"when": 1714414260766,
"tag": "0000_chubby_darkhawk",
"when": 1714817536714,
"tag": "0000_hot_mandrill",
"breakpoints": true
}
]

View File

@@ -22,7 +22,16 @@ CREATE TABLE `app` (
`href` text
);
--> statement-breakpoint
CREATE TABLE `boardPermission` (
CREATE TABLE `boardGroupPermission` (
`board_id` text NOT NULL,
`group_id` text NOT NULL,
`permission` text NOT NULL,
PRIMARY KEY(`board_id`, `group_id`, `permission`),
FOREIGN KEY (`board_id`) REFERENCES `board`(`id`) ON UPDATE no action ON DELETE cascade,
FOREIGN KEY (`group_id`) REFERENCES `group`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE TABLE `boardUserPermission` (
`board_id` text NOT NULL,
`user_id` text NOT NULL,
`permission` text NOT NULL,

View File

@@ -1,7 +1,7 @@
{
"version": "5",
"dialect": "sqlite",
"id": "e3ff4a97-d357-4a64-989b-78668b36c82d",
"id": "116fcd87-09c7-4c7c-b590-0ed5681ffdc5",
"prevId": "00000000-0000-0000-0000-000000000000",
"tables": {
"account": {
@@ -155,8 +155,62 @@
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"boardPermission": {
"name": "boardPermission",
"boardGroupPermission": {
"name": "boardGroupPermission",
"columns": {
"board_id": {
"name": "board_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"group_id": {
"name": "group_id",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"permission": {
"name": "permission",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {
"boardGroupPermission_board_id_board_id_fk": {
"name": "boardGroupPermission_board_id_board_id_fk",
"tableFrom": "boardGroupPermission",
"tableTo": "board",
"columnsFrom": ["board_id"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onUpdate": "no action"
},
"boardGroupPermission_group_id_group_id_fk": {
"name": "boardGroupPermission_group_id_group_id_fk",
"tableFrom": "boardGroupPermission",
"tableTo": "group",
"columnsFrom": ["group_id"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"boardGroupPermission_board_id_group_id_permission_pk": {
"columns": ["board_id", "group_id", "permission"],
"name": "boardGroupPermission_board_id_group_id_permission_pk"
}
},
"uniqueConstraints": {}
},
"boardUserPermission": {
"name": "boardUserPermission",
"columns": {
"board_id": {
"name": "board_id",
@@ -182,18 +236,18 @@
},
"indexes": {},
"foreignKeys": {
"boardPermission_board_id_board_id_fk": {
"name": "boardPermission_board_id_board_id_fk",
"tableFrom": "boardPermission",
"boardUserPermission_board_id_board_id_fk": {
"name": "boardUserPermission_board_id_board_id_fk",
"tableFrom": "boardUserPermission",
"tableTo": "board",
"columnsFrom": ["board_id"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onUpdate": "no action"
},
"boardPermission_user_id_user_id_fk": {
"name": "boardPermission_user_id_user_id_fk",
"tableFrom": "boardPermission",
"boardUserPermission_user_id_user_id_fk": {
"name": "boardUserPermission_user_id_user_id_fk",
"tableFrom": "boardUserPermission",
"tableTo": "user",
"columnsFrom": ["user_id"],
"columnsTo": ["id"],
@@ -202,9 +256,9 @@
}
},
"compositePrimaryKeys": {
"boardPermission_board_id_user_id_permission_pk": {
"boardUserPermission_board_id_user_id_permission_pk": {
"columns": ["board_id", "permission", "user_id"],
"name": "boardPermission_board_id_user_id_permission_pk"
"name": "boardUserPermission_board_id_user_id_permission_pk"
}
},
"uniqueConstraints": {}

View File

@@ -5,8 +5,8 @@
{
"idx": 0,
"version": "5",
"when": 1714414359385,
"tag": "0000_abnormal_kree",
"when": 1714817544524,
"tag": "0000_premium_forgotten_one",
"breakpoints": true
}
]

View File

@@ -20,8 +20,8 @@
"migration:sqlite:generate": "drizzle-kit generate:sqlite --config ./sqlite.config.ts",
"migration:run": "tsx ./migrate.ts",
"migration:mysql:generate": "drizzle-kit generate:mysql --config ./mysql.config.ts",
"push": "drizzle-kit push:sqlite",
"studio": "drizzle-kit studio",
"push": "drizzle-kit push:sqlite --config ./sqlite.config.ts",
"studio": "drizzle-kit studio --config ./sqlite.config.ts",
"typecheck": "tsc --noEmit"
},
"dependencies": {

View File

@@ -201,8 +201,8 @@ export const boards = mysqlTable("board", {
columnCount: int("column_count").default(10).notNull(),
});
export const boardPermissions = mysqlTable(
"boardPermission",
export const boardUserPermissions = mysqlTable(
"boardUserPermission",
{
boardId: text("board_id")
.notNull()
@@ -219,6 +219,24 @@ export const boardPermissions = mysqlTable(
}),
);
export const boardGroupPermissions = mysqlTable(
"boardGroupPermission",
{
boardId: text("board_id")
.notNull()
.references(() => boards.id, { onDelete: "cascade" }),
groupId: text("group_id")
.notNull()
.references(() => groups.id, { onDelete: "cascade" }),
permission: text("permission").$type<BoardPermission>().notNull(),
},
(table) => ({
compoundKey: primaryKey({
columns: [table.boardId, table.groupId, table.permission],
}),
}),
);
export const sections = mysqlTable("section", {
id: varchar("id", { length: 256 }).notNull().primaryKey(),
boardId: varchar("board_id", { length: 256 })
@@ -277,7 +295,7 @@ export const accountRelations = relations(accounts, ({ one }) => ({
export const userRelations = relations(users, ({ many }) => ({
accounts: many(accounts),
boards: many(boards),
boardPermissions: many(boardPermissions),
boardPermissions: many(boardUserPermissions),
groups: many(groupMembers),
ownedGroups: many(groups),
invites: many(invites),
@@ -310,6 +328,7 @@ export const groupMemberRelations = relations(groupMembers, ({ one }) => ({
export const groupRelations = relations(groups, ({ one, many }) => ({
permissions: many(groupPermissions),
boardPermissions: many(boardGroupPermissions),
members: many(groupMembers),
owner: one(users, {
fields: [groups.ownerId],
@@ -327,15 +346,29 @@ export const groupPermissionRelations = relations(
}),
);
export const boardPermissionRelations = relations(
boardPermissions,
export const boardUserPermissionRelations = relations(
boardUserPermissions,
({ one }) => ({
user: one(users, {
fields: [boardPermissions.userId],
fields: [boardUserPermissions.userId],
references: [users.id],
}),
board: one(boards, {
fields: [boardPermissions.boardId],
fields: [boardUserPermissions.boardId],
references: [boards.id],
}),
}),
);
export const boardGroupPermissionRelations = relations(
boardGroupPermissions,
({ one }) => ({
group: one(groups, {
fields: [boardGroupPermissions.groupId],
references: [groups.id],
}),
board: one(boards, {
fields: [boardGroupPermissions.boardId],
references: [boards.id],
}),
}),
@@ -362,7 +395,8 @@ export const boardRelations = relations(boards, ({ many, one }) => ({
fields: [boards.creatorId],
references: [users.id],
}),
permissions: many(boardPermissions),
userPermissions: many(boardUserPermissions),
groupPermissions: many(boardGroupPermissions),
}));
export const sectionRelations = relations(sections, ({ many, one }) => ({

View File

@@ -198,8 +198,8 @@ export const boards = sqliteTable("board", {
columnCount: int("column_count").default(10).notNull(),
});
export const boardPermissions = sqliteTable(
"boardPermission",
export const boardUserPermissions = sqliteTable(
"boardUserPermission",
{
boardId: text("board_id")
.notNull()
@@ -216,6 +216,24 @@ export const boardPermissions = sqliteTable(
}),
);
export const boardGroupPermissions = sqliteTable(
"boardGroupPermission",
{
boardId: text("board_id")
.notNull()
.references(() => boards.id, { onDelete: "cascade" }),
groupId: text("group_id")
.notNull()
.references(() => groups.id, { onDelete: "cascade" }),
permission: text("permission").$type<BoardPermission>().notNull(),
},
(table) => ({
compoundKey: primaryKey({
columns: [table.boardId, table.groupId, table.permission],
}),
}),
);
export const sections = sqliteTable("section", {
id: text("id").notNull().primaryKey(),
boardId: text("board_id")
@@ -274,7 +292,7 @@ export const accountRelations = relations(accounts, ({ one }) => ({
export const userRelations = relations(users, ({ many }) => ({
accounts: many(accounts),
boards: many(boards),
boardPermissions: many(boardPermissions),
boardPermissions: many(boardUserPermissions),
groups: many(groupMembers),
ownedGroups: many(groups),
invites: many(invites),
@@ -307,6 +325,7 @@ export const groupMemberRelations = relations(groupMembers, ({ one }) => ({
export const groupRelations = relations(groups, ({ one, many }) => ({
permissions: many(groupPermissions),
boardPermissions: many(boardGroupPermissions),
members: many(groupMembers),
owner: one(users, {
fields: [groups.ownerId],
@@ -324,15 +343,29 @@ export const groupPermissionRelations = relations(
}),
);
export const boardPermissionRelations = relations(
boardPermissions,
export const boardUserPermissionRelations = relations(
boardUserPermissions,
({ one }) => ({
user: one(users, {
fields: [boardPermissions.userId],
fields: [boardUserPermissions.userId],
references: [users.id],
}),
board: one(boards, {
fields: [boardPermissions.boardId],
fields: [boardUserPermissions.boardId],
references: [boards.id],
}),
}),
);
export const boardGroupPermissionRelations = relations(
boardGroupPermissions,
({ one }) => ({
group: one(groups, {
fields: [boardGroupPermissions.groupId],
references: [groups.id],
}),
board: one(boards, {
fields: [boardGroupPermissions.boardId],
references: [boards.id],
}),
}),
@@ -359,7 +392,8 @@ export const boardRelations = relations(boards, ({ many, one }) => ({
fields: [boards.creatorId],
references: [users.id],
}),
permissions: many(boardPermissions),
userPermissions: many(boardUserPermissions),
groupPermissions: many(boardGroupPermissions),
}));
export const sectionRelations = relations(sections, ({ many, one }) => ({