From 0c25fe38647dabd9bdb514362b000ec725b996c2 Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 17 Apr 2025 17:22:53 +0100 Subject: [PATCH] chore: move policies to drizzle --- packages/db/drizzle.config.ts | 2 +- .../db/migrations/0008_mature_ravenous.sql | 347 +++ .../db/migrations/meta/0008_snapshot.json | 2004 +++++++++++++++++ packages/db/migrations/meta/_journal.json | 7 + packages/db/package.json | 2 +- packages/db/seed.sql | 598 ----- packages/db/src/schema.ts | 486 ---- packages/db/src/schema/boards.ts | 127 ++ packages/db/src/schema/cards.ts | 504 +++++ packages/db/src/schema/feedback.ts | 30 + packages/db/src/schema/imports.ts | 59 + packages/db/src/schema/index.ts | 8 + packages/db/src/schema/labels.ts | 108 + packages/db/src/schema/lists.ts | 115 + packages/db/src/schema/users.ts | 75 + packages/db/src/schema/workspaces.ts | 168 ++ 16 files changed, 3554 insertions(+), 1086 deletions(-) create mode 100644 packages/db/migrations/0008_mature_ravenous.sql create mode 100644 packages/db/migrations/meta/0008_snapshot.json delete mode 100644 packages/db/src/schema.ts create mode 100644 packages/db/src/schema/boards.ts create mode 100644 packages/db/src/schema/cards.ts create mode 100644 packages/db/src/schema/feedback.ts create mode 100644 packages/db/src/schema/imports.ts create mode 100644 packages/db/src/schema/index.ts create mode 100644 packages/db/src/schema/labels.ts create mode 100644 packages/db/src/schema/lists.ts create mode 100644 packages/db/src/schema/users.ts create mode 100644 packages/db/src/schema/workspaces.ts diff --git a/packages/db/drizzle.config.ts b/packages/db/drizzle.config.ts index 3aca5ed7..916dd339 100644 --- a/packages/db/drizzle.config.ts +++ b/packages/db/drizzle.config.ts @@ -1,7 +1,7 @@ import { type Config } from "drizzle-kit"; export default { - schema: "./src/schema.ts", + schema: "./src/schema", out: "./migrations", dialect: "postgresql", dbCredentials: { diff --git a/packages/db/migrations/0008_mature_ravenous.sql b/packages/db/migrations/0008_mature_ravenous.sql new file mode 100644 index 00000000..faa2dc95 --- /dev/null +++ b/packages/db/migrations/0008_mature_ravenous.sql @@ -0,0 +1,347 @@ +ALTER TABLE "board" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint +ALTER TABLE "card_activity" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint +ALTER TABLE "_card_workspace_members" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint +ALTER TABLE "card" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint +ALTER TABLE "_card_labels" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint +ALTER TABLE "card_comments" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint +ALTER TABLE "feedback" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint +ALTER TABLE "import" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint +ALTER TABLE "label" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint +ALTER TABLE "list" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint +ALTER TABLE "user" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint +ALTER TABLE "workspace_members" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint +ALTER TABLE "workspace" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint +CREATE POLICY "Allow access to boards in user's workspace or public boards" ON "board" AS PERMISSIVE FOR SELECT TO "anon", "authenticated" USING ( + "workspaceId" IN ( + SELECT "workspaceId" + FROM workspace_members + WHERE "userId" = auth.uid() + ) + OR visibility = 'public' + );--> statement-breakpoint +CREATE POLICY "Allow inserting boards in user's workspace" ON "board" AS PERMISSIVE FOR INSERT TO "authenticated" WITH CHECK ( + "workspaceId" IN ( + SELECT "workspaceId" + FROM workspace_members + WHERE "userId" = auth.uid() + ) + );--> statement-breakpoint +CREATE POLICY "Allow updating boards in user's workspace" ON "board" AS PERMISSIVE FOR UPDATE TO "authenticated" USING ( + "workspaceId" IN ( + SELECT "workspaceId" + FROM workspace_members + WHERE "userId" = auth.uid() + ) + );--> statement-breakpoint +CREATE POLICY "Allow deleting boards in user's workspace" ON "board" AS PERMISSIVE FOR DELETE TO "authenticated" USING ( + "workspaceId" IN ( + SELECT "workspaceId" + FROM workspace_members + WHERE "userId" = auth.uid() + ) + );--> statement-breakpoint +CREATE POLICY "Allow access to card activity in user's workspace or public boards" ON "card_activity" AS PERMISSIVE FOR SELECT TO "anon", "authenticated" USING ( + "cardId" IN ( + SELECT c.id + FROM card c + JOIN list l ON c."listId" = l.id + JOIN board b ON l."boardId" = b.id + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + OR b.visibility = 'public' + ) + );--> statement-breakpoint +CREATE POLICY "Allow inserting card activity in user's workspace" ON "card_activity" AS PERMISSIVE FOR INSERT TO "authenticated" WITH CHECK ( + "cardId" IN ( + SELECT c.id + FROM card c + JOIN list l ON c."listId" = l.id + JOIN board b ON l."boardId" = b.id + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + ) + );--> statement-breakpoint +CREATE POLICY "Allow access to card workspace members in user's workspace" ON "_card_workspace_members" AS PERMISSIVE FOR ALL TO "authenticated" USING ( + "cardId" IN ( + SELECT c.id + FROM card c + JOIN list l ON c."listId" = l.id + JOIN board b ON l."boardId" = b.id + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + ) + AND + "workspaceMemberId" IN ( + SELECT wm.id + FROM workspace_members wm + WHERE wm."workspaceId" IN ( + SELECT "workspaceId" + FROM workspace_members + WHERE "userId" = auth.uid() + ) + ) + );--> statement-breakpoint +CREATE POLICY "Allow access to cards in user's workspace or public boards" ON "card" AS PERMISSIVE FOR SELECT TO "anon", "authenticated" USING ( + "listId" IN ( + SELECT l.id + FROM list l + JOIN board b ON l."boardId" = b.id + LEFT JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + OR b.visibility = 'public' + ) + );--> statement-breakpoint +CREATE POLICY "Allow inserting cards in user's workspace" ON "card" AS PERMISSIVE FOR INSERT TO "authenticated" WITH CHECK ( + "listId" IN ( + SELECT l.id + FROM list l + JOIN board b ON l."boardId" = b.id + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + ) + );--> statement-breakpoint +CREATE POLICY "Allow updating cards in user's workspace" ON "card" AS PERMISSIVE FOR UPDATE TO "authenticated" USING ( + "listId" IN ( + SELECT l.id + FROM list l + JOIN board b ON l."boardId" = b.id + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + ) + );--> statement-breakpoint +CREATE POLICY "Allow deleting cards in user's workspace" ON "card" AS PERMISSIVE FOR DELETE TO "authenticated" USING ( + "listId" IN ( + SELECT l.id + FROM list l + JOIN board b ON l."boardId" = b.id + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + ) + );--> statement-breakpoint +CREATE POLICY "Allow access to card labels in user's workspace or public boards" ON "_card_labels" AS PERMISSIVE FOR SELECT TO "anon", "authenticated" USING ( + "cardId" IN ( + SELECT c.id + FROM card c + JOIN list l ON c."listId" = l.id + JOIN board b ON l."boardId" = b.id + LEFT JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" AND wm."userId" = auth.uid() + WHERE wm."userId" = auth.uid() + OR b.visibility = 'public' + ) + AND + "labelId" IN ( + SELECT l.id + FROM label l + JOIN board b ON l."boardId" = b.id + LEFT JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" AND wm."userId" = auth.uid() + WHERE wm."userId" = auth.uid() + OR b.visibility = 'public' + ) + );--> statement-breakpoint +CREATE POLICY "Allow inserting card labels in user's workspace" ON "_card_labels" AS PERMISSIVE FOR INSERT TO "authenticated" WITH CHECK ( + "cardId" IN ( + SELECT c.id + FROM card c + JOIN list l ON c."listId" = l.id + JOIN board b ON l."boardId" = b.id + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + ) + AND + "labelId" IN ( + SELECT l.id + FROM label l + JOIN board b ON l."boardId" = b.id + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + ) + );--> statement-breakpoint +CREATE POLICY "Allow updating card labels in user's workspace" ON "_card_labels" AS PERMISSIVE FOR UPDATE TO "authenticated" USING ( + "cardId" IN ( + SELECT c.id + FROM card c + JOIN list l ON c."listId" = l.id + JOIN board b ON l."boardId" = b.id + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + ) + AND + "labelId" IN ( + SELECT l.id + FROM label l + JOIN board b ON l."boardId" = b.id + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + ) + );--> statement-breakpoint +CREATE POLICY "Allow deleting card labels in user's workspace" ON "_card_labels" AS PERMISSIVE FOR DELETE TO "authenticated" USING ( + "cardId" IN ( + SELECT c.id + FROM card c + JOIN list l ON c."listId" = l.id + JOIN board b ON l."boardId" = b.id + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + ) + AND + "labelId" IN ( + SELECT l.id + FROM label l + JOIN board b ON l."boardId" = b.id + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + ) + );--> statement-breakpoint +CREATE POLICY "Allow access to card comments in user's workspace or public boards" ON "card_comments" AS PERMISSIVE FOR SELECT TO "anon", "authenticated" USING ( + "cardId" IN ( + SELECT c.id + FROM card c + JOIN list l ON c."listId" = l.id + JOIN board b ON l."boardId" = b.id + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + OR b.visibility = 'public' + ) + );--> statement-breakpoint +CREATE POLICY "Allow inserting comments on cards in user's workspace" ON "card_comments" AS PERMISSIVE FOR INSERT TO "authenticated" WITH CHECK ( + "cardId" IN ( + SELECT c.id + FROM card c + JOIN list l ON c."listId" = l.id + JOIN board b ON l."boardId" = b.id + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + ) + );--> statement-breakpoint +CREATE POLICY "Allow updating own comments" ON "card_comments" AS PERMISSIVE FOR UPDATE TO "authenticated" USING ( + "createdBy" = auth.uid() + );--> statement-breakpoint +CREATE POLICY "Allow deleting own comments" ON "card_comments" AS PERMISSIVE FOR DELETE TO "authenticated" USING ( + "createdBy" = auth.uid() + );--> statement-breakpoint +CREATE POLICY "Allow access to user's own imports" ON "import" AS PERMISSIVE FOR ALL TO "authenticated" USING ( + "createdBy" = auth.uid() + );--> statement-breakpoint +CREATE POLICY "Allow access to labels in user's workspace or public boards" ON "label" AS PERMISSIVE FOR SELECT TO "anon", "authenticated" USING ( + "boardId" IN ( + SELECT b.id + FROM board b + LEFT JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + OR b.visibility = 'public' + ) + );--> statement-breakpoint +CREATE POLICY "Allow inserting labels in user's workspace" ON "label" AS PERMISSIVE FOR INSERT TO "authenticated" WITH CHECK ( + "boardId" IN ( + SELECT b.id + FROM board b + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + ) + );--> statement-breakpoint +CREATE POLICY "Allow updating labels in user's workspace" ON "label" AS PERMISSIVE FOR UPDATE TO "authenticated" USING ( + "boardId" IN ( + SELECT b.id + FROM board b + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + ) + );--> statement-breakpoint +CREATE POLICY "Allow deleting labels in user's workspace" ON "label" AS PERMISSIVE FOR DELETE TO "authenticated" USING ( + "boardId" IN ( + SELECT b.id + FROM board b + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + ) + );--> statement-breakpoint +CREATE POLICY "Allow access to lists in user's workspace or public boards" ON "list" AS PERMISSIVE FOR SELECT TO "anon", "authenticated" USING ( + "boardId" IN ( + SELECT b.id + FROM board b + LEFT JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + OR b.visibility = 'public' + ) + );--> statement-breakpoint +CREATE POLICY "Allow inserting lists in user's workspace" ON "list" AS PERMISSIVE FOR INSERT TO "authenticated" WITH CHECK ( + "boardId" IN ( + SELECT b.id + FROM board b + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + ) + );--> statement-breakpoint +CREATE POLICY "Allow updating lists in user's workspace" ON "list" AS PERMISSIVE FOR UPDATE TO "authenticated" USING ( + "boardId" IN ( + SELECT b.id + FROM board b + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + ) + );--> statement-breakpoint +CREATE POLICY "Allow deleting lists in user's workspace" ON "list" AS PERMISSIVE FOR DELETE TO "authenticated" USING ( + "boardId" IN ( + SELECT b.id + FROM board b + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + ) + );--> statement-breakpoint +CREATE POLICY "Allow viewing members in user's workspace" ON "user" AS PERMISSIVE FOR SELECT TO "authenticated" USING ( + id IN ( + SELECT wm."userId" + FROM workspace_members wm + WHERE wm."workspaceId" IN ( + SELECT "workspaceId" + FROM workspace_members + WHERE "userId" = auth.uid() + ) + ) + );--> statement-breakpoint +CREATE POLICY "Allow members to view workspace membership" ON "workspace_members" AS PERMISSIVE FOR SELECT TO "authenticated" USING ( + "userId" = auth.uid() OR + is_workspace_member(auth.uid(), "workspaceId") + );--> statement-breakpoint +CREATE POLICY "Allow admins to add workspace members" ON "workspace_members" AS PERMISSIVE FOR INSERT TO "authenticated" WITH CHECK ( + is_workspace_admin(auth.uid(), "workspaceId") + );--> statement-breakpoint +CREATE POLICY "Allow admins to update workspace members" ON "workspace_members" AS PERMISSIVE FOR UPDATE TO "authenticated" USING ( + is_workspace_admin(auth.uid(), "workspaceId") + );--> statement-breakpoint +CREATE POLICY "Allow admins to remove workspace members" ON "workspace_members" AS PERMISSIVE FOR DELETE TO "authenticated" USING ( + is_workspace_admin(auth.uid(), "workspaceId") + );--> statement-breakpoint +CREATE POLICY "Allow viewing user's workspaces" ON "workspace" AS PERMISSIVE FOR SELECT TO "anon", "authenticated" USING ( + CASE + WHEN auth.uid() IS NULL THEN + EXISTS ( + SELECT 1 + FROM board + WHERE "workspaceId" = workspace.id + AND visibility = 'public' + ) + ELSE + id IN ( + SELECT "workspaceId" + FROM workspace_members + WHERE "userId" = auth.uid() + ) + OR "createdBy" = auth.uid() + END + );--> statement-breakpoint +CREATE POLICY "Allow updating user's workspaces" ON "workspace" AS PERMISSIVE FOR UPDATE TO "authenticated" USING ( + id IN ( + SELECT "workspaceId" + FROM workspace_members + WHERE "userId" = auth.uid() + ) + );--> statement-breakpoint +CREATE POLICY "Allow deleting user's workspaces" ON "workspace" AS PERMISSIVE FOR DELETE TO "authenticated" USING ( + id IN ( + SELECT "workspaceId" + FROM workspace_members + WHERE "userId" = auth.uid() + ) + );--> statement-breakpoint +CREATE POLICY "Allow authenticated users to create workspaces" ON "workspace" AS PERMISSIVE FOR INSERT TO "authenticated" WITH CHECK (true); \ No newline at end of file diff --git a/packages/db/migrations/meta/0008_snapshot.json b/packages/db/migrations/meta/0008_snapshot.json new file mode 100644 index 00000000..89663c8a --- /dev/null +++ b/packages/db/migrations/meta/0008_snapshot.json @@ -0,0 +1,2004 @@ +{ + "id": "164a9145-d0f6-4651-b25c-ecc713054951", + "prevId": "cd98372e-e781-4864-8813-eee66f5e5568", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.board": { + "name": "board", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slug": { + "name": "slug", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "createdBy": { + "name": "createdBy", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deletedAt": { + "name": "deletedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deletedBy": { + "name": "deletedBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "importId": { + "name": "importId", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "workspaceId": { + "name": "workspaceId", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "visibility": { + "name": "visibility", + "type": "board_visibility", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'private'" + } + }, + "indexes": { + "board_visibility_idx": { + "name": "board_visibility_idx", + "columns": [ + { + "expression": "visibility", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "unique_slug_per_workspace": { + "name": "unique_slug_per_workspace", + "columns": [ + { + "expression": "workspaceId", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"board\".\"deletedAt\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "board_createdBy_user_id_fk": { + "name": "board_createdBy_user_id_fk", + "tableFrom": "board", + "tableTo": "user", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "board_deletedBy_user_id_fk": { + "name": "board_deletedBy_user_id_fk", + "tableFrom": "board", + "tableTo": "user", + "columnsFrom": [ + "deletedBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "board_importId_import_id_fk": { + "name": "board_importId_import_id_fk", + "tableFrom": "board", + "tableTo": "import", + "columnsFrom": [ + "importId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "board_workspaceId_workspace_id_fk": { + "name": "board_workspaceId_workspace_id_fk", + "tableFrom": "board", + "tableTo": "workspace", + "columnsFrom": [ + "workspaceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "board_publicId_unique": { + "name": "board_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + } + }, + "policies": { + "Allow access to boards in user's workspace or public boards": { + "name": "Allow access to boards in user's workspace or public boards", + "as": "PERMISSIVE", + "for": "SELECT", + "to": [ + "anon", + "authenticated" + ], + "using": "\n \"workspaceId\" IN (\n SELECT \"workspaceId\"\n FROM workspace_members\n WHERE \"userId\" = auth.uid()\n )\n OR visibility = 'public'\n " + }, + "Allow inserting boards in user's workspace": { + "name": "Allow inserting boards in user's workspace", + "as": "PERMISSIVE", + "for": "INSERT", + "to": [ + "authenticated" + ], + "withCheck": "\n \"workspaceId\" IN (\n SELECT \"workspaceId\"\n FROM workspace_members\n WHERE \"userId\" = auth.uid()\n )\n " + }, + "Allow updating boards in user's workspace": { + "name": "Allow updating boards in user's workspace", + "as": "PERMISSIVE", + "for": "UPDATE", + "to": [ + "authenticated" + ], + "using": "\n \"workspaceId\" IN (\n SELECT \"workspaceId\"\n FROM workspace_members\n WHERE \"userId\" = auth.uid()\n )\n " + }, + "Allow deleting boards in user's workspace": { + "name": "Allow deleting boards in user's workspace", + "as": "PERMISSIVE", + "for": "DELETE", + "to": [ + "authenticated" + ], + "using": "\n \"workspaceId\" IN (\n SELECT \"workspaceId\"\n FROM workspace_members\n WHERE \"userId\" = auth.uid()\n )\n " + } + }, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.card_activity": { + "name": "card_activity", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "card_activity_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "cardId": { + "name": "cardId", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "fromIndex": { + "name": "fromIndex", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "toIndex": { + "name": "toIndex", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "fromListId": { + "name": "fromListId", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "toListId": { + "name": "toListId", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "labelId": { + "name": "labelId", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "workspaceMemberId": { + "name": "workspaceMemberId", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "fromTitle": { + "name": "fromTitle", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "toTitle": { + "name": "toTitle", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "fromDescription": { + "name": "fromDescription", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "toDescription": { + "name": "toDescription", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "createdBy": { + "name": "createdBy", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "commentId": { + "name": "commentId", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "fromComment": { + "name": "fromComment", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "toComment": { + "name": "toComment", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "card_activity_cardId_card_id_fk": { + "name": "card_activity_cardId_card_id_fk", + "tableFrom": "card_activity", + "tableTo": "card", + "columnsFrom": [ + "cardId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "card_activity_fromListId_list_id_fk": { + "name": "card_activity_fromListId_list_id_fk", + "tableFrom": "card_activity", + "tableTo": "list", + "columnsFrom": [ + "fromListId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "card_activity_toListId_list_id_fk": { + "name": "card_activity_toListId_list_id_fk", + "tableFrom": "card_activity", + "tableTo": "list", + "columnsFrom": [ + "toListId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "card_activity_labelId_label_id_fk": { + "name": "card_activity_labelId_label_id_fk", + "tableFrom": "card_activity", + "tableTo": "label", + "columnsFrom": [ + "labelId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "card_activity_workspaceMemberId_workspace_members_id_fk": { + "name": "card_activity_workspaceMemberId_workspace_members_id_fk", + "tableFrom": "card_activity", + "tableTo": "workspace_members", + "columnsFrom": [ + "workspaceMemberId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "card_activity_createdBy_user_id_fk": { + "name": "card_activity_createdBy_user_id_fk", + "tableFrom": "card_activity", + "tableTo": "user", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "card_activity_commentId_card_comments_id_fk": { + "name": "card_activity_commentId_card_comments_id_fk", + "tableFrom": "card_activity", + "tableTo": "card_comments", + "columnsFrom": [ + "commentId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "card_activity_publicId_unique": { + "name": "card_activity_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + } + }, + "policies": { + "Allow access to card activity in user's workspace or public boards": { + "name": "Allow access to card activity in user's workspace or public boards", + "as": "PERMISSIVE", + "for": "SELECT", + "to": [ + "anon", + "authenticated" + ], + "using": "\n \"cardId\" IN (\n SELECT c.id\n FROM card c\n JOIN list l ON c.\"listId\" = l.id\n JOIN board b ON l.\"boardId\" = b.id\n JOIN workspace_members wm ON b.\"workspaceId\" = wm.\"workspaceId\"\n WHERE wm.\"userId\" = auth.uid()\n OR b.visibility = 'public'\n )\n " + }, + "Allow inserting card activity in user's workspace": { + "name": "Allow inserting card activity in user's workspace", + "as": "PERMISSIVE", + "for": "INSERT", + "to": [ + "authenticated" + ], + "withCheck": "\n \"cardId\" IN (\n SELECT c.id\n FROM card c\n JOIN list l ON c.\"listId\" = l.id\n JOIN board b ON l.\"boardId\" = b.id\n JOIN workspace_members wm ON b.\"workspaceId\" = wm.\"workspaceId\"\n WHERE wm.\"userId\" = auth.uid()\n )\n " + } + }, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public._card_workspace_members": { + "name": "_card_workspace_members", + "schema": "", + "columns": { + "cardId": { + "name": "cardId", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "workspaceMemberId": { + "name": "workspaceMemberId", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "_card_workspace_members_cardId_card_id_fk": { + "name": "_card_workspace_members_cardId_card_id_fk", + "tableFrom": "_card_workspace_members", + "tableTo": "card", + "columnsFrom": [ + "cardId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "_card_workspace_members_workspaceMemberId_workspace_members_id_fk": { + "name": "_card_workspace_members_workspaceMemberId_workspace_members_id_fk", + "tableFrom": "_card_workspace_members", + "tableTo": "workspace_members", + "columnsFrom": [ + "workspaceMemberId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "_card_workspace_members_cardId_workspaceMemberId_pk": { + "name": "_card_workspace_members_cardId_workspaceMemberId_pk", + "columns": [ + "cardId", + "workspaceMemberId" + ] + } + }, + "uniqueConstraints": {}, + "policies": { + "Allow access to card workspace members in user's workspace": { + "name": "Allow access to card workspace members in user's workspace", + "as": "PERMISSIVE", + "for": "ALL", + "to": [ + "authenticated" + ], + "using": "\n \"cardId\" IN (\n SELECT c.id\n FROM card c\n JOIN list l ON c.\"listId\" = l.id\n JOIN board b ON l.\"boardId\" = b.id\n JOIN workspace_members wm ON b.\"workspaceId\" = wm.\"workspaceId\"\n WHERE wm.\"userId\" = auth.uid()\n )\n AND\n \"workspaceMemberId\" IN (\n SELECT wm.id\n FROM workspace_members wm\n WHERE wm.\"workspaceId\" IN (\n SELECT \"workspaceId\"\n FROM workspace_members\n WHERE \"userId\" = auth.uid()\n )\n )\n " + } + }, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.card": { + "name": "card", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "index": { + "name": "index", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdBy": { + "name": "createdBy", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deletedAt": { + "name": "deletedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deletedBy": { + "name": "deletedBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "listId": { + "name": "listId", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "importId": { + "name": "importId", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "card_createdBy_user_id_fk": { + "name": "card_createdBy_user_id_fk", + "tableFrom": "card", + "tableTo": "user", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "card_deletedBy_user_id_fk": { + "name": "card_deletedBy_user_id_fk", + "tableFrom": "card", + "tableTo": "user", + "columnsFrom": [ + "deletedBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "card_listId_list_id_fk": { + "name": "card_listId_list_id_fk", + "tableFrom": "card", + "tableTo": "list", + "columnsFrom": [ + "listId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "card_importId_import_id_fk": { + "name": "card_importId_import_id_fk", + "tableFrom": "card", + "tableTo": "import", + "columnsFrom": [ + "importId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "card_publicId_unique": { + "name": "card_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + } + }, + "policies": { + "Allow access to cards in user's workspace or public boards": { + "name": "Allow access to cards in user's workspace or public boards", + "as": "PERMISSIVE", + "for": "SELECT", + "to": [ + "anon", + "authenticated" + ], + "using": "\n \"listId\" IN (\n SELECT l.id\n FROM list l\n JOIN board b ON l.\"boardId\" = b.id\n LEFT JOIN workspace_members wm ON b.\"workspaceId\" = wm.\"workspaceId\"\n WHERE wm.\"userId\" = auth.uid()\n OR b.visibility = 'public'\n )\n " + }, + "Allow inserting cards in user's workspace": { + "name": "Allow inserting cards in user's workspace", + "as": "PERMISSIVE", + "for": "INSERT", + "to": [ + "authenticated" + ], + "withCheck": "\n \"listId\" IN (\n SELECT l.id\n FROM list l\n JOIN board b ON l.\"boardId\" = b.id\n JOIN workspace_members wm ON b.\"workspaceId\" = wm.\"workspaceId\"\n WHERE wm.\"userId\" = auth.uid()\n )\n " + }, + "Allow updating cards in user's workspace": { + "name": "Allow updating cards in user's workspace", + "as": "PERMISSIVE", + "for": "UPDATE", + "to": [ + "authenticated" + ], + "using": "\n \"listId\" IN (\n SELECT l.id\n FROM list l\n JOIN board b ON l.\"boardId\" = b.id\n JOIN workspace_members wm ON b.\"workspaceId\" = wm.\"workspaceId\"\n WHERE wm.\"userId\" = auth.uid()\n )\n " + }, + "Allow deleting cards in user's workspace": { + "name": "Allow deleting cards in user's workspace", + "as": "PERMISSIVE", + "for": "DELETE", + "to": [ + "authenticated" + ], + "using": "\n \"listId\" IN (\n SELECT l.id\n FROM list l\n JOIN board b ON l.\"boardId\" = b.id\n JOIN workspace_members wm ON b.\"workspaceId\" = wm.\"workspaceId\"\n WHERE wm.\"userId\" = auth.uid()\n )\n " + } + }, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public._card_labels": { + "name": "_card_labels", + "schema": "", + "columns": { + "cardId": { + "name": "cardId", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "labelId": { + "name": "labelId", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "_card_labels_cardId_card_id_fk": { + "name": "_card_labels_cardId_card_id_fk", + "tableFrom": "_card_labels", + "tableTo": "card", + "columnsFrom": [ + "cardId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "_card_labels_labelId_label_id_fk": { + "name": "_card_labels_labelId_label_id_fk", + "tableFrom": "_card_labels", + "tableTo": "label", + "columnsFrom": [ + "labelId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "_card_labels_cardId_labelId_pk": { + "name": "_card_labels_cardId_labelId_pk", + "columns": [ + "cardId", + "labelId" + ] + } + }, + "uniqueConstraints": {}, + "policies": { + "Allow access to card labels in user's workspace or public boards": { + "name": "Allow access to card labels in user's workspace or public boards", + "as": "PERMISSIVE", + "for": "SELECT", + "to": [ + "anon", + "authenticated" + ], + "using": "\n \"cardId\" IN (\n SELECT c.id\n FROM card c\n JOIN list l ON c.\"listId\" = l.id\n JOIN board b ON l.\"boardId\" = b.id\n LEFT JOIN workspace_members wm ON b.\"workspaceId\" = wm.\"workspaceId\" AND wm.\"userId\" = auth.uid()\n WHERE wm.\"userId\" = auth.uid()\n OR b.visibility = 'public'\n )\n AND\n \"labelId\" IN (\n SELECT l.id\n FROM label l\n JOIN board b ON l.\"boardId\" = b.id\n LEFT JOIN workspace_members wm ON b.\"workspaceId\" = wm.\"workspaceId\" AND wm.\"userId\" = auth.uid()\n WHERE wm.\"userId\" = auth.uid()\n OR b.visibility = 'public'\n )\n " + }, + "Allow inserting card labels in user's workspace": { + "name": "Allow inserting card labels in user's workspace", + "as": "PERMISSIVE", + "for": "INSERT", + "to": [ + "authenticated" + ], + "withCheck": "\n \"cardId\" IN (\n SELECT c.id\n FROM card c\n JOIN list l ON c.\"listId\" = l.id\n JOIN board b ON l.\"boardId\" = b.id\n JOIN workspace_members wm ON b.\"workspaceId\" = wm.\"workspaceId\"\n WHERE wm.\"userId\" = auth.uid()\n )\n AND\n \"labelId\" IN (\n SELECT l.id\n FROM label l\n JOIN board b ON l.\"boardId\" = b.id\n JOIN workspace_members wm ON b.\"workspaceId\" = wm.\"workspaceId\"\n WHERE wm.\"userId\" = auth.uid()\n )\n " + }, + "Allow updating card labels in user's workspace": { + "name": "Allow updating card labels in user's workspace", + "as": "PERMISSIVE", + "for": "UPDATE", + "to": [ + "authenticated" + ], + "using": "\n \"cardId\" IN (\n SELECT c.id\n FROM card c\n JOIN list l ON c.\"listId\" = l.id\n JOIN board b ON l.\"boardId\" = b.id\n JOIN workspace_members wm ON b.\"workspaceId\" = wm.\"workspaceId\"\n WHERE wm.\"userId\" = auth.uid()\n )\n AND\n \"labelId\" IN (\n SELECT l.id\n FROM label l\n JOIN board b ON l.\"boardId\" = b.id\n JOIN workspace_members wm ON b.\"workspaceId\" = wm.\"workspaceId\"\n WHERE wm.\"userId\" = auth.uid()\n )\n " + }, + "Allow deleting card labels in user's workspace": { + "name": "Allow deleting card labels in user's workspace", + "as": "PERMISSIVE", + "for": "DELETE", + "to": [ + "authenticated" + ], + "using": "\n \"cardId\" IN (\n SELECT c.id\n FROM card c\n JOIN list l ON c.\"listId\" = l.id\n JOIN board b ON l.\"boardId\" = b.id\n JOIN workspace_members wm ON b.\"workspaceId\" = wm.\"workspaceId\"\n WHERE wm.\"userId\" = auth.uid()\n )\n AND\n \"labelId\" IN (\n SELECT l.id\n FROM label l\n JOIN board b ON l.\"boardId\" = b.id\n JOIN workspace_members wm ON b.\"workspaceId\" = wm.\"workspaceId\"\n WHERE wm.\"userId\" = auth.uid()\n )\n " + } + }, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.card_comments": { + "name": "card_comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true + }, + "comment": { + "name": "comment", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cardId": { + "name": "cardId", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "createdBy": { + "name": "createdBy", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deletedAt": { + "name": "deletedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deletedBy": { + "name": "deletedBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "card_comments_cardId_card_id_fk": { + "name": "card_comments_cardId_card_id_fk", + "tableFrom": "card_comments", + "tableTo": "card", + "columnsFrom": [ + "cardId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "card_comments_createdBy_user_id_fk": { + "name": "card_comments_createdBy_user_id_fk", + "tableFrom": "card_comments", + "tableTo": "user", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "card_comments_deletedBy_user_id_fk": { + "name": "card_comments_deletedBy_user_id_fk", + "tableFrom": "card_comments", + "tableTo": "user", + "columnsFrom": [ + "deletedBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "card_comments_publicId_unique": { + "name": "card_comments_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + } + }, + "policies": { + "Allow access to card comments in user's workspace or public boards": { + "name": "Allow access to card comments in user's workspace or public boards", + "as": "PERMISSIVE", + "for": "SELECT", + "to": [ + "anon", + "authenticated" + ], + "using": "\n \"cardId\" IN (\n SELECT c.id\n FROM card c\n JOIN list l ON c.\"listId\" = l.id\n JOIN board b ON l.\"boardId\" = b.id\n JOIN workspace_members wm ON b.\"workspaceId\" = wm.\"workspaceId\"\n WHERE wm.\"userId\" = auth.uid()\n OR b.visibility = 'public'\n )\n " + }, + "Allow inserting comments on cards in user's workspace": { + "name": "Allow inserting comments on cards in user's workspace", + "as": "PERMISSIVE", + "for": "INSERT", + "to": [ + "authenticated" + ], + "withCheck": "\n \"cardId\" IN (\n SELECT c.id\n FROM card c\n JOIN list l ON c.\"listId\" = l.id\n JOIN board b ON l.\"boardId\" = b.id\n JOIN workspace_members wm ON b.\"workspaceId\" = wm.\"workspaceId\"\n WHERE wm.\"userId\" = auth.uid()\n )\n " + }, + "Allow updating own comments": { + "name": "Allow updating own comments", + "as": "PERMISSIVE", + "for": "UPDATE", + "to": [ + "authenticated" + ], + "using": "\n \"createdBy\" = auth.uid()\n " + }, + "Allow deleting own comments": { + "name": "Allow deleting own comments", + "as": "PERMISSIVE", + "for": "DELETE", + "to": [ + "authenticated" + ], + "using": "\n \"createdBy\" = auth.uid()\n " + } + }, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.feedback": { + "name": "feedback", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "feedback": { + "name": "feedback", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "createdBy": { + "name": "createdBy", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reviewed": { + "name": "reviewed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "feedback_createdBy_user_id_fk": { + "name": "feedback_createdBy_user_id_fk", + "tableFrom": "feedback", + "tableTo": "user", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.import": { + "name": "import", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "createdBy": { + "name": "createdBy", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "import_createdBy_user_id_fk": { + "name": "import_createdBy_user_id_fk", + "tableFrom": "import", + "tableTo": "user", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "import_publicId_unique": { + "name": "import_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + } + }, + "policies": { + "Allow access to user's own imports": { + "name": "Allow access to user's own imports", + "as": "PERMISSIVE", + "for": "ALL", + "to": [ + "authenticated" + ], + "using": "\n \"createdBy\" = auth.uid()\n " + } + }, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.label": { + "name": "label", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "colourCode": { + "name": "colourCode", + "type": "varchar(12)", + "primaryKey": false, + "notNull": false + }, + "createdBy": { + "name": "createdBy", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "boardId": { + "name": "boardId", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "importId": { + "name": "importId", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "label_createdBy_user_id_fk": { + "name": "label_createdBy_user_id_fk", + "tableFrom": "label", + "tableTo": "user", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "label_boardId_board_id_fk": { + "name": "label_boardId_board_id_fk", + "tableFrom": "label", + "tableTo": "board", + "columnsFrom": [ + "boardId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "label_importId_import_id_fk": { + "name": "label_importId_import_id_fk", + "tableFrom": "label", + "tableTo": "import", + "columnsFrom": [ + "importId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "label_publicId_unique": { + "name": "label_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + } + }, + "policies": { + "Allow access to labels in user's workspace or public boards": { + "name": "Allow access to labels in user's workspace or public boards", + "as": "PERMISSIVE", + "for": "SELECT", + "to": [ + "anon", + "authenticated" + ], + "using": "\n \"boardId\" IN (\n SELECT b.id\n FROM board b\n LEFT JOIN workspace_members wm ON b.\"workspaceId\" = wm.\"workspaceId\"\n WHERE wm.\"userId\" = auth.uid()\n OR b.visibility = 'public'\n )\n " + }, + "Allow inserting labels in user's workspace": { + "name": "Allow inserting labels in user's workspace", + "as": "PERMISSIVE", + "for": "INSERT", + "to": [ + "authenticated" + ], + "withCheck": "\n \"boardId\" IN (\n SELECT b.id\n FROM board b\n JOIN workspace_members wm ON b.\"workspaceId\" = wm.\"workspaceId\"\n WHERE wm.\"userId\" = auth.uid()\n )\n " + }, + "Allow updating labels in user's workspace": { + "name": "Allow updating labels in user's workspace", + "as": "PERMISSIVE", + "for": "UPDATE", + "to": [ + "authenticated" + ], + "using": "\n \"boardId\" IN (\n SELECT b.id\n FROM board b\n JOIN workspace_members wm ON b.\"workspaceId\" = wm.\"workspaceId\"\n WHERE wm.\"userId\" = auth.uid()\n )\n " + }, + "Allow deleting labels in user's workspace": { + "name": "Allow deleting labels in user's workspace", + "as": "PERMISSIVE", + "for": "DELETE", + "to": [ + "authenticated" + ], + "using": "\n \"boardId\" IN (\n SELECT b.id\n FROM board b\n JOIN workspace_members wm ON b.\"workspaceId\" = wm.\"workspaceId\"\n WHERE wm.\"userId\" = auth.uid()\n )\n " + } + }, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.list": { + "name": "list", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "index": { + "name": "index", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "createdBy": { + "name": "createdBy", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deletedAt": { + "name": "deletedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deletedBy": { + "name": "deletedBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "boardId": { + "name": "boardId", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "importId": { + "name": "importId", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "list_createdBy_user_id_fk": { + "name": "list_createdBy_user_id_fk", + "tableFrom": "list", + "tableTo": "user", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "list_deletedBy_user_id_fk": { + "name": "list_deletedBy_user_id_fk", + "tableFrom": "list", + "tableTo": "user", + "columnsFrom": [ + "deletedBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "list_boardId_board_id_fk": { + "name": "list_boardId_board_id_fk", + "tableFrom": "list", + "tableTo": "board", + "columnsFrom": [ + "boardId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "list_importId_import_id_fk": { + "name": "list_importId_import_id_fk", + "tableFrom": "list", + "tableTo": "import", + "columnsFrom": [ + "importId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "list_publicId_unique": { + "name": "list_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + } + }, + "policies": { + "Allow access to lists in user's workspace or public boards": { + "name": "Allow access to lists in user's workspace or public boards", + "as": "PERMISSIVE", + "for": "SELECT", + "to": [ + "anon", + "authenticated" + ], + "using": "\n \"boardId\" IN (\n SELECT b.id\n FROM board b\n LEFT JOIN workspace_members wm ON b.\"workspaceId\" = wm.\"workspaceId\"\n WHERE wm.\"userId\" = auth.uid()\n OR b.visibility = 'public'\n )\n " + }, + "Allow inserting lists in user's workspace": { + "name": "Allow inserting lists in user's workspace", + "as": "PERMISSIVE", + "for": "INSERT", + "to": [ + "authenticated" + ], + "withCheck": "\n \"boardId\" IN (\n SELECT b.id\n FROM board b\n JOIN workspace_members wm ON b.\"workspaceId\" = wm.\"workspaceId\"\n WHERE wm.\"userId\" = auth.uid()\n )\n " + }, + "Allow updating lists in user's workspace": { + "name": "Allow updating lists in user's workspace", + "as": "PERMISSIVE", + "for": "UPDATE", + "to": [ + "authenticated" + ], + "using": "\n \"boardId\" IN (\n SELECT b.id\n FROM board b\n JOIN workspace_members wm ON b.\"workspaceId\" = wm.\"workspaceId\"\n WHERE wm.\"userId\" = auth.uid()\n )\n " + }, + "Allow deleting lists in user's workspace": { + "name": "Allow deleting lists in user's workspace", + "as": "PERMISSIVE", + "for": "DELETE", + "to": [ + "authenticated" + ], + "using": "\n \"boardId\" IN (\n SELECT b.id\n FROM board b\n JOIN workspace_members wm ON b.\"workspaceId\" = wm.\"workspaceId\"\n WHERE wm.\"userId\" = auth.uid()\n )\n " + } + }, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "emailVerified": { + "name": "emailVerified", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "image": { + "name": "image", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "stripeCustomerId": { + "name": "stripeCustomerId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": { + "Allow viewing members in user's workspace": { + "name": "Allow viewing members in user's workspace", + "as": "PERMISSIVE", + "for": "SELECT", + "to": [ + "authenticated" + ], + "using": "\n id IN (\n SELECT wm.\"userId\"\n FROM workspace_members wm\n WHERE wm.\"workspaceId\" IN (\n SELECT \"workspaceId\"\n FROM workspace_members\n WHERE \"userId\" = auth.uid()\n )\n )\n " + } + }, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.workspace_slugs": { + "name": "workspace_slugs", + "schema": "", + "columns": { + "slug": { + "name": "slug", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "slug_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workspace_slugs_slug_unique": { + "name": "workspace_slugs_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_members": { + "name": "workspace_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true + }, + "userId": { + "name": "userId", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "workspaceId": { + "name": "workspaceId", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "createdBy": { + "name": "createdBy", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deletedAt": { + "name": "deletedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deletedBy": { + "name": "deletedBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "member_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'invited'" + } + }, + "indexes": {}, + "foreignKeys": { + "workspace_members_userId_user_id_fk": { + "name": "workspace_members_userId_user_id_fk", + "tableFrom": "workspace_members", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workspace_members_workspaceId_workspace_id_fk": { + "name": "workspace_members_workspaceId_workspace_id_fk", + "tableFrom": "workspace_members", + "tableTo": "workspace", + "columnsFrom": [ + "workspaceId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_members_deletedBy_user_id_fk": { + "name": "workspace_members_deletedBy_user_id_fk", + "tableFrom": "workspace_members", + "tableTo": "user", + "columnsFrom": [ + "deletedBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workspace_members_publicId_unique": { + "name": "workspace_members_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + } + }, + "policies": { + "Allow members to view workspace membership": { + "name": "Allow members to view workspace membership", + "as": "PERMISSIVE", + "for": "SELECT", + "to": [ + "authenticated" + ], + "using": "\n \"userId\" = auth.uid() OR\n is_workspace_member(auth.uid(), \"workspaceId\")\n " + }, + "Allow admins to add workspace members": { + "name": "Allow admins to add workspace members", + "as": "PERMISSIVE", + "for": "INSERT", + "to": [ + "authenticated" + ], + "withCheck": "\n is_workspace_admin(auth.uid(), \"workspaceId\")\n " + }, + "Allow admins to update workspace members": { + "name": "Allow admins to update workspace members", + "as": "PERMISSIVE", + "for": "UPDATE", + "to": [ + "authenticated" + ], + "using": "\n is_workspace_admin(auth.uid(), \"workspaceId\")\n " + }, + "Allow admins to remove workspace members": { + "name": "Allow admins to remove workspace members", + "as": "PERMISSIVE", + "for": "DELETE", + "to": [ + "authenticated" + ], + "using": "\n is_workspace_admin(auth.uid(), \"workspaceId\")\n " + } + }, + "checkConstraints": {}, + "isRLSEnabled": true + }, + "public.workspace": { + "name": "workspace", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "publicId": { + "name": "publicId", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slug": { + "name": "slug", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "plan": { + "name": "plan", + "type": "workspace_plan", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'free'" + }, + "createdBy": { + "name": "createdBy", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deletedAt": { + "name": "deletedAt", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deletedBy": { + "name": "deletedBy", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "workspace_createdBy_user_id_fk": { + "name": "workspace_createdBy_user_id_fk", + "tableFrom": "workspace", + "tableTo": "user", + "columnsFrom": [ + "createdBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workspace_deletedBy_user_id_fk": { + "name": "workspace_deletedBy_user_id_fk", + "tableFrom": "workspace", + "tableTo": "user", + "columnsFrom": [ + "deletedBy" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workspace_publicId_unique": { + "name": "workspace_publicId_unique", + "nullsNotDistinct": false, + "columns": [ + "publicId" + ] + }, + "workspace_slug_unique": { + "name": "workspace_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": { + "Allow viewing user's workspaces": { + "name": "Allow viewing user's workspaces", + "as": "PERMISSIVE", + "for": "SELECT", + "to": [ + "anon", + "authenticated" + ], + "using": "\n CASE \n WHEN auth.uid() IS NULL THEN\n EXISTS (\n SELECT 1 \n FROM board \n WHERE \"workspaceId\" = workspace.id \n AND visibility = 'public'\n )\n ELSE\n id IN (\n SELECT \"workspaceId\"\n FROM workspace_members\n WHERE \"userId\" = auth.uid()\n )\n OR \"createdBy\" = auth.uid()\n END\n " + }, + "Allow updating user's workspaces": { + "name": "Allow updating user's workspaces", + "as": "PERMISSIVE", + "for": "UPDATE", + "to": [ + "authenticated" + ], + "using": "\n id IN (\n SELECT \"workspaceId\"\n FROM workspace_members\n WHERE \"userId\" = auth.uid()\n )\n " + }, + "Allow deleting user's workspaces": { + "name": "Allow deleting user's workspaces", + "as": "PERMISSIVE", + "for": "DELETE", + "to": [ + "authenticated" + ], + "using": "\n id IN (\n SELECT \"workspaceId\"\n FROM workspace_members\n WHERE \"userId\" = auth.uid()\n )\n " + }, + "Allow authenticated users to create workspaces": { + "name": "Allow authenticated users to create workspaces", + "as": "PERMISSIVE", + "for": "INSERT", + "to": [ + "authenticated" + ], + "withCheck": "true" + } + }, + "checkConstraints": {}, + "isRLSEnabled": true + } + }, + "enums": { + "public.board_visibility": { + "name": "board_visibility", + "schema": "public", + "values": [ + "private", + "public" + ] + }, + "public.card_activity_type": { + "name": "card_activity_type", + "schema": "public", + "values": [ + "card.created", + "card.updated.title", + "card.updated.description", + "card.updated.index", + "card.updated.list", + "card.updated.label.added", + "card.updated.label.removed", + "card.updated.member.added", + "card.updated.member.removed", + "card.updated.comment.added", + "card.updated.comment.updated", + "card.updated.comment.deleted", + "card.archived" + ] + }, + "public.source": { + "name": "source", + "schema": "public", + "values": [ + "trello" + ] + }, + "public.status": { + "name": "status", + "schema": "public", + "values": [ + "started", + "success", + "failed" + ] + }, + "public.role": { + "name": "role", + "schema": "public", + "values": [ + "admin", + "member", + "guest" + ] + }, + "public.member_status": { + "name": "member_status", + "schema": "public", + "values": [ + "invited", + "active", + "removed" + ] + }, + "public.slug_type": { + "name": "slug_type", + "schema": "public", + "values": [ + "reserved", + "premium" + ] + }, + "public.workspace_plan": { + "name": "workspace_plan", + "schema": "public", + "values": [ + "free", + "pro", + "enterprise" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/db/migrations/meta/_journal.json b/packages/db/migrations/meta/_journal.json index 943e5371..204c9b55 100644 --- a/packages/db/migrations/meta/_journal.json +++ b/packages/db/migrations/meta/_journal.json @@ -57,6 +57,13 @@ "when": 1738168854269, "tag": "0007_redundant_silver_fox", "breakpoints": true + }, + { + "idx": 8, + "version": "7", + "when": 1744906695652, + "tag": "0008_mature_ravenous", + "breakpoints": true } ] } \ No newline at end of file diff --git a/packages/db/package.json b/packages/db/package.json index 0fd170cd..f886a1cd 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -14,7 +14,7 @@ }, "./schema": { "types": "./dist/schema.d.ts", - "default": "./src/schema.ts" + "default": "./src/schema/index.ts" }, "./types/*": { "types": "./dist/types/*.d.ts", diff --git a/packages/db/seed.sql b/packages/db/seed.sql index 6722182a..ce76ef7c 100644 --- a/packages/db/seed.sql +++ b/packages/db/seed.sql @@ -129,604 +129,6 @@ AS $$ ); $$; -alter table "_card_labels" enable row level security; -alter table "_card_workspace_members" enable row level security; -alter table "_card_activity" enable row level security; -alter table "_card_comments" enable row level security; -alter table "board" enable row level security; -alter table "card" enable row level security; -alter table "import" enable row level security; -alter table "label" enable row level security; -alter table "user" enable row level security; -alter table "list" enable row level security; -alter table "workspace" enable row level security; -alter table "workspace_members" enable row level security; -alter table "workspace_slugs" enable row level security; - - -/* BOARD */ -CREATE POLICY "Allow access to boards in user's workspace or public boards" -ON public.board -AS PERMISSIVE -FOR SELECT -TO anon, authenticated -USING ( - "workspaceId" IN ( - SELECT "workspaceId" - FROM workspace_members - WHERE "userId" = auth.uid() - ) - OR visibility = 'public' -); - -CREATE POLICY "Allow inserting boards in user's workspace" -ON public.board -AS PERMISSIVE -FOR INSERT -TO authenticated -WITH CHECK ( - "workspaceId" IN ( - SELECT "workspaceId" - FROM workspace_members - WHERE "userId" = auth.uid() - ) -); - -CREATE POLICY "Allow updating boards in user's workspace" -ON public.board -AS PERMISSIVE -FOR UPDATE -TO authenticated -USING ( - "workspaceId" IN ( - SELECT "workspaceId" - FROM workspace_members - WHERE "userId" = auth.uid() - ) -); - -CREATE POLICY "Allow deleting boards in user's workspace" -ON public.board -AS PERMISSIVE -FOR DELETE -TO authenticated -USING ( - "workspaceId" IN ( - SELECT "workspaceId" - FROM workspace_members - WHERE "userId" = auth.uid() - ) -); - -CREATE POLICY "Allow modifications to boards in user's workspace" -ON public.board -AS PERMISSIVE -FOR INSERT -TO authenticated -USING ( - "workspaceId" IN ( - SELECT "workspaceId" - FROM workspace_members - WHERE "userId" = auth.uid() - ) -); - -/* LIST */ -CREATE POLICY "Allow access to lists in user's workspace or public boards" -ON public.list -AS PERMISSIVE -FOR SELECT -TO anon, authenticated -USING ( - "boardId" IN ( - SELECT b.id - FROM board b - LEFT JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" - WHERE wm."userId" = auth.uid() - OR b.visibility = 'public' - ) -); - -CREATE POLICY "Allow inserting lists in user's workspace" -ON public.list -AS PERMISSIVE -FOR INSERT -TO authenticated -WITH CHECK ( - "boardId" IN ( - SELECT b.id - FROM board b - JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" - WHERE wm."userId" = auth.uid() - ) -); - -CREATE POLICY "Allow updating lists in user's workspace" -ON public.list -AS PERMISSIVE -FOR UPDATE -TO authenticated -USING ( - "boardId" IN ( - SELECT b.id - FROM board b - JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" - WHERE wm."userId" = auth.uid() - ) -); - -CREATE POLICY "Allow deleting lists in user's workspace" -ON public.list -AS PERMISSIVE -FOR DELETE -TO authenticated -USING ( - "boardId" IN ( - SELECT b.id - FROM board b - JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" - WHERE wm."userId" = auth.uid() - ) -); - -/* CARD */ -CREATE POLICY "Allow access to cards in user's workspace or public boards" -ON public.card -AS PERMISSIVE -FOR SELECT -TO anon, authenticated -USING ( - "listId" IN ( - SELECT l.id - FROM list l - JOIN board b ON l."boardId" = b.id - LEFT JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" - WHERE wm."userId" = auth.uid() - OR b.visibility = 'public' - ) -); - -CREATE POLICY "Allow inserting cards in user's workspace" -ON public.card -AS PERMISSIVE -FOR INSERT -TO authenticated -WITH CHECK ( - "listId" IN ( - SELECT l.id - FROM list l - JOIN board b ON l."boardId" = b.id - JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" - WHERE wm."userId" = auth.uid() - ) -); - -CREATE POLICY "Allow updating cards in user's workspace" -ON public.card -AS PERMISSIVE -FOR UPDATE -TO authenticated -USING ( - "listId" IN ( - SELECT l.id - FROM list l - JOIN board b ON l."boardId" = b.id - JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" - WHERE wm."userId" = auth.uid() - ) -); - -CREATE POLICY "Allow deleting cards in user's workspace" -ON public.card -AS PERMISSIVE -FOR DELETE -TO authenticated -USING ( - "listId" IN ( - SELECT l.id - FROM list l - JOIN board b ON l."boardId" = b.id - JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" - WHERE wm."userId" = auth.uid() - ) -); - -/* LABEL */ -CREATE POLICY "Allow access to labels in user's workspace or public boards" -ON public.label -AS PERMISSIVE -FOR SELECT -TO anon, authenticated -USING ( - "boardId" IN ( - SELECT b.id - FROM board b - LEFT JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" - WHERE wm."userId" = auth.uid() - OR b.visibility = 'public' - ) -); - -CREATE POLICY "Allow inserting labels in user's workspace" -ON public.label -AS PERMISSIVE -FOR INSERT -TO authenticated -WITH CHECK ( - "boardId" IN ( - SELECT b.id - FROM board b - JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" - WHERE wm."userId" = auth.uid() - ) -); - -CREATE POLICY "Allow updating labels in user's workspace" -ON public.label -AS PERMISSIVE -FOR UPDATE -TO authenticated -USING ( - "boardId" IN ( - SELECT b.id - FROM board b - JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" - WHERE wm."userId" = auth.uid() - ) -); - -CREATE POLICY "Allow deleting labels in user's workspace" -ON public.label -AS PERMISSIVE -FOR DELETE -TO authenticated -USING ( - "boardId" IN ( - SELECT b.id - FROM board b - JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" - WHERE wm."userId" = auth.uid() - ) -); - -/* CARD LABELS */ -CREATE POLICY "Allow access to card labels in user's workspace or public boards" -ON public._card_labels -AS PERMISSIVE -FOR SELECT -TO anon, authenticated -USING ( - "cardId" IN ( - SELECT c.id - FROM card c - JOIN list l ON c."listId" = l.id - JOIN board b ON l."boardId" = b.id - LEFT JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" AND wm."userId" = auth.uid() - WHERE wm."userId" = auth.uid() - OR b.visibility = 'public' - ) - AND - "labelId" IN ( - SELECT l.id - FROM label l - JOIN board b ON l."boardId" = b.id - LEFT JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" AND wm."userId" = auth.uid() - WHERE wm."userId" = auth.uid() - OR b.visibility = 'public' - ) -); - -CREATE POLICY "Allow inserting card labels in user's workspace" -ON public._card_labels -AS PERMISSIVE -FOR INSERT -TO authenticated -WITH CHECK ( - "cardId" IN ( - SELECT c.id - FROM card c - JOIN list l ON c."listId" = l.id - JOIN board b ON l."boardId" = b.id - JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" - WHERE wm."userId" = auth.uid() - ) - AND - "labelId" IN ( - SELECT l.id - FROM label l - JOIN board b ON l."boardId" = b.id - JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" - WHERE wm."userId" = auth.uid() - ) -); - -CREATE POLICY "Allow updating card labels in user's workspace" -ON public._card_labels -AS PERMISSIVE -FOR UPDATE -TO authenticated -USING ( - "cardId" IN ( - SELECT c.id - FROM card c - JOIN list l ON c."listId" = l.id - JOIN board b ON l."boardId" = b.id - JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" - WHERE wm."userId" = auth.uid() - ) - AND - "labelId" IN ( - SELECT l.id - FROM label l - JOIN board b ON l."boardId" = b.id - JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" - WHERE wm."userId" = auth.uid() - ) -); - -CREATE POLICY "Allow deleting card labels in user's workspace" -ON public._card_labels -AS PERMISSIVE -FOR DELETE -TO authenticated -USING ( - "cardId" IN ( - SELECT c.id - FROM card c - JOIN list l ON c."listId" = l.id - JOIN board b ON l."boardId" = b.id - JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" - WHERE wm."userId" = auth.uid() - ) - AND - "labelId" IN ( - SELECT l.id - FROM label l - JOIN board b ON l."boardId" = b.id - JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" - WHERE wm."userId" = auth.uid() - ) -); - -/* CARD ACTIVITY */ -CREATE POLICY "Allow access to card activity in user's workspace or public boards" -ON public.card_activity -AS PERMISSIVE -FOR SELECT -TO anon, authenticated -USING ( - "cardId" IN ( - SELECT c.id - FROM card c - JOIN list l ON c."listId" = l.id - JOIN board b ON l."boardId" = b.id - JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" - WHERE wm."userId" = auth.uid() - OR b.visibility = 'public' - ) -); - -CREATE POLICY "Allow inserting card activity in user's workspace" -ON public.card_activity -AS PERMISSIVE -FOR INSERT -TO authenticated -WITH CHECK ( - "cardId" IN ( - SELECT c.id - FROM card c - JOIN list l ON c."listId" = l.id - JOIN board b ON l."boardId" = b.id - JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" - WHERE wm."userId" = auth.uid() - ) -); - -/* CARD COMMENTS */ -CREATE POLICY "Allow access to card comments in user's workspace or public boards" -ON public.card_comments -AS PERMISSIVE -FOR SELECT -TO anon, authenticated -USING ( - "cardId" IN ( - SELECT c.id - FROM card c - JOIN list l ON c."listId" = l.id - JOIN board b ON l."boardId" = b.id - JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" - WHERE wm."userId" = auth.uid() - OR b.visibility = 'public' - ) -); - -CREATE POLICY "Allow inserting comments on cards in user's workspace" -ON public.card_comments -AS PERMISSIVE -FOR INSERT -TO authenticated -WITH CHECK ( - "cardId" IN ( - SELECT c.id - FROM card c - JOIN list l ON c."listId" = l.id - JOIN board b ON l."boardId" = b.id - JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" - WHERE wm."userId" = auth.uid() - ) -); - -CREATE POLICY "Allow updating own comments" -ON public.card_comments -AS PERMISSIVE -FOR UPDATE -TO authenticated -USING ( - "createdBy" = auth.uid() -); - -CREATE POLICY "Allow deleting own comments" -ON public.card_comments -AS PERMISSIVE -FOR DELETE -TO authenticated -USING ( - "createdBy" = auth.uid() -); - -/* CARD WORKSPACE MEMBERS */ -CREATE POLICY "Allow access to card workspace members in user's workspace" -ON public._card_workspace_members -AS PERMISSIVE -FOR ALL -TO authenticated -USING ( - "cardId" IN ( - SELECT c.id - FROM card c - JOIN list l ON c."listId" = l.id - JOIN board b ON l."boardId" = b.id - JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" - WHERE wm."userId" = auth.uid() - ) - AND - "workspaceMemberId" IN ( - SELECT wm.id - FROM workspace_members wm - WHERE wm."workspaceId" IN ( - SELECT "workspaceId" - FROM workspace_members - WHERE "userId" = auth.uid() - ) - ) -); - -/* USER */ -CREATE POLICY "Allow viewing members in user's workspace" -ON public.user -AS PERMISSIVE -FOR SELECT -TO authenticated -USING ( - id IN ( - SELECT wm."userId" - FROM workspace_members wm - WHERE wm."workspaceId" IN ( - SELECT "workspaceId" - FROM workspace_members - WHERE "userId" = auth.uid() - ) - ) -); - -/* WORKSPACE */ -CREATE POLICY "Allow viewing user's workspaces" -ON public.workspace -AS PERMISSIVE -FOR SELECT -TO anon, authenticated -USING ( - CASE - WHEN auth.uid() IS NULL THEN - -- For anonymous users, only allow access through public boards - EXISTS ( - SELECT 1 - FROM board - WHERE "workspaceId" = workspace.id - AND visibility = 'public' - ) - ELSE - -- For authenticated users, allow access to their workspaces - id IN ( - SELECT "workspaceId" - FROM workspace_members - WHERE "userId" = auth.uid() - ) - OR "createdBy" = auth.uid() - END -); - -CREATE POLICY "Allow updating user's workspaces" -ON public.workspace -AS PERMISSIVE -FOR UPDATE -TO authenticated -USING ( - id IN ( - SELECT "workspaceId" - FROM workspace_members - WHERE "userId" = auth.uid() - ) -); - -CREATE POLICY "Allow deleting user's workspaces" -ON public.workspace -AS PERMISSIVE -FOR DELETE -TO authenticated -USING ( - id IN ( - SELECT "workspaceId" - FROM workspace_members - WHERE "userId" = auth.uid() - ) -); - -CREATE POLICY "Allow authenticated users to create workspaces" -ON public.workspace -AS PERMISSIVE -FOR INSERT -TO authenticated -USING (true); - -/* WORKSPACE MEMBERS */ -CREATE POLICY "Allow members to view workspace membership" -ON public.workspace_members -AS PERMISSIVE -FOR SELECT -TO authenticated -USING ( - "userId" = auth.uid() OR - is_workspace_member(auth.uid(), "workspaceId") -); - -CREATE POLICY "Allow admins to add workspace members" -ON public.workspace_members -AS PERMISSIVE -FOR INSERT -TO authenticated -WITH CHECK ( - is_workspace_admin(auth.uid(), "workspaceId") -); - -CREATE POLICY "Allow admins to update workspace members" -ON public.workspace_members -AS PERMISSIVE -FOR UPDATE -TO authenticated -USING ( - is_workspace_admin(auth.uid(), "workspaceId") -); - -CREATE POLICY "Allow admins to remove workspace members" -ON public.workspace_members -AS PERMISSIVE -FOR DELETE -TO authenticated -USING ( - is_workspace_admin(auth.uid(), "workspaceId") -); - -/* IMPORT */ -CREATE POLICY "Allow access to user's own imports" -ON public.import -AS PERMISSIVE -FOR ALL -TO authenticated -USING ( - "createdBy" = auth.uid() -); - /* BUCKETS */ insert into storage.buckets (id, name, public) diff --git a/packages/db/src/schema.ts b/packages/db/src/schema.ts deleted file mode 100644 index a7bcaba5..00000000 --- a/packages/db/src/schema.ts +++ /dev/null @@ -1,486 +0,0 @@ -import { relations, sql } from "drizzle-orm"; -import { - bigint, - bigserial, - boolean, - index, - integer, - pgEnum, - pgTable, - primaryKey, - text, - timestamp, - uniqueIndex, - uuid, - varchar, -} from "drizzle-orm/pg-core"; - -export const importSourceEnum = pgEnum("source", ["trello"]); -export const importStatusEnum = pgEnum("status", [ - "started", - "success", - "failed", -]); -export const memberRoleEnum = pgEnum("role", ["admin", "member", "guest"]); -export const memberStatusEnum = pgEnum("member_status", [ - "invited", - "active", - "removed", -]); -export const activityTypeEnum = pgEnum("card_activity_type", [ - "card.created", - "card.updated.title", - "card.updated.description", - "card.updated.index", - "card.updated.list", - "card.updated.label.added", - "card.updated.label.removed", - "card.updated.member.added", - "card.updated.member.removed", - "card.updated.comment.added", - "card.updated.comment.updated", - "card.updated.comment.deleted", - "card.archived", -]); -export const slugTypeEnum = pgEnum("slug_type", ["reserved", "premium"]); -export const workspacePlanEnum = pgEnum("workspace_plan", [ - "free", - "pro", - "enterprise", -]); -export const boardVisibilityEnum = pgEnum("board_visibility", [ - "private", - "public", -]); - -export const boards = pgTable( - "board", - { - id: bigserial("id", { mode: "number" }).primaryKey(), - publicId: varchar("publicId", { length: 12 }).notNull().unique(), - name: varchar("name", { length: 255 }).notNull(), - description: text("description"), - slug: varchar("slug", { length: 255 }).notNull(), - createdBy: uuid("createdBy") - .notNull() - .references(() => users.id), - createdAt: timestamp("createdAt").defaultNow().notNull(), - updatedAt: timestamp("updatedAt"), - deletedAt: timestamp("deletedAt"), - deletedBy: uuid("deletedBy").references(() => users.id), - importId: bigint("importId", { mode: "number" }).references( - () => imports.id, - ), - workspaceId: bigint("workspaceId", { mode: "number" }) - .notNull() - .references(() => workspaces.id, { onDelete: "cascade" }), - visibility: boardVisibilityEnum("visibility").notNull().default("private"), - }, - (table) => ({ - visibilityIndex: index("board_visibility_idx").on(table.visibility), - uniqueSlugPerWorkspace: uniqueIndex("unique_slug_per_workspace") - .on(table.workspaceId, table.slug) - .where(sql`${table.deletedAt} IS NULL`), - }), -); - -export const boardsRelations = relations(boards, ({ one, many }) => ({ - createdBy: one(users, { - fields: [boards.createdBy], - references: [users.id], - }), - lists: many(lists), - labels: many(labels), - deletedBy: one(users, { - fields: [boards.deletedBy], - references: [users.id], - }), - import: one(imports, { - fields: [boards.importId], - references: [imports.id], - }), - workspace: one(workspaces, { - fields: [boards.workspaceId], - references: [workspaces.id], - }), -})); - -export const imports = pgTable("import", { - id: bigserial("id", { mode: "number" }).primaryKey(), - publicId: varchar("publicId", { length: 12 }).notNull().unique(), - source: importSourceEnum("source").notNull(), - status: importStatusEnum("status").notNull(), - createdBy: uuid("createdBy") - .notNull() - .references(() => users.id), - createdAt: timestamp("createdAt").defaultNow().notNull(), -}); - -export const importsRelations = relations(imports, ({ one, many }) => ({ - createdBy: one(users, { - fields: [imports.createdBy], - references: [users.id], - }), - boards: many(boards), - cards: many(cards), - lists: many(lists), - labels: many(labels), -})); - -export const labels = pgTable("label", { - id: bigserial("id", { mode: "number" }).primaryKey(), - publicId: varchar("publicId", { length: 12 }).notNull().unique(), - name: varchar("name", { length: 255 }).notNull(), - colourCode: varchar("colourCode", { length: 12 }), - createdBy: uuid("createdBy") - .notNull() - .references(() => users.id), - createdAt: timestamp("createdAt").defaultNow().notNull(), - updatedAt: timestamp("updatedAt"), - boardId: bigint("boardId", { mode: "number" }) - .notNull() - .references(() => boards.id, { onDelete: "cascade" }), - importId: bigint("importId", { mode: "number" }).references(() => imports.id), -}); - -export const labelsRelations = relations(labels, ({ one, many }) => ({ - createdBy: one(users, { - fields: [labels.createdBy], - references: [users.id], - }), - board: one(boards, { - fields: [labels.boardId], - references: [boards.id], - }), - cards: many(cardsToLabels), - import: one(imports, { - fields: [labels.importId], - references: [imports.id], - }), -})); - -export const cardsToLabels = pgTable( - "_card_labels", - { - cardId: bigint("cardId", { mode: "number" }) - .notNull() - .references(() => cards.id), - labelId: bigint("labelId", { mode: "number" }) - .notNull() - .references(() => labels.id, { onDelete: "cascade" }), - }, - (t) => ({ - pk: primaryKey(t.cardId, t.labelId), - }), -); - -export const cardToLabelsRelations = relations(cardsToLabels, ({ one }) => ({ - card: one(cards, { - fields: [cardsToLabels.cardId], - references: [cards.id], - }), - label: one(labels, { - fields: [cardsToLabels.labelId], - references: [labels.id], - }), -})); - -export const cardToWorkspaceMembers = pgTable( - "_card_workspace_members", - { - cardId: bigint("cardId", { mode: "number" }) - .notNull() - .references(() => cards.id), - workspaceMemberId: bigint("workspaceMemberId", { mode: "number" }) - .notNull() - .references(() => workspaceMembers.id, { onDelete: "cascade" }), - }, - (t) => ({ - pk: primaryKey(t.cardId, t.workspaceMemberId), - }), -); - -export const cardToWorkspaceMembersRelations = relations( - cardToWorkspaceMembers, - ({ one }) => ({ - card: one(cards, { - fields: [cardToWorkspaceMembers.cardId], - references: [cards.id], - }), - member: one(workspaceMembers, { - fields: [cardToWorkspaceMembers.workspaceMemberId], - references: [workspaceMembers.id], - }), - }), -); - -export const lists = pgTable("list", { - id: bigserial("id", { mode: "number" }).primaryKey(), - publicId: varchar("publicId", { length: 12 }).notNull().unique(), - name: varchar("name", { length: 255 }).notNull(), - index: integer("index").notNull(), - createdBy: uuid("createdBy") - .notNull() - .references(() => users.id), - createdAt: timestamp("createdAt").defaultNow().notNull(), - updatedAt: timestamp("updatedAt"), - deletedAt: timestamp("deletedAt"), - deletedBy: uuid("deletedBy").references(() => users.id), - boardId: bigint("boardId", { mode: "number" }) - .notNull() - .references(() => boards.id, { onDelete: "cascade" }), - importId: bigint("importId", { mode: "number" }).references(() => imports.id), -}); - -export const listsRelations = relations(lists, ({ one, many }) => ({ - createdBy: one(users, { - fields: [lists.createdBy], - references: [users.id], - }), - board: one(boards, { - fields: [lists.boardId], - references: [boards.id], - }), - cards: many(cards), - deletedBy: one(users, { - fields: [lists.deletedBy], - references: [users.id], - }), - import: one(imports, { - fields: [lists.importId], - references: [imports.id], - }), -})); - -export const cards = pgTable("card", { - id: bigserial("id", { mode: "number" }).primaryKey(), - publicId: varchar("publicId", { length: 12 }).notNull().unique(), - title: varchar("title", { length: 255 }).notNull(), - description: text("description"), - index: integer("index").notNull(), - createdBy: uuid("createdBy") - .notNull() - .references(() => users.id), - createdAt: timestamp("createdAt").defaultNow().notNull(), - updatedAt: timestamp("updatedAt"), - deletedAt: timestamp("deletedAt"), - deletedBy: uuid("deletedBy").references(() => users.id), - listId: bigint("listId", { mode: "number" }) - .notNull() - .references(() => lists.id, { onDelete: "cascade" }), - importId: bigint("importId", { mode: "number" }).references(() => imports.id), -}); - -export const cardsRelations = relations(cards, ({ one, many }) => ({ - createdBy: one(users, { - fields: [cards.createdBy], - references: [users.id], - }), - list: one(lists, { - fields: [cards.listId], - references: [lists.id], - }), - deletedBy: one(users, { - fields: [cards.deletedBy], - references: [users.id], - }), - labels: many(cardsToLabels), - members: many(cardToWorkspaceMembers), - import: one(imports, { - fields: [cards.importId], - references: [imports.id], - }), - comments: many(comments), -})); - -export const users = pgTable("user", { - id: uuid("id").notNull().primaryKey(), - name: varchar("name", { length: 255 }), - email: varchar("email", { length: 255 }).notNull().unique(), - emailVerified: timestamp("emailVerified", { mode: "date" }), - image: varchar("image", { length: 255 }), - stripeCustomerId: varchar("stripeCustomerId", { length: 255 }), -}); - -export const usersRelations = relations(users, ({ many }) => ({ - boards: many(boards), - cards: many(cards), - imports: many(imports), - lists: many(lists), - workspaces: many(workspaces), -})); - -export const workspaces = pgTable("workspace", { - id: bigserial("id", { mode: "number" }).primaryKey(), - publicId: varchar("publicId", { length: 12 }).notNull().unique(), - name: varchar("name", { length: 255 }).notNull(), - description: text("description"), - slug: varchar("slug", { length: 255 }).notNull().unique(), - plan: workspacePlanEnum("plan").notNull().default("free"), - createdBy: uuid("createdBy") - .notNull() - .references(() => users.id), - createdAt: timestamp("createdAt").defaultNow().notNull(), - updatedAt: timestamp("updatedAt"), - deletedAt: timestamp("deletedAt"), - deletedBy: uuid("deletedBy").references(() => users.id), -}); - -export const workspaceRelations = relations(workspaces, ({ one, many }) => ({ - user: one(users, { fields: [workspaces.createdBy], references: [users.id] }), - members: many(workspaceMembers), -})); - -export const workspaceMembers = pgTable("workspace_members", { - id: bigserial("id", { mode: "number" }).primaryKey(), - publicId: varchar("publicId", { length: 12 }).notNull().unique(), - userId: uuid("userId") - .notNull() - .references(() => users.id), - workspaceId: bigint("workspaceId", { mode: "number" }) - .notNull() - .references(() => workspaces.id, { onDelete: "cascade" }), - createdBy: uuid("createdBy").notNull(), - createdAt: timestamp("createdAt").defaultNow().notNull(), - updatedAt: timestamp("updatedAt"), - deletedAt: timestamp("deletedAt"), - deletedBy: uuid("deletedBy").references(() => users.id), - role: memberRoleEnum("role").notNull(), - status: memberStatusEnum("status").default("invited").notNull(), -}); - -export const usersToWorkspacesRelations = relations( - workspaceMembers, - ({ one }) => ({ - addedBy: one(users, { - fields: [workspaceMembers.createdBy], - references: [users.id], - }), - deletedBy: one(users, { - fields: [workspaceMembers.deletedBy], - references: [users.id], - }), - user: one(users, { - fields: [workspaceMembers.userId], - references: [users.id], - }), - workspace: one(workspaces, { - fields: [workspaceMembers.workspaceId], - references: [workspaces.id], - }), - }), -); - -export const cardActivities = pgTable("card_activity", { - id: bigserial("id", { mode: "number" }).primaryKey(), - publicId: varchar("publicId", { length: 12 }).notNull().unique(), - type: activityTypeEnum("type").notNull(), - cardId: bigint("cardId", { mode: "number" }) - .notNull() - .references(() => cards.id, { onDelete: "cascade" }), - fromIndex: integer("fromIndex"), - toIndex: integer("toIndex"), - fromListId: bigint("fromListId", { mode: "number" }).references( - () => lists.id, - ), - toListId: bigint("toListId", { mode: "number" }).references(() => lists.id), - labelId: bigint("labelId", { mode: "number" }).references(() => labels.id), - workspaceMemberId: bigint("workspaceMemberId", { mode: "number" }).references( - () => workspaceMembers.id, - ), - fromTitle: varchar("fromTitle", { length: 255 }), - toTitle: varchar("toTitle", { length: 255 }), - fromDescription: text("fromDescription"), - toDescription: text("toDescription"), - createdBy: uuid("createdBy") - .notNull() - .references(() => users.id), - createdAt: timestamp("createdAt").defaultNow().notNull(), - commentId: bigint("commentId", { mode: "number" }).references( - () => comments.id, - ), - fromComment: text("fromComment"), - toComment: text("toComment"), -}); - -export const cardActivitiesRelations = relations(cardActivities, ({ one }) => ({ - card: one(cards, { - fields: [cardActivities.cardId], - references: [cards.id], - }), - fromList: one(lists, { - fields: [cardActivities.fromListId], - references: [lists.id], - }), - toList: one(lists, { - fields: [cardActivities.toListId], - references: [lists.id], - }), - label: one(labels, { - fields: [cardActivities.labelId], - references: [labels.id], - }), - workspaceMember: one(workspaceMembers, { - fields: [cardActivities.workspaceMemberId], - references: [workspaceMembers.id], - }), - createdBy: one(users, { - fields: [cardActivities.createdBy], - references: [users.id], - }), -})); - -export const comments = pgTable("card_comments", { - id: bigserial("id", { mode: "number" }).primaryKey(), - publicId: varchar("publicId", { length: 12 }).notNull().unique(), - comment: text("comment").notNull(), - cardId: bigint("cardId", { mode: "number" }) - .notNull() - .references(() => cards.id, { onDelete: "cascade" }), - createdBy: uuid("createdBy") - .notNull() - .references(() => users.id), - createdAt: timestamp("createdAt").defaultNow().notNull(), - updatedAt: timestamp("updatedAt"), - deletedAt: timestamp("deletedAt"), - deletedBy: uuid("deletedBy").references(() => users.id), -}); - -export const commentsRelations = relations(comments, ({ one }) => ({ - card: one(cards, { - fields: [comments.cardId], - references: [cards.id], - }), - createdBy: one(users, { - fields: [comments.createdBy], - references: [users.id], - }), - deletedBy: one(users, { - fields: [comments.deletedBy], - references: [users.id], - }), -})); - -export const slugs = pgTable("workspace_slugs", { - slug: varchar("slug", { length: 255 }).notNull().unique(), - type: slugTypeEnum("type").notNull(), -}); - -export const feedback = pgTable("feedback", { - id: bigserial("id", { mode: "number" }).primaryKey(), - feedback: text("feedback").notNull(), - createdBy: uuid("createdBy") - .notNull() - .references(() => users.id), - createdAt: timestamp("createdAt").defaultNow().notNull(), - updatedAt: timestamp("updatedAt"), - url: text("url").notNull(), - reviewed: boolean("reviewed").default(false).notNull(), -}); - -export const feedbackRelations = relations(feedback, ({ one }) => ({ - createdBy: one(users, { - fields: [feedback.createdBy], - references: [users.id], - }), -})); diff --git a/packages/db/src/schema/boards.ts b/packages/db/src/schema/boards.ts new file mode 100644 index 00000000..82d383d7 --- /dev/null +++ b/packages/db/src/schema/boards.ts @@ -0,0 +1,127 @@ +import { relations, sql } from "drizzle-orm"; +import { + bigint, + bigserial, + index, + pgEnum, + pgPolicy, + pgTable, + text, + timestamp, + uniqueIndex, + uuid, + varchar, +} from "drizzle-orm/pg-core"; +import { anonRole, authenticatedRole } from "drizzle-orm/supabase"; + +import { imports } from "./imports"; +import { labels } from "./labels"; +import { lists } from "./lists"; +import { users } from "./users"; +import { workspaces } from "./workspaces"; + +export const boardVisibilityEnum = pgEnum("board_visibility", [ + "private", + "public", +]); + +export const boards = pgTable( + "board", + { + id: bigserial("id", { mode: "number" }).primaryKey(), + publicId: varchar("publicId", { length: 12 }).notNull().unique(), + name: varchar("name", { length: 255 }).notNull(), + description: text("description"), + slug: varchar("slug", { length: 255 }).notNull(), + createdBy: uuid("createdBy") + .notNull() + .references(() => users.id), + createdAt: timestamp("createdAt").defaultNow().notNull(), + updatedAt: timestamp("updatedAt"), + deletedAt: timestamp("deletedAt"), + deletedBy: uuid("deletedBy").references(() => users.id), + importId: bigint("importId", { mode: "number" }).references( + () => imports.id, + ), + workspaceId: bigint("workspaceId", { mode: "number" }) + .notNull() + .references(() => workspaces.id, { onDelete: "cascade" }), + visibility: boardVisibilityEnum("visibility").notNull().default("private"), + }, + (table) => [ + index("board_visibility_idx").on(table.visibility), + uniqueIndex("unique_slug_per_workspace") + .on(table.workspaceId, table.slug) + .where(sql`${table.deletedAt} IS NULL`), + pgPolicy("Allow access to boards in user's workspace or public boards", { + for: "select", + as: "permissive", + to: [authenticatedRole, anonRole], + using: sql` + "workspaceId" IN ( + SELECT "workspaceId" + FROM workspace_members + WHERE "userId" = auth.uid() + ) + OR visibility = 'public' + `, + }), + pgPolicy("Allow inserting boards in user's workspace", { + for: "insert", + as: "permissive", + to: [authenticatedRole], + withCheck: sql` + "workspaceId" IN ( + SELECT "workspaceId" + FROM workspace_members + WHERE "userId" = auth.uid() + ) + `, + }), + pgPolicy("Allow updating boards in user's workspace", { + for: "update", + as: "permissive", + to: [authenticatedRole], + using: sql` + "workspaceId" IN ( + SELECT "workspaceId" + FROM workspace_members + WHERE "userId" = auth.uid() + ) + `, + }), + pgPolicy("Allow deleting boards in user's workspace", { + for: "delete", + as: "permissive", + to: [authenticatedRole], + using: sql` + "workspaceId" IN ( + SELECT "workspaceId" + FROM workspace_members + WHERE "userId" = auth.uid() + ) + `, + }), + ], +).enableRLS(); + +export const boardsRelations = relations(boards, ({ one, many }) => ({ + createdBy: one(users, { + fields: [boards.createdBy], + references: [users.id], + }), + lists: many(lists), + labels: many(labels), + deletedBy: one(users, { + fields: [boards.deletedBy], + references: [users.id], + }), + import: one(imports, { + fields: [boards.importId], + references: [imports.id], + }), + workspace: one(workspaces, { + fields: [boards.workspaceId], + references: [workspaces.id], + }), +})); diff --git a/packages/db/src/schema/cards.ts b/packages/db/src/schema/cards.ts new file mode 100644 index 00000000..2de473b4 --- /dev/null +++ b/packages/db/src/schema/cards.ts @@ -0,0 +1,504 @@ +import { relations, sql } from "drizzle-orm"; +import { + bigint, + bigserial, + integer, + pgEnum, + pgPolicy, + pgTable, + primaryKey, + text, + timestamp, + uuid, + varchar, +} from "drizzle-orm/pg-core"; +import { anonRole, authenticatedRole } from "drizzle-orm/supabase"; + +import { imports } from "./imports"; +import { labels } from "./labels"; +import { lists } from "./lists"; +import { users } from "./users"; +import { workspaceMembers } from "./workspaces"; + +export const activityTypeEnum = pgEnum("card_activity_type", [ + "card.created", + "card.updated.title", + "card.updated.description", + "card.updated.index", + "card.updated.list", + "card.updated.label.added", + "card.updated.label.removed", + "card.updated.member.added", + "card.updated.member.removed", + "card.updated.comment.added", + "card.updated.comment.updated", + "card.updated.comment.deleted", + "card.archived", +]); + +export const cards = pgTable( + "card", + { + id: bigserial("id", { mode: "number" }).primaryKey(), + publicId: varchar("publicId", { length: 12 }).notNull().unique(), + title: varchar("title", { length: 255 }).notNull(), + description: text("description"), + index: integer("index").notNull(), + createdBy: uuid("createdBy") + .notNull() + .references(() => users.id), + createdAt: timestamp("createdAt").defaultNow().notNull(), + updatedAt: timestamp("updatedAt"), + deletedAt: timestamp("deletedAt"), + deletedBy: uuid("deletedBy").references(() => users.id), + listId: bigint("listId", { mode: "number" }) + .notNull() + .references(() => lists.id, { onDelete: "cascade" }), + importId: bigint("importId", { mode: "number" }).references( + () => imports.id, + ), + }, + () => [ + pgPolicy("Allow access to cards in user's workspace or public boards", { + for: "select", + as: "permissive", + to: [authenticatedRole, anonRole], + using: sql` + "listId" IN ( + SELECT l.id + FROM list l + JOIN board b ON l."boardId" = b.id + LEFT JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + OR b.visibility = 'public' + ) + `, + }), + pgPolicy("Allow inserting cards in user's workspace", { + for: "insert", + as: "permissive", + to: [authenticatedRole], + withCheck: sql` + "listId" IN ( + SELECT l.id + FROM list l + JOIN board b ON l."boardId" = b.id + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + ) + `, + }), + + pgPolicy("Allow updating cards in user's workspace", { + for: "update", + as: "permissive", + to: [authenticatedRole], + using: sql` + "listId" IN ( + SELECT l.id + FROM list l + JOIN board b ON l."boardId" = b.id + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + ) + `, + }), + pgPolicy("Allow deleting cards in user's workspace", { + for: "delete", + as: "permissive", + to: [authenticatedRole], + using: sql` + "listId" IN ( + SELECT l.id + FROM list l + JOIN board b ON l."boardId" = b.id + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + ) + `, + }), + ], +).enableRLS(); + +export const cardsRelations = relations(cards, ({ one, many }) => ({ + createdBy: one(users, { + fields: [cards.createdBy], + references: [users.id], + }), + list: one(lists, { + fields: [cards.listId], + references: [lists.id], + }), + deletedBy: one(users, { + fields: [cards.deletedBy], + references: [users.id], + }), + labels: many(cardsToLabels), + members: many(cardToWorkspaceMembers), + import: one(imports, { + fields: [cards.importId], + references: [imports.id], + }), + comments: many(comments), +})); + +export const cardActivities = pgTable( + "card_activity", + { + id: bigserial("id", { mode: "number" }).primaryKey(), + publicId: varchar("publicId", { length: 12 }).notNull().unique(), + type: activityTypeEnum("type").notNull(), + cardId: bigint("cardId", { mode: "number" }) + .notNull() + .references(() => cards.id, { onDelete: "cascade" }), + fromIndex: integer("fromIndex"), + toIndex: integer("toIndex"), + fromListId: bigint("fromListId", { mode: "number" }).references( + () => lists.id, + ), + toListId: bigint("toListId", { mode: "number" }).references(() => lists.id), + labelId: bigint("labelId", { mode: "number" }).references(() => labels.id), + workspaceMemberId: bigint("workspaceMemberId", { + mode: "number", + }).references(() => workspaceMembers.id), + fromTitle: varchar("fromTitle", { length: 255 }), + toTitle: varchar("toTitle", { length: 255 }), + fromDescription: text("fromDescription"), + toDescription: text("toDescription"), + createdBy: uuid("createdBy") + .notNull() + .references(() => users.id), + createdAt: timestamp("createdAt").defaultNow().notNull(), + commentId: bigint("commentId", { mode: "number" }).references( + () => comments.id, + ), + fromComment: text("fromComment"), + toComment: text("toComment"), + }, + () => [ + pgPolicy( + "Allow access to card activity in user's workspace or public boards", + { + for: "select", + as: "permissive", + to: [authenticatedRole, anonRole], + using: sql` + "cardId" IN ( + SELECT c.id + FROM card c + JOIN list l ON c."listId" = l.id + JOIN board b ON l."boardId" = b.id + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + OR b.visibility = 'public' + ) + `, + }, + ), + pgPolicy("Allow inserting card activity in user's workspace", { + for: "insert", + as: "permissive", + to: [authenticatedRole], + withCheck: sql` + "cardId" IN ( + SELECT c.id + FROM card c + JOIN list l ON c."listId" = l.id + JOIN board b ON l."boardId" = b.id + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + ) + `, + }), + ], +).enableRLS(); + +export const cardActivitiesRelations = relations(cardActivities, ({ one }) => ({ + card: one(cards, { + fields: [cardActivities.cardId], + references: [cards.id], + }), + fromList: one(lists, { + fields: [cardActivities.fromListId], + references: [lists.id], + }), + toList: one(lists, { + fields: [cardActivities.toListId], + references: [lists.id], + }), + label: one(labels, { + fields: [cardActivities.labelId], + references: [labels.id], + }), + workspaceMember: one(workspaceMembers, { + fields: [cardActivities.workspaceMemberId], + references: [workspaceMembers.id], + }), + createdBy: one(users, { + fields: [cardActivities.createdBy], + references: [users.id], + }), +})); + +export const cardsToLabels = pgTable( + "_card_labels", + { + cardId: bigint("cardId", { mode: "number" }) + .notNull() + .references(() => cards.id), + labelId: bigint("labelId", { mode: "number" }) + .notNull() + .references(() => labels.id, { onDelete: "cascade" }), + }, + (t) => [ + primaryKey({ columns: [t.cardId, t.labelId] }), + pgPolicy( + "Allow access to card labels in user's workspace or public boards", + { + for: "select", + as: "permissive", + to: [authenticatedRole, anonRole], + using: sql` + "cardId" IN ( + SELECT c.id + FROM card c + JOIN list l ON c."listId" = l.id + JOIN board b ON l."boardId" = b.id + LEFT JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" AND wm."userId" = auth.uid() + WHERE wm."userId" = auth.uid() + OR b.visibility = 'public' + ) + AND + "labelId" IN ( + SELECT l.id + FROM label l + JOIN board b ON l."boardId" = b.id + LEFT JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" AND wm."userId" = auth.uid() + WHERE wm."userId" = auth.uid() + OR b.visibility = 'public' + ) + `, + }, + ), + pgPolicy("Allow inserting card labels in user's workspace", { + for: "insert", + as: "permissive", + to: [authenticatedRole], + withCheck: sql` + "cardId" IN ( + SELECT c.id + FROM card c + JOIN list l ON c."listId" = l.id + JOIN board b ON l."boardId" = b.id + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + ) + AND + "labelId" IN ( + SELECT l.id + FROM label l + JOIN board b ON l."boardId" = b.id + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + ) + `, + }), + pgPolicy("Allow updating card labels in user's workspace", { + for: "update", + as: "permissive", + to: [authenticatedRole], + using: sql` + "cardId" IN ( + SELECT c.id + FROM card c + JOIN list l ON c."listId" = l.id + JOIN board b ON l."boardId" = b.id + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + ) + AND + "labelId" IN ( + SELECT l.id + FROM label l + JOIN board b ON l."boardId" = b.id + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + ) + `, + }), + pgPolicy("Allow deleting card labels in user's workspace", { + for: "delete", + as: "permissive", + to: [authenticatedRole], + using: sql` + "cardId" IN ( + SELECT c.id + FROM card c + JOIN list l ON c."listId" = l.id + JOIN board b ON l."boardId" = b.id + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + ) + AND + "labelId" IN ( + SELECT l.id + FROM label l + JOIN board b ON l."boardId" = b.id + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + ) + `, + }), + ], +).enableRLS(); + +export const cardToLabelsRelations = relations(cardsToLabels, ({ one }) => ({ + card: one(cards, { + fields: [cardsToLabels.cardId], + references: [cards.id], + }), + label: one(labels, { + fields: [cardsToLabels.labelId], + references: [labels.id], + }), +})); + +export const cardToWorkspaceMembers = pgTable( + "_card_workspace_members", + { + cardId: bigint("cardId", { mode: "number" }) + .notNull() + .references(() => cards.id), + workspaceMemberId: bigint("workspaceMemberId", { mode: "number" }) + .notNull() + .references(() => workspaceMembers.id, { onDelete: "cascade" }), + }, + (t) => [ + primaryKey({ columns: [t.cardId, t.workspaceMemberId] }), + pgPolicy("Allow access to card workspace members in user's workspace", { + for: "all", + as: "permissive", + to: [authenticatedRole], + using: sql` + "cardId" IN ( + SELECT c.id + FROM card c + JOIN list l ON c."listId" = l.id + JOIN board b ON l."boardId" = b.id + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + ) + AND + "workspaceMemberId" IN ( + SELECT wm.id + FROM workspace_members wm + WHERE wm."workspaceId" IN ( + SELECT "workspaceId" + FROM workspace_members + WHERE "userId" = auth.uid() + ) + ) + `, + }), + ], +).enableRLS(); + +export const cardToWorkspaceMembersRelations = relations( + cardToWorkspaceMembers, + ({ one }) => ({ + card: one(cards, { + fields: [cardToWorkspaceMembers.cardId], + references: [cards.id], + }), + member: one(workspaceMembers, { + fields: [cardToWorkspaceMembers.workspaceMemberId], + references: [workspaceMembers.id], + }), + }), +); + +export const comments = pgTable( + "card_comments", + { + id: bigserial("id", { mode: "number" }).primaryKey(), + publicId: varchar("publicId", { length: 12 }).notNull().unique(), + comment: text("comment").notNull(), + cardId: bigint("cardId", { mode: "number" }) + .notNull() + .references(() => cards.id, { onDelete: "cascade" }), + createdBy: uuid("createdBy") + .notNull() + .references(() => users.id), + createdAt: timestamp("createdAt").defaultNow().notNull(), + updatedAt: timestamp("updatedAt"), + deletedAt: timestamp("deletedAt"), + deletedBy: uuid("deletedBy").references(() => users.id), + }, + () => [ + pgPolicy( + "Allow access to card comments in user's workspace or public boards", + { + for: "select", + as: "permissive", + to: [authenticatedRole, anonRole], + using: sql` + "cardId" IN ( + SELECT c.id + FROM card c + JOIN list l ON c."listId" = l.id + JOIN board b ON l."boardId" = b.id + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + OR b.visibility = 'public' + ) + `, + }, + ), + pgPolicy("Allow inserting comments on cards in user's workspace", { + for: "insert", + as: "permissive", + to: [authenticatedRole], + withCheck: sql` + "cardId" IN ( + SELECT c.id + FROM card c + JOIN list l ON c."listId" = l.id + JOIN board b ON l."boardId" = b.id + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + ) + `, + }), + pgPolicy("Allow updating own comments", { + for: "update", + as: "permissive", + to: [authenticatedRole], + using: sql` + "createdBy" = auth.uid() + `, + }), + pgPolicy("Allow deleting own comments", { + for: "delete", + as: "permissive", + to: [authenticatedRole], + using: sql` + "createdBy" = auth.uid() + `, + }), + ], +).enableRLS(); + +export const commentsRelations = relations(comments, ({ one }) => ({ + card: one(cards, { + fields: [comments.cardId], + references: [cards.id], + }), + createdBy: one(users, { + fields: [comments.createdBy], + references: [users.id], + }), + deletedBy: one(users, { + fields: [comments.deletedBy], + references: [users.id], + }), +})); diff --git a/packages/db/src/schema/feedback.ts b/packages/db/src/schema/feedback.ts new file mode 100644 index 00000000..edaea7fb --- /dev/null +++ b/packages/db/src/schema/feedback.ts @@ -0,0 +1,30 @@ +import { relations } from "drizzle-orm"; +import { + bigserial, + boolean, + pgTable, + text, + timestamp, + uuid, +} from "drizzle-orm/pg-core"; + +import { users } from "./users"; + +export const feedback = pgTable("feedback", { + id: bigserial("id", { mode: "number" }).primaryKey(), + feedback: text("feedback").notNull(), + createdBy: uuid("createdBy") + .notNull() + .references(() => users.id), + createdAt: timestamp("createdAt").defaultNow().notNull(), + updatedAt: timestamp("updatedAt"), + url: text("url").notNull(), + reviewed: boolean("reviewed").default(false).notNull(), +}).enableRLS(); + +export const feedbackRelations = relations(feedback, ({ one }) => ({ + createdBy: one(users, { + fields: [feedback.createdBy], + references: [users.id], + }), +})); diff --git a/packages/db/src/schema/imports.ts b/packages/db/src/schema/imports.ts new file mode 100644 index 00000000..6a7f887f --- /dev/null +++ b/packages/db/src/schema/imports.ts @@ -0,0 +1,59 @@ +import { relations, sql } from "drizzle-orm"; +import { + bigserial, + pgEnum, + pgPolicy, + pgTable, + timestamp, + uuid, + varchar, +} from "drizzle-orm/pg-core"; +import { authenticatedRole } from "drizzle-orm/supabase"; + +import { boards } from "./boards"; +import { cards } from "./cards"; +import { labels } from "./labels"; +import { lists } from "./lists"; +import { users } from "./users"; + +export const importSourceEnum = pgEnum("source", ["trello"]); +export const importStatusEnum = pgEnum("status", [ + "started", + "success", + "failed", +]); + +export const imports = pgTable( + "import", + { + id: bigserial("id", { mode: "number" }).primaryKey(), + publicId: varchar("publicId", { length: 12 }).notNull().unique(), + source: importSourceEnum("source").notNull(), + status: importStatusEnum("status").notNull(), + createdBy: uuid("createdBy") + .notNull() + .references(() => users.id), + createdAt: timestamp("createdAt").defaultNow().notNull(), + }, + () => [ + pgPolicy("Allow access to user's own imports", { + for: "all", + as: "permissive", + to: [authenticatedRole], + using: sql` + "createdBy" = auth.uid() + `, + }), + ], +).enableRLS(); + +export const importsRelations = relations(imports, ({ one, many }) => ({ + createdBy: one(users, { + fields: [imports.createdBy], + references: [users.id], + }), + boards: many(boards), + cards: many(cards), + lists: many(lists), + labels: many(labels), +})); diff --git a/packages/db/src/schema/index.ts b/packages/db/src/schema/index.ts new file mode 100644 index 00000000..0511260f --- /dev/null +++ b/packages/db/src/schema/index.ts @@ -0,0 +1,8 @@ +export * from "./boards"; +export * from "./cards"; +export * from "./feedback"; +export * from "./imports"; +export * from "./labels"; +export * from "./lists"; +export * from "./users"; +export * from "./workspaces"; diff --git a/packages/db/src/schema/labels.ts b/packages/db/src/schema/labels.ts new file mode 100644 index 00000000..0843d9a0 --- /dev/null +++ b/packages/db/src/schema/labels.ts @@ -0,0 +1,108 @@ +import { relations, sql } from "drizzle-orm"; +import { + bigint, + bigserial, + pgPolicy, + pgTable, + timestamp, + uuid, + varchar, +} from "drizzle-orm/pg-core"; +import { anonRole, authenticatedRole } from "drizzle-orm/supabase"; + +import { boards } from "./boards"; +import { cardsToLabels } from "./cards"; +import { imports } from "./imports"; +import { users } from "./users"; + +export const labels = pgTable( + "label", + { + id: bigserial("id", { mode: "number" }).primaryKey(), + publicId: varchar("publicId", { length: 12 }).notNull().unique(), + name: varchar("name", { length: 255 }).notNull(), + colourCode: varchar("colourCode", { length: 12 }), + createdBy: uuid("createdBy") + .notNull() + .references(() => users.id), + createdAt: timestamp("createdAt").defaultNow().notNull(), + updatedAt: timestamp("updatedAt"), + boardId: bigint("boardId", { mode: "number" }) + .notNull() + .references(() => boards.id, { onDelete: "cascade" }), + importId: bigint("importId", { mode: "number" }).references( + () => imports.id, + ), + }, + () => [ + pgPolicy("Allow access to labels in user's workspace or public boards", { + for: "select", + as: "permissive", + to: [authenticatedRole, anonRole], + using: sql` + "boardId" IN ( + SELECT b.id + FROM board b + LEFT JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + OR b.visibility = 'public' + ) + `, + }), + pgPolicy("Allow inserting labels in user's workspace", { + for: "insert", + as: "permissive", + to: [authenticatedRole], + withCheck: sql` + "boardId" IN ( + SELECT b.id + FROM board b + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + ) + `, + }), + pgPolicy("Allow updating labels in user's workspace", { + for: "update", + as: "permissive", + to: [authenticatedRole], + using: sql` + "boardId" IN ( + SELECT b.id + FROM board b + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + ) + `, + }), + pgPolicy("Allow deleting labels in user's workspace", { + for: "delete", + as: "permissive", + to: [authenticatedRole], + using: sql` + "boardId" IN ( + SELECT b.id + FROM board b + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + ) + `, + }), + ], +).enableRLS(); + +export const labelsRelations = relations(labels, ({ one, many }) => ({ + createdBy: one(users, { + fields: [labels.createdBy], + references: [users.id], + }), + board: one(boards, { + fields: [labels.boardId], + references: [boards.id], + }), + cards: many(cardsToLabels), + import: one(imports, { + fields: [labels.importId], + references: [imports.id], + }), +})); diff --git a/packages/db/src/schema/lists.ts b/packages/db/src/schema/lists.ts new file mode 100644 index 00000000..1822882e --- /dev/null +++ b/packages/db/src/schema/lists.ts @@ -0,0 +1,115 @@ +import { relations, sql } from "drizzle-orm"; +import { + bigint, + bigserial, + integer, + pgPolicy, + pgTable, + timestamp, + uuid, + varchar, +} from "drizzle-orm/pg-core"; +import { anonRole, authenticatedRole } from "drizzle-orm/supabase"; + +import { boards } from "./boards"; +import { cards } from "./cards"; +import { imports } from "./imports"; +import { users } from "./users"; + +export const lists = pgTable( + "list", + { + id: bigserial("id", { mode: "number" }).primaryKey(), + publicId: varchar("publicId", { length: 12 }).notNull().unique(), + name: varchar("name", { length: 255 }).notNull(), + index: integer("index").notNull(), + createdBy: uuid("createdBy") + .notNull() + .references(() => users.id), + createdAt: timestamp("createdAt").defaultNow().notNull(), + updatedAt: timestamp("updatedAt"), + deletedAt: timestamp("deletedAt"), + deletedBy: uuid("deletedBy").references(() => users.id), + boardId: bigint("boardId", { mode: "number" }) + .notNull() + .references(() => boards.id, { onDelete: "cascade" }), + importId: bigint("importId", { mode: "number" }).references( + () => imports.id, + ), + }, + () => [ + pgPolicy("Allow access to lists in user's workspace or public boards", { + for: "select", + as: "permissive", + to: [authenticatedRole, anonRole], + using: sql` + "boardId" IN ( + SELECT b.id + FROM board b + LEFT JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + OR b.visibility = 'public' + ) + `, + }), + pgPolicy("Allow inserting lists in user's workspace", { + for: "insert", + as: "permissive", + to: [authenticatedRole], + withCheck: sql` + "boardId" IN ( + SELECT b.id + FROM board b + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + ) + `, + }), + pgPolicy("Allow updating lists in user's workspace", { + for: "update", + as: "permissive", + to: [authenticatedRole], + using: sql` + "boardId" IN ( + SELECT b.id + FROM board b + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + ) + `, + }), + pgPolicy("Allow deleting lists in user's workspace", { + for: "delete", + as: "permissive", + to: [authenticatedRole], + using: sql` + "boardId" IN ( + SELECT b.id + FROM board b + JOIN workspace_members wm ON b."workspaceId" = wm."workspaceId" + WHERE wm."userId" = auth.uid() + ) + `, + }), + ], +).enableRLS(); + +export const listsRelations = relations(lists, ({ one, many }) => ({ + createdBy: one(users, { + fields: [lists.createdBy], + references: [users.id], + }), + board: one(boards, { + fields: [lists.boardId], + references: [boards.id], + }), + cards: many(cards), + deletedBy: one(users, { + fields: [lists.deletedBy], + references: [users.id], + }), + import: one(imports, { + fields: [lists.importId], + references: [imports.id], + }), +})); diff --git a/packages/db/src/schema/users.ts b/packages/db/src/schema/users.ts new file mode 100644 index 00000000..4f2f1814 --- /dev/null +++ b/packages/db/src/schema/users.ts @@ -0,0 +1,75 @@ +import { relations, sql } from "drizzle-orm"; +import { + pgPolicy, + pgTable, + timestamp, + uuid, + varchar, +} from "drizzle-orm/pg-core"; +import { authenticatedRole } from "drizzle-orm/supabase"; + +import { boards } from "./boards"; +import { cards } from "./cards"; +import { imports } from "./imports"; +import { lists } from "./lists"; +import { workspaceMembers, workspaces } from "./workspaces"; + +export const users = pgTable( + "user", + { + id: uuid("id").notNull().primaryKey(), + name: varchar("name", { length: 255 }), + email: varchar("email", { length: 255 }).notNull().unique(), + emailVerified: timestamp("emailVerified", { mode: "date" }), + image: varchar("image", { length: 255 }), + stripeCustomerId: varchar("stripeCustomerId", { length: 255 }), + }, + () => [ + pgPolicy("Allow viewing members in user's workspace", { + for: "select", + as: "permissive", + to: [authenticatedRole], + using: sql` + id IN ( + SELECT wm."userId" + FROM workspace_members wm + WHERE wm."workspaceId" IN ( + SELECT "workspaceId" + FROM workspace_members + WHERE "userId" = auth.uid() + ) + ) + `, + }), + ], +).enableRLS(); + +export const usersRelations = relations(users, ({ many }) => ({ + boards: many(boards), + cards: many(cards), + imports: many(imports), + lists: many(lists), + workspaces: many(workspaces), +})); + +export const usersToWorkspacesRelations = relations( + workspaceMembers, + ({ one }) => ({ + addedBy: one(users, { + fields: [workspaceMembers.createdBy], + references: [users.id], + }), + deletedBy: one(users, { + fields: [workspaceMembers.deletedBy], + references: [users.id], + }), + user: one(users, { + fields: [workspaceMembers.userId], + references: [users.id], + }), + workspace: one(workspaces, { + fields: [workspaceMembers.workspaceId], + references: [workspaces.id], + }), + }), +); diff --git a/packages/db/src/schema/workspaces.ts b/packages/db/src/schema/workspaces.ts new file mode 100644 index 00000000..ee6044ae --- /dev/null +++ b/packages/db/src/schema/workspaces.ts @@ -0,0 +1,168 @@ +import { relations, sql } from "drizzle-orm"; +import { + bigint, + bigserial, + pgEnum, + pgPolicy, + pgTable, + text, + timestamp, + uuid, + varchar, +} from "drizzle-orm/pg-core"; +import { anonRole, authenticatedRole } from "drizzle-orm/supabase"; + +import { users } from "./users"; + +export const memberRoleEnum = pgEnum("role", ["admin", "member", "guest"]); +export const memberStatusEnum = pgEnum("member_status", [ + "invited", + "active", + "removed", +]); +export const slugTypeEnum = pgEnum("slug_type", ["reserved", "premium"]); +export const workspacePlanEnum = pgEnum("workspace_plan", [ + "free", + "pro", + "enterprise", +]); + +export const workspaces = pgTable( + "workspace", + { + id: bigserial("id", { mode: "number" }).primaryKey(), + publicId: varchar("publicId", { length: 12 }).notNull().unique(), + name: varchar("name", { length: 255 }).notNull(), + description: text("description"), + slug: varchar("slug", { length: 255 }).notNull().unique(), + plan: workspacePlanEnum("plan").notNull().default("free"), + createdBy: uuid("createdBy") + .notNull() + .references(() => users.id), + createdAt: timestamp("createdAt").defaultNow().notNull(), + updatedAt: timestamp("updatedAt"), + deletedAt: timestamp("deletedAt"), + deletedBy: uuid("deletedBy").references(() => users.id), + }, + () => [ + pgPolicy("Allow viewing user's workspaces", { + for: "select", + as: "permissive", + to: [authenticatedRole, anonRole], + using: sql` + CASE + WHEN auth.uid() IS NULL THEN + EXISTS ( + SELECT 1 + FROM board + WHERE "workspaceId" = workspace.id + AND visibility = 'public' + ) + ELSE + id IN ( + SELECT "workspaceId" + FROM workspace_members + WHERE "userId" = auth.uid() + ) + OR "createdBy" = auth.uid() + END + `, + }), + pgPolicy("Allow updating user's workspaces", { + for: "update", + as: "permissive", + to: [authenticatedRole], + using: sql` + id IN ( + SELECT "workspaceId" + FROM workspace_members + WHERE "userId" = auth.uid() + ) + `, + }), + pgPolicy("Allow deleting user's workspaces", { + for: "delete", + as: "permissive", + to: [authenticatedRole], + using: sql` + id IN ( + SELECT "workspaceId" + FROM workspace_members + WHERE "userId" = auth.uid() + ) + `, + }), + pgPolicy("Allow authenticated users to create workspaces", { + for: "insert", + as: "permissive", + to: [authenticatedRole], + withCheck: sql`true`, + }), + ], +).enableRLS(); + +export const workspaceRelations = relations(workspaces, ({ one, many }) => ({ + user: one(users, { fields: [workspaces.createdBy], references: [users.id] }), + members: many(workspaceMembers), +})); + +export const workspaceMembers = pgTable( + "workspace_members", + { + id: bigserial("id", { mode: "number" }).primaryKey(), + publicId: varchar("publicId", { length: 12 }).notNull().unique(), + userId: uuid("userId") + .notNull() + .references(() => users.id), + workspaceId: bigint("workspaceId", { mode: "number" }) + .notNull() + .references(() => workspaces.id, { onDelete: "cascade" }), + createdBy: uuid("createdBy").notNull(), + createdAt: timestamp("createdAt").defaultNow().notNull(), + updatedAt: timestamp("updatedAt"), + deletedAt: timestamp("deletedAt"), + deletedBy: uuid("deletedBy").references(() => users.id), + role: memberRoleEnum("role").notNull(), + status: memberStatusEnum("status").default("invited").notNull(), + }, + () => [ + pgPolicy("Allow members to view workspace membership", { + for: "select", + as: "permissive", + to: [authenticatedRole], + using: sql` + "userId" = auth.uid() OR + is_workspace_member(auth.uid(), "workspaceId") + `, + }), + pgPolicy("Allow admins to add workspace members", { + for: "insert", + as: "permissive", + to: [authenticatedRole], + withCheck: sql` + is_workspace_admin(auth.uid(), "workspaceId") + `, + }), + pgPolicy("Allow admins to update workspace members", { + for: "update", + as: "permissive", + to: [authenticatedRole], + using: sql` + is_workspace_admin(auth.uid(), "workspaceId") + `, + }), + pgPolicy("Allow admins to remove workspace members", { + for: "delete", + as: "permissive", + to: [authenticatedRole], + using: sql` + is_workspace_admin(auth.uid(), "workspaceId") + `, + }), + ], +).enableRLS(); + +export const slugs = pgTable("workspace_slugs", { + slug: varchar("slug", { length: 255 }).notNull().unique(), + type: slugTypeEnum("type").notNull(), +});