From c1348cf4558b29f6b8e0d0bb6f0ace79efce2d9f Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 23 Apr 2025 13:11:00 +0100 Subject: [PATCH] refactor: reinstate drizzle --- packages/api/src/routers/card.ts | 21 +- packages/api/src/routers/feedback.ts | 2 +- packages/api/src/routers/member.ts | 8 +- packages/api/src/routers/user.ts | 4 +- packages/api/src/routers/workspace.ts | 6 +- packages/api/src/trpc.ts | 37 +- .../migrations/0009_shallow_silver_surfer.sql | 37 + .../db/migrations/meta/0009_snapshot.json | 1651 +++++++++++++++++ packages/db/migrations/meta/_journal.json | 7 + packages/db/package.json | 2 +- packages/db/src/client.ts | 5 + packages/db/src/repository/card.repo.ts | 68 +- packages/db/src/repository/user.repo.ts | 21 +- packages/db/src/schema/boards.ts | 51 - packages/db/src/schema/cards.ts | 421 +---- packages/db/src/schema/imports.ts | 37 +- packages/db/src/schema/labels.ts | 94 +- packages/db/src/schema/lists.ts | 98 +- packages/db/src/schema/users.ts | 48 +- packages/db/src/schema/workspaces.ts | 164 +- packages/supabase/src/clients.ts | 30 +- pnpm-lock.yaml | 31 +- 22 files changed, 1964 insertions(+), 879 deletions(-) create mode 100644 packages/db/migrations/0009_shallow_silver_surfer.sql create mode 100644 packages/db/migrations/meta/0009_snapshot.json diff --git a/packages/api/src/routers/card.ts b/packages/api/src/routers/card.ts index d24b2c4a..cc54d560 100644 --- a/packages/api/src/routers/card.ts +++ b/packages/api/src/routers/card.ts @@ -57,18 +57,11 @@ export const cardRouter = createTRPCRouter({ let index = 0; - if (list.cards.length) { - if (input.position === "end" && lastCard) index = lastCard.index + 1; - - if (input.position === "start") { - await cardRepo.pushIndex(ctx.db, { - listId: list.id, - cardIndex: 0, - }); - } + if (list.cards.length && input.position === "end" && lastCard) { + index = lastCard.index + 1; } - const newCard = await cardRepo.create(ctx.db, { + const newCard = await cardRepo.create(ctx.drizzleDb, { title: input.title, description: input.description, createdBy: userId, @@ -76,7 +69,7 @@ export const cardRouter = createTRPCRouter({ index, }); - const newCardId = newCard?.id; + const newCardId = newCard.id; if (!newCardId) throw new TRPCError({ @@ -84,12 +77,6 @@ export const cardRouter = createTRPCRouter({ code: "INTERNAL_SERVER_ERROR", }); - await cardActivityRepo.create(ctx.db, { - type: "card.created", - cardId: newCard.id, - createdBy: userId, - }); - if (newCardId && input.labelPublicIds.length) { const labels = await labelRepo.getAllByPublicIds( ctx.db, diff --git a/packages/api/src/routers/feedback.ts b/packages/api/src/routers/feedback.ts index f0d57102..073e2da1 100644 --- a/packages/api/src/routers/feedback.ts +++ b/packages/api/src/routers/feedback.ts @@ -24,7 +24,7 @@ export const feedbackRouter = createTRPCRouter({ code: "UNAUTHORIZED", }); - const result = await feedbackRepo.create(ctx.adminDb, { + const result = await feedbackRepo.create(ctx.db, { feedback: input.feedback, createdBy: userId, url: input.url, diff --git a/packages/api/src/routers/member.ts b/packages/api/src/routers/member.ts index 0ff00225..993593b5 100644 --- a/packages/api/src/routers/member.ts +++ b/packages/api/src/routers/member.ts @@ -73,12 +73,12 @@ export const memberRouter = createTRPCRouter({ let hashedToken: string | undefined; let verificationType: string | undefined; - const existingUser = await userRepo.getByEmail(ctx.adminDb, input.email); + const existingUser = await userRepo.getByEmail(ctx.db, input.email); if (existingUser) { invitedUserId = existingUser.id; - const magicLink = await ctx.adminDb.auth.admin.generateLink({ + const magicLink = await ctx.db.auth.admin.generateLink({ type: "magiclink", email: input.email, options: { @@ -89,7 +89,7 @@ export const memberRouter = createTRPCRouter({ hashedToken = magicLink.data.properties?.hashed_token; verificationType = magicLink.data.properties?.verification_type; } else { - const invite = await ctx.adminDb.auth.admin.generateLink({ + const invite = await ctx.db.auth.admin.generateLink({ type: "invite", email: input.email, options: { @@ -111,7 +111,7 @@ export const memberRouter = createTRPCRouter({ }, }); - const newUser = await userRepo.create(ctx.adminDb, { + const newUser = await userRepo.create(ctx.db, { email: invitedUserEmail, id: invitedUserAuthId, stripeCustomerId: stripeCustomer.id, diff --git a/packages/api/src/routers/user.ts b/packages/api/src/routers/user.ts index e40cf337..6eefa5ce 100644 --- a/packages/api/src/routers/user.ts +++ b/packages/api/src/routers/user.ts @@ -37,7 +37,7 @@ export const userRouter = createTRPCRouter({ code: "UNAUTHORIZED", }); - const result = await userRepo.getById(ctx.db, userId); + const result = await userRepo.getById(ctx.drizzleDb, userId); if (!result?.name) { throw new TRPCError({ @@ -81,7 +81,7 @@ export const userRouter = createTRPCRouter({ code: "UNAUTHORIZED", }); - const result = await userRepo.update(ctx.adminDb, userId, input); + const result = await userRepo.update(ctx.db, userId, input); if (!result) { throw new TRPCError({ diff --git a/packages/api/src/routers/workspace.ts b/packages/api/src/routers/workspace.ts index b86fcaa6..dc37083b 100644 --- a/packages/api/src/routers/workspace.ts +++ b/packages/api/src/routers/workspace.ts @@ -126,7 +126,7 @@ export const workspaceRouter = createTRPCRouter({ const workspacePublicId = generateUID(); - const result = await workspaceRepo.create(ctx.adminDb, { + const result = await workspaceRepo.create(ctx.db, { publicId: workspacePublicId, name: input.name, slug: workspacePublicId, @@ -174,7 +174,7 @@ export const workspaceRouter = createTRPCRouter({ ); const reservedOrPremiumWorkspaceSlug = - await workspaceSlugRepo.getWorkspaceSlug(ctx.adminDb, input.slug); + await workspaceSlugRepo.getWorkspaceSlug(ctx.db, input.slug); const isWorkspaceSlugAvailable = await workspaceRepo.isWorkspaceSlugAvailable(ctx.db, input.slug); @@ -261,7 +261,7 @@ export const workspaceRouter = createTRPCRouter({ const slug = input.workspaceSlug.toLowerCase(); // check slug is not reserved const workspaceSlug = await workspaceSlugRepo.getWorkspaceSlug( - ctx.adminDb, + ctx.db, slug, ); diff --git a/packages/api/src/trpc.ts b/packages/api/src/trpc.ts index 55fbf743..77c9d940 100644 --- a/packages/api/src/trpc.ts +++ b/packages/api/src/trpc.ts @@ -1,20 +1,15 @@ -// import type { Pool } from "@neondatabase/serverless"; import type { FetchCreateContextFnOptions } from "@trpc/server/adapters/fetch"; import type { CreateNextContextOptions } from "@trpc/server/adapters/next"; -// import type { NeonDatabase as DrizzleClient } from "drizzle-orm/neon-serverless"; import type { OpenApiMeta } from "trpc-to-openapi"; import { initTRPC, TRPCError } from "@trpc/server"; import superjson from "superjson"; import { ZodError } from "zod"; +import type { dbClient } from "@kan/db/client"; import type { Database } from "@kan/db/types/database.types"; import type { SupabaseClient } from "@kan/supabase"; -// import { createDrizzleClient } from "@kan/db/client"; -import { - createNextApiClient, - createTRPCAdminClient, - createTRPCClient, -} from "@kan/supabase"; +import { createDrizzleClient } from "@kan/db/client"; +import { createNextApiClient, createTRPCClient } from "@kan/supabase"; export interface User { id: string; @@ -23,22 +18,14 @@ export interface User { interface CreateContextOptions { user: User | null; db: SupabaseClient; - adminDb: SupabaseClient; - // drizzleDb: - // | (DrizzleClient< - // typeof import("/Users/henryball/kan/packages/db/dist/schema") - // > & { - // $client: Pool; - // }) - // | null; + drizzleDb: dbClient; } export const createInnerTRPCContext = (opts: CreateContextOptions) => { return { user: opts.user, db: opts.db, - adminDb: opts.adminDb, - // drizzleDb: opts.drizzleDb, + drizzleDb: opts.drizzleDb, }; }; @@ -47,37 +34,35 @@ export const createTRPCContext = async ({ resHeaders, }: FetchCreateContextFnOptions) => { const db = createTRPCClient(req, resHeaders); - const adminDb = createTRPCAdminClient(); const { data: { user }, } = await db.auth.getUser(); - // const drizzleDb = createDrizzleClient(); + const drizzleDb = createDrizzleClient(); - return createInnerTRPCContext({ db, adminDb, user }); + return createInnerTRPCContext({ db, user, drizzleDb }); }; export const createRESTContext = async ({ req }: CreateNextContextOptions) => { const db = createNextApiClient(req); - const adminDb = createTRPCAdminClient(); const authHeader = req.headers.authorization; const accessToken = authHeader?.startsWith("Bearer ") ? authHeader.substring(7) : null; + const drizzleDb = createDrizzleClient(); + if (!accessToken) { - return createInnerTRPCContext({ db, adminDb, user: null }); + return createInnerTRPCContext({ db, user: null, drizzleDb }); } const { data: { user }, } = await db.auth.getUser(accessToken); - // const drizzleDb = createDrizzleClient(); - - return createInnerTRPCContext({ db, adminDb, user }); + return createInnerTRPCContext({ db, user, drizzleDb }); }; const t = initTRPC diff --git a/packages/db/migrations/0009_shallow_silver_surfer.sql b/packages/db/migrations/0009_shallow_silver_surfer.sql new file mode 100644 index 00000000..816afd83 --- /dev/null +++ b/packages/db/migrations/0009_shallow_silver_surfer.sql @@ -0,0 +1,37 @@ +DROP POLICY "Allow access to boards in user's workspace or public boards" ON "board" CASCADE;--> statement-breakpoint +DROP POLICY "Allow inserting boards in user's workspace" ON "board" CASCADE;--> statement-breakpoint +DROP POLICY "Allow updating boards in user's workspace" ON "board" CASCADE;--> statement-breakpoint +DROP POLICY "Allow deleting boards in user's workspace" ON "board" CASCADE;--> statement-breakpoint +DROP POLICY "Allow access to card activity in user's workspace or public boards" ON "card_activity" CASCADE;--> statement-breakpoint +DROP POLICY "Allow inserting card activity in user's workspace" ON "card_activity" CASCADE;--> statement-breakpoint +DROP POLICY "Allow access to card workspace members in user's workspace" ON "_card_workspace_members" CASCADE;--> statement-breakpoint +DROP POLICY "Allow access to cards in user's workspace or public boards" ON "card" CASCADE;--> statement-breakpoint +DROP POLICY "Allow inserting cards in user's workspace" ON "card" CASCADE;--> statement-breakpoint +DROP POLICY "Allow updating cards in user's workspace" ON "card" CASCADE;--> statement-breakpoint +DROP POLICY "Allow deleting cards in user's workspace" ON "card" CASCADE;--> statement-breakpoint +DROP POLICY "Allow access to card labels in user's workspace or public boards" ON "_card_labels" CASCADE;--> statement-breakpoint +DROP POLICY "Allow inserting card labels in user's workspace" ON "_card_labels" CASCADE;--> statement-breakpoint +DROP POLICY "Allow updating card labels in user's workspace" ON "_card_labels" CASCADE;--> statement-breakpoint +DROP POLICY "Allow deleting card labels in user's workspace" ON "_card_labels" CASCADE;--> statement-breakpoint +DROP POLICY "Allow access to card comments in user's workspace or public boards" ON "card_comments" CASCADE;--> statement-breakpoint +DROP POLICY "Allow inserting comments on cards in user's workspace" ON "card_comments" CASCADE;--> statement-breakpoint +DROP POLICY "Allow updating own comments" ON "card_comments" CASCADE;--> statement-breakpoint +DROP POLICY "Allow deleting own comments" ON "card_comments" CASCADE;--> statement-breakpoint +DROP POLICY "Allow access to user's own imports" ON "import" CASCADE;--> statement-breakpoint +DROP POLICY "Allow access to labels in user's workspace or public boards" ON "label" CASCADE;--> statement-breakpoint +DROP POLICY "Allow inserting labels in user's workspace" ON "label" CASCADE;--> statement-breakpoint +DROP POLICY "Allow updating labels in user's workspace" ON "label" CASCADE;--> statement-breakpoint +DROP POLICY "Allow deleting labels in user's workspace" ON "label" CASCADE;--> statement-breakpoint +DROP POLICY "Allow access to lists in user's workspace or public boards" ON "list" CASCADE;--> statement-breakpoint +DROP POLICY "Allow inserting lists in user's workspace" ON "list" CASCADE;--> statement-breakpoint +DROP POLICY "Allow updating lists in user's workspace" ON "list" CASCADE;--> statement-breakpoint +DROP POLICY "Allow deleting lists in user's workspace" ON "list" CASCADE;--> statement-breakpoint +DROP POLICY "Allow viewing members in user's workspace" ON "user" CASCADE;--> statement-breakpoint +DROP POLICY "Allow members to view workspace membership" ON "workspace_members" CASCADE;--> statement-breakpoint +DROP POLICY "Allow admins to add workspace members" ON "workspace_members" CASCADE;--> statement-breakpoint +DROP POLICY "Allow admins to update workspace members" ON "workspace_members" CASCADE;--> statement-breakpoint +DROP POLICY "Allow admins to remove workspace members" ON "workspace_members" CASCADE;--> statement-breakpoint +DROP POLICY "Allow viewing user's workspaces" ON "workspace" CASCADE;--> statement-breakpoint +DROP POLICY "Allow updating user's workspaces" ON "workspace" CASCADE;--> statement-breakpoint +DROP POLICY "Allow deleting user's workspaces" ON "workspace" CASCADE;--> statement-breakpoint +DROP POLICY "Allow authenticated users to create workspaces" ON "workspace" CASCADE; \ No newline at end of file diff --git a/packages/db/migrations/meta/0009_snapshot.json b/packages/db/migrations/meta/0009_snapshot.json new file mode 100644 index 00000000..183a18ad --- /dev/null +++ b/packages/db/migrations/meta/0009_snapshot.json @@ -0,0 +1,1651 @@ +{ + "id": "0564220f-2da3-41be-b394-65ee7964f491", + "prevId": "164a9145-d0f6-4651-b25c-ecc713054951", + "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": {}, + "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": {}, + "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": {}, + "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": {}, + "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": {}, + "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": {}, + "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": {}, + "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": {}, + "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": {}, + "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": {}, + "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": {}, + "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": {}, + "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 204c9b55..8c175bf5 100644 --- a/packages/db/migrations/meta/_journal.json +++ b/packages/db/migrations/meta/_journal.json @@ -64,6 +64,13 @@ "when": 1744906695652, "tag": "0008_mature_ravenous", "breakpoints": true + }, + { + "idx": 9, + "version": "7", + "when": 1745407291997, + "tag": "0009_shallow_silver_surfer", + "breakpoints": true } ] } \ No newline at end of file diff --git a/packages/db/package.json b/packages/db/package.json index f886a1cd..dfe2ea16 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -41,7 +41,7 @@ "@kan/shared": "workspace:^", "@neondatabase/serverless": "^0.10.4", "@vercel/postgres": "^0.10.0", - "drizzle-orm": "^0.36.4", + "drizzle-orm": "^0.42.0", "drizzle-zod": "^0.5.1", "zod": "catalog:" }, diff --git a/packages/db/src/client.ts b/packages/db/src/client.ts index f1089afe..a83d17ad 100644 --- a/packages/db/src/client.ts +++ b/packages/db/src/client.ts @@ -1,8 +1,13 @@ +import type { NeonDatabase as DrizzleClient } from "drizzle-orm/neon-serverless"; import { Pool } from "@neondatabase/serverless"; import { drizzle } from "drizzle-orm/neon-serverless"; import * as schema from "./schema"; +export type dbClient = DrizzleClient & { + $client: Pool; +}; + export const createDrizzleClient = () => { const pool = new Pool({ connectionString: process.env.POSTGRES_URL, diff --git a/packages/db/src/repository/card.repo.ts b/packages/db/src/repository/card.repo.ts index 3413713f..28133e03 100644 --- a/packages/db/src/repository/card.repo.ts +++ b/packages/db/src/repository/card.repo.ts @@ -1,10 +1,13 @@ import type { SupabaseClient } from "@supabase/supabase-js"; +import { and, eq, isNull, sql } from "drizzle-orm"; +import type { dbClient } from "@kan/db/client"; import type { Database } from "@kan/db/types/database.types"; +import * as schema from "@kan/db/schema"; import { generateUID } from "@kan/shared/utils"; export const create = async ( - db: SupabaseClient, + db: dbClient, cardInput: { title: string; description: string; @@ -13,21 +16,56 @@ export const create = async ( index: number; }, ) => { - const { data } = await db - .from("card") - .insert({ - publicId: generateUID(), - title: cardInput.title, - description: cardInput.description, - createdBy: cardInput.createdBy, - listId: cardInput.listId, - index: cardInput.index, - }) - .select(`id`) - .limit(1) - .single(); + return db.transaction(async (tx) => { + const getExistingCardAtIndex = async () => + tx.query.cards.findFirst({ + columns: { + id: true, + }, + where: and( + eq(schema.cards.listId, cardInput.listId), + eq(schema.cards.index, cardInput.index), + isNull(schema.cards.deletedAt), + ), + }); - return data; + const existingCardAtIndex = await getExistingCardAtIndex(); + + if (existingCardAtIndex?.id) { + await tx.execute(sql` + UPDATE card + SET index = index + 1 + WHERE "listId" = ${cardInput.listId} AND index >= ${cardInput.index} AND "deletedAt" IS NULL; + `); + + const refetchedExistingCardAtIndex = await getExistingCardAtIndex(); + + if (refetchedExistingCardAtIndex?.id) return tx.rollback(); + } + + const result = await tx + .insert(schema.cards) + .values({ + publicId: generateUID(), + title: cardInput.title, + description: cardInput.description, + createdBy: cardInput.createdBy, + listId: cardInput.listId, + index: cardInput.index, + }) + .returning({ id: schema.cards.id }); + + if (!result[0]) return tx.rollback(); + + await tx.insert(schema.cardActivities).values({ + publicId: generateUID(), + cardId: result[0].id, + type: "card.created", + createdBy: cardInput.createdBy, + }); + + return result[0]; + }); }; export const bulkCreateCardLabelRelationships = async ( diff --git a/packages/db/src/repository/user.repo.ts b/packages/db/src/repository/user.repo.ts index 05742ff5..d4c42e6e 100644 --- a/packages/db/src/repository/user.repo.ts +++ b/packages/db/src/repository/user.repo.ts @@ -1,14 +1,21 @@ import type { SupabaseClient } from "@supabase/supabase-js"; +import { eq } from "drizzle-orm"; +import type { dbClient } from "@kan/db/client"; import type { Database } from "@kan/db/types/database.types"; +import * as schema from "@kan/db/schema"; -export const getById = async (db: SupabaseClient, userId: string) => { - const { data } = await db - .from("user") - .select(`id, name, email, image, stripeCustomerId`) - .eq("id", userId) - .limit(1) - .single(); +export const getById = async (db: dbClient, userId: string) => { + const data = await db.query.users.findFirst({ + columns: { + id: true, + name: true, + email: true, + image: true, + stripeCustomerId: true, + }, + where: eq(schema.users.id, userId), + }); return data; }; diff --git a/packages/db/src/schema/boards.ts b/packages/db/src/schema/boards.ts index 82d383d7..0f3f0306 100644 --- a/packages/db/src/schema/boards.ts +++ b/packages/db/src/schema/boards.ts @@ -4,7 +4,6 @@ import { bigserial, index, pgEnum, - pgPolicy, pgTable, text, timestamp, @@ -12,7 +11,6 @@ import { uuid, varchar, } from "drizzle-orm/pg-core"; -import { anonRole, authenticatedRole } from "drizzle-orm/supabase"; import { imports } from "./imports"; import { labels } from "./labels"; @@ -53,55 +51,6 @@ export const boards = pgTable( 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(); diff --git a/packages/db/src/schema/cards.ts b/packages/db/src/schema/cards.ts index 2de473b4..ed6cbe40 100644 --- a/packages/db/src/schema/cards.ts +++ b/packages/db/src/schema/cards.ts @@ -1,10 +1,9 @@ -import { relations, sql } from "drizzle-orm"; +import { relations } from "drizzle-orm"; import { bigint, bigserial, integer, pgEnum, - pgPolicy, pgTable, primaryKey, text, @@ -12,7 +11,6 @@ import { uuid, varchar, } from "drizzle-orm/pg-core"; -import { anonRole, authenticatedRole } from "drizzle-orm/supabase"; import { imports } from "./imports"; import { labels } from "./labels"; @@ -36,89 +34,24 @@ export const activityTypeEnum = pgEnum("card_activity_type", [ "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 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), +}).enableRLS(); export const cardsRelations = relations(cards, ({ one, many }) => ({ createdBy: one(users, { @@ -142,76 +75,37 @@ export const cardsRelations = relations(cards, ({ one, many }) => ({ 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 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"), +}).enableRLS(); export const cardActivitiesRelations = relations(cardActivities, ({ one }) => ({ card: one(cards, { @@ -250,106 +144,7 @@ export const cardsToLabels = pgTable( .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() - ) - `, - }), - ], + (t) => [primaryKey({ columns: [t.cardId, t.labelId] })], ).enableRLS(); export const cardToLabelsRelations = relations(cardsToLabels, ({ one }) => ({ @@ -373,34 +168,7 @@ export const cardToWorkspaceMembers = pgTable( .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() - ) - ) - `, - }), - ], + (t) => [primaryKey({ columns: [t.cardId, t.workspaceMemberId] })], ).enableRLS(); export const cardToWorkspaceMembersRelations = relations( @@ -417,76 +185,21 @@ export const cardToWorkspaceMembersRelations = relations( }), ); -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 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), +}).enableRLS(); export const commentsRelations = relations(comments, ({ one }) => ({ card: one(cards, { diff --git a/packages/db/src/schema/imports.ts b/packages/db/src/schema/imports.ts index 6a7f887f..eb51ac5a 100644 --- a/packages/db/src/schema/imports.ts +++ b/packages/db/src/schema/imports.ts @@ -1,14 +1,12 @@ -import { relations, sql } from "drizzle-orm"; +import { relations } 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"; @@ -23,29 +21,16 @@ export const importStatusEnum = pgEnum("status", [ "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 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(), +}).enableRLS(); export const importsRelations = relations(imports, ({ one, many }) => ({ createdBy: one(users, { diff --git a/packages/db/src/schema/labels.ts b/packages/db/src/schema/labels.ts index 0843d9a0..7ab31717 100644 --- a/packages/db/src/schema/labels.ts +++ b/packages/db/src/schema/labels.ts @@ -1,95 +1,33 @@ -import { relations, sql } from "drizzle-orm"; +import { relations } 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 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), +}).enableRLS(); export const labelsRelations = relations(labels, ({ one, many }) => ({ createdBy: one(users, { diff --git a/packages/db/src/schema/lists.ts b/packages/db/src/schema/lists.ts index 1822882e..5a3b013d 100644 --- a/packages/db/src/schema/lists.ts +++ b/packages/db/src/schema/lists.ts @@ -1,98 +1,36 @@ -import { relations, sql } from "drizzle-orm"; +import { relations } 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 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), +}).enableRLS(); export const listsRelations = relations(lists, ({ one, many }) => ({ createdBy: one(users, { diff --git a/packages/db/src/schema/users.ts b/packages/db/src/schema/users.ts index 4f2f1814..8efb54b9 100644 --- a/packages/db/src/schema/users.ts +++ b/packages/db/src/schema/users.ts @@ -1,12 +1,5 @@ -import { relations, sql } from "drizzle-orm"; -import { - pgPolicy, - pgTable, - timestamp, - uuid, - varchar, -} from "drizzle-orm/pg-core"; -import { authenticatedRole } from "drizzle-orm/supabase"; +import { relations } from "drizzle-orm"; +import { pgTable, timestamp, uuid, varchar } from "drizzle-orm/pg-core"; import { boards } from "./boards"; import { cards } from "./cards"; @@ -14,35 +7,14 @@ 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 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 }), +}).enableRLS(); export const usersRelations = relations(users, ({ many }) => ({ boards: many(boards), diff --git a/packages/db/src/schema/workspaces.ts b/packages/db/src/schema/workspaces.ts index ee6044ae..7c2aa3c2 100644 --- a/packages/db/src/schema/workspaces.ts +++ b/packages/db/src/schema/workspaces.ts @@ -1,16 +1,14 @@ -import { relations, sql } from "drizzle-orm"; +import { relations } 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"; @@ -27,140 +25,44 @@ export const workspacePlanEnum = pgEnum("workspace_plan", [ "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 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), +}).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 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(), +}).enableRLS(); export const slugs = pgTable("workspace_slugs", { slug: varchar("slug", { length: 255 }).notNull().unique(), diff --git a/packages/supabase/src/clients.ts b/packages/supabase/src/clients.ts index c9bcca4b..6b2eb35f 100644 --- a/packages/supabase/src/clients.ts +++ b/packages/supabase/src/clients.ts @@ -74,15 +74,15 @@ export function createNextApiClient(req: NextApiRequest) { export function createTRPCClient(req: Request, resHeaders: Headers) { const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL; - const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY; + const serviceKey = process.env.SUPABASE_SERVICE_API_KEY; - if (!supabaseUrl || !supabaseKey) { + if (!supabaseUrl || !serviceKey) { throw new Error("Missing Supabase environment variables"); } const supabase = createServerClient( supabaseUrl, - supabaseKey, + serviceKey, { cookies: { get(name: string) { @@ -101,27 +101,3 @@ export function createTRPCClient(req: Request, resHeaders: Headers) { return supabase; } - -export function createTRPCAdminClient() { - const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL; - const serviceKey = process.env.SUPABASE_SERVICE_API_KEY; - - if (!supabaseUrl || !serviceKey) { - throw new Error("Missing Supabase environment variables"); - } - - const supabase = createServerClient( - supabaseUrl, - serviceKey, - { - cookies: { - get: (_name: string) => "", - set: (_name: string, _value: string, _options: CookieOptions) => - undefined, - remove: (_name: string, _options: CookieOptions) => undefined, - }, - }, - ); - - return supabase; -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 061dde7b..7d08938c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -264,11 +264,11 @@ importers: specifier: ^0.10.0 version: 0.10.0(utf-8-validate@6.0.3) drizzle-orm: - specifier: ^0.36.4 - version: 0.36.4(@neondatabase/serverless@0.10.4)(@types/pg@8.11.6)(@types/react@18.3.15)(@vercel/postgres@0.10.0(utf-8-validate@6.0.3))(pg@8.13.1)(postgres@3.4.5)(react@18.3.1) + specifier: ^0.42.0 + version: 0.42.0(@neondatabase/serverless@0.10.4)(@types/pg@8.11.6)(@vercel/postgres@0.10.0(utf-8-validate@6.0.3))(pg@8.13.1)(postgres@3.4.5) drizzle-zod: specifier: ^0.5.1 - version: 0.5.1(drizzle-orm@0.36.4(@neondatabase/serverless@0.10.4)(@types/pg@8.11.6)(@types/react@18.3.15)(@vercel/postgres@0.10.0(utf-8-validate@6.0.3))(pg@8.13.1)(postgres@3.4.5)(react@18.3.1))(zod@3.24.0) + version: 0.5.1(drizzle-orm@0.42.0(@neondatabase/serverless@0.10.4)(@types/pg@8.11.6)(@vercel/postgres@0.10.0(utf-8-validate@6.0.3))(pg@8.13.1)(postgres@3.4.5))(zod@3.24.0) zod: specifier: 'catalog:' version: 3.24.0 @@ -2562,36 +2562,35 @@ packages: resolution: {integrity: sha512-JimOV+ystXTWMgZkLHYHf2w3oS28hxiH1FR0dkmJLc7GHzdGJoJAQtQS5DRppnabsRZwE2U1F6CuezVBgmsBBQ==} hasBin: true - drizzle-orm@0.36.4: - resolution: {integrity: sha512-1OZY3PXD7BR00Gl61UUOFihslDldfH4NFRH2MbP54Yxi0G/PKn4HfO65JYZ7c16DeP3SpM3Aw+VXVG9j6CRSXA==} + drizzle-orm@0.42.0: + resolution: {integrity: sha512-pS8nNJm2kBNZwrOjTHJfdKkaU+KuUQmV/vk5D57NojDq4FG+0uAYGMulXtYT///HfgsMF0hnFFvu1ezI3OwOkg==} peerDependencies: '@aws-sdk/client-rds-data': '>=3' - '@cloudflare/workers-types': '>=3' + '@cloudflare/workers-types': '>=4' '@electric-sql/pglite': '>=0.2.0' '@libsql/client': '>=0.10.0' '@libsql/client-wasm': '>=0.10.0' '@neondatabase/serverless': '>=0.10.0' '@op-engineering/op-sqlite': '>=2' '@opentelemetry/api': ^1.4.1 - '@planetscale/database': '>=1' + '@planetscale/database': '>=1.13' '@prisma/client': '*' '@tidbcloud/serverless': '*' '@types/better-sqlite3': '*' '@types/pg': '*' - '@types/react': '>=18' '@types/sql.js': '*' '@vercel/postgres': '>=0.8.0' '@xata.io/client': '*' better-sqlite3: '>=7' bun-types: '*' expo-sqlite: '>=14.0.0' + gel: '>=2' knex: '*' kysely: '*' mysql2: '>=2' pg: '>=8' postgres: '>=3' prisma: '*' - react: '>=18' sql.js: '>=1' sqlite3: '>=5' peerDependenciesMeta: @@ -2621,8 +2620,6 @@ packages: optional: true '@types/pg': optional: true - '@types/react': - optional: true '@types/sql.js': optional: true '@vercel/postgres': @@ -2635,6 +2632,8 @@ packages: optional: true expo-sqlite: optional: true + gel: + optional: true knex: optional: true kysely: @@ -2647,8 +2646,6 @@ packages: optional: true prisma: optional: true - react: - optional: true sql.js: optional: true sqlite3: @@ -7231,19 +7228,17 @@ snapshots: transitivePeerDependencies: - supports-color - drizzle-orm@0.36.4(@neondatabase/serverless@0.10.4)(@types/pg@8.11.6)(@types/react@18.3.15)(@vercel/postgres@0.10.0(utf-8-validate@6.0.3))(pg@8.13.1)(postgres@3.4.5)(react@18.3.1): + drizzle-orm@0.42.0(@neondatabase/serverless@0.10.4)(@types/pg@8.11.6)(@vercel/postgres@0.10.0(utf-8-validate@6.0.3))(pg@8.13.1)(postgres@3.4.5): optionalDependencies: '@neondatabase/serverless': 0.10.4 '@types/pg': 8.11.6 - '@types/react': 18.3.15 '@vercel/postgres': 0.10.0(utf-8-validate@6.0.3) pg: 8.13.1 postgres: 3.4.5 - react: 18.3.1 - drizzle-zod@0.5.1(drizzle-orm@0.36.4(@neondatabase/serverless@0.10.4)(@types/pg@8.11.6)(@types/react@18.3.15)(@vercel/postgres@0.10.0(utf-8-validate@6.0.3))(pg@8.13.1)(postgres@3.4.5)(react@18.3.1))(zod@3.24.0): + drizzle-zod@0.5.1(drizzle-orm@0.42.0(@neondatabase/serverless@0.10.4)(@types/pg@8.11.6)(@vercel/postgres@0.10.0(utf-8-validate@6.0.3))(pg@8.13.1)(postgres@3.4.5))(zod@3.24.0): dependencies: - drizzle-orm: 0.36.4(@neondatabase/serverless@0.10.4)(@types/pg@8.11.6)(@types/react@18.3.15)(@vercel/postgres@0.10.0(utf-8-validate@6.0.3))(pg@8.13.1)(postgres@3.4.5)(react@18.3.1) + drizzle-orm: 0.42.0(@neondatabase/serverless@0.10.4)(@types/pg@8.11.6)(@vercel/postgres@0.10.0(utf-8-validate@6.0.3))(pg@8.13.1)(postgres@3.4.5) zod: 3.24.0 dunder-proto@1.0.0: