From 520a376b0f26e1fbe0ca2c209f5a11b8045a0557 Mon Sep 17 00:00:00 2001 From: Henry Date: Thu, 24 Apr 2025 21:47:36 +0100 Subject: [PATCH] refactor: update board repo to use drizzle --- packages/db/src/repository/board.repo.ts | 361 +++++++++++++---------- packages/db/src/schema/boards.ts | 10 +- 2 files changed, 213 insertions(+), 158 deletions(-) diff --git a/packages/db/src/repository/board.repo.ts b/packages/db/src/repository/board.repo.ts index 2cef93bc..04b2c52b 100644 --- a/packages/db/src/repository/board.repo.ts +++ b/packages/db/src/repository/board.repo.ts @@ -1,8 +1,7 @@ -import type { SupabaseClient } from "@supabase/supabase-js"; -import { and, asc, eq, inArray, isNull } from "drizzle-orm"; +import { and, asc, desc, eq, inArray, isNull } from "drizzle-orm"; import type { dbClient } from "@kan/db/client"; -import type { Database } from "@kan/db/types/database.types"; +import type { BoardVisibilityStatus } from "@kan/db/schema"; import { boards, cards, lists, workspaceMembers } from "@kan/db/schema"; import { generateUID } from "@kan/shared/utils"; @@ -16,18 +15,15 @@ export const getAllByWorkspaceId = (db: dbClient, workspaceId: number) => { }); }; -export const getIdByPublicId = async ( - db: SupabaseClient, - boardPublicId: string, -) => { - const { data } = await db - .from("board") - .select("id") - .eq("publicId", boardPublicId) - .limit(1) - .single(); +export const getIdByPublicId = async (db: dbClient, boardPublicId: string) => { + const board = await db.query.boards.findFirst({ + columns: { + id: true, + }, + where: eq(boards.publicId, boardPublicId), + }); - return data; + return board; }; export const getByPublicId = async ( @@ -161,104 +157,150 @@ export const getByPublicId = async ( }; export const getBySlug = async ( - db: SupabaseClient, + db: dbClient, boardSlug: string, filters: { members: string[]; labels: string[]; }, ) => { - let query = db - .from("board") - .select( - ` - publicId, - name, - slug, - workspace ( - publicId, - name, - slug, - description - ), - labels:label ( - publicId, - name, - colourCode - ), - lists:list ( - publicId, - name, - boardId, - index, - cards:card ( - publicId, - title, - description, - listId, - index, - labels:label( - publicId, - name, - colourCode - ), - _filteredLabels:label${filters.labels.length > 0 ? "!inner" : ""} ( - publicId - ) - ) - ) - `, - ) - .eq("slug", boardSlug) - .is("deletedAt", null) - .is("lists.deletedAt", null) - .is("lists.cards.deletedAt", null); + const board = await db.query.boards.findFirst({ + columns: { + publicId: true, + name: true, + slug: true, + visibility: true, + }, + with: { + workspace: { + columns: { + publicId: true, + }, + with: { + members: { + columns: { + publicId: true, + }, + with: { + user: { + columns: { + name: true, + email: true, + image: true, + }, + }, + }, + where: isNull(workspaceMembers.deletedAt), + }, + }, + }, + labels: { + columns: { + publicId: true, + name: true, + colourCode: true, + }, + }, + lists: { + columns: { + publicId: true, + name: true, + boardId: true, + index: true, + }, + with: { + cards: { + columns: { + publicId: true, + title: true, + description: true, + listId: true, + index: true, + }, + with: { + labels: { + with: { + label: { + columns: { + publicId: true, + name: true, + colourCode: true, + }, + // where: inArray(label.publicId, filters.labels), + }, + }, + }, + }, + where: isNull(cards.deletedAt), + orderBy: [asc(cards.index)], + }, + }, + where: isNull(lists.deletedAt), + orderBy: [asc(lists.index)], + }, + }, + where: and(eq(boards.slug, boardSlug), isNull(boards.deletedAt)), + }); - if (filters.labels.length > 0) { - query = query.in("lists.cards._filteredLabels.publicId", filters.labels); - } + if (!board) return null; - const { data } = await query - .order("index", { foreignTable: "list", ascending: true }) - .order("index", { foreignTable: "list.card", ascending: true }) - .limit(1) - .single(); + const formattedResult = { + ...board, + lists: board.lists.map((list) => ({ + ...list, + cards: list.cards.map((card) => ({ + ...card, + labels: card.labels.map((label) => label.label), + })), + })), + }; - return data; + return formattedResult; }; -export const getWithListIdsByPublicId = async ( - db: SupabaseClient, +export const getWithListIdsByPublicId = ( + db: dbClient, boardPublicId: string, ) => { - const { data } = await db - .from("board") - .select(`id, lists:list (id)`) - .eq("publicId", boardPublicId) - .limit(1) - .single(); - - return data; + return db.query.boards.findFirst({ + columns: { + id: true, + }, + with: { + lists: { + columns: { + id: true, + }, + }, + }, + where: eq(boards.publicId, boardPublicId), + }); }; -export const getWithLatestListIndexByPublicId = async ( - db: SupabaseClient, +export const getWithLatestListIndexByPublicId = ( + db: dbClient, boardPublicId: string, ) => { - const { data } = await db - .from("board") - .select(`id, lists:list (index)`) - .eq("publicId", boardPublicId) - .order("index", { foreignTable: "list", ascending: false }) - .is("list.deletedAt", null) - .limit(1) - .single(); - - return data; + return db.query.boards.findFirst({ + columns: { + id: true, + }, + with: { + lists: { + columns: { + index: true, + }, + where: isNull(lists.deletedAt), + orderBy: [desc(lists.index)], + limit: 1, + }, + }, + where: eq(boards.publicId, boardPublicId), + }); }; export const create = async ( - db: SupabaseClient, + db: dbClient, boardInput: { publicId?: string; name: string; @@ -268,9 +310,9 @@ export const create = async ( slug: string; }, ) => { - const { data } = await db - .from("board") - .insert({ + const [result] = await db + .insert(boards) + .values({ publicId: boardInput.publicId ?? generateUID(), name: boardInput.name, createdBy: boardInput.createdBy, @@ -278,76 +320,87 @@ export const create = async ( importId: boardInput.importId, slug: boardInput.slug, }) - .select(`id, publicId, name`) - .limit(1) - .single(); - - return data; -}; - -export const update = async ( - db: SupabaseClient, - boardInput: { - name: string | undefined; - slug: string | undefined; - visibility: "public" | "private" | undefined; - boardPublicId: string; - }, -) => { - const { data } = await db - .from("board") - .update({ - name: boardInput.name, - slug: boardInput.slug, - visibility: boardInput.visibility, - updatedAt: new Date().toISOString(), - }) - .eq("publicId", boardInput.boardPublicId) - .select(`publicId, name`) - .limit(1) - .order("id", { ascending: false }) - .single(); - - return data; -}; - -export const softDelete = async ( - db: SupabaseClient, - args: { - boardId: number; - deletedAt: string; - deletedBy: string; - }, -) => { - const result = db - .from("board") - .update({ deletedAt: args.deletedAt, deletedBy: args.deletedBy }) - .eq("id", args.boardId) - .is("deletedAt", null); + .returning({ + id: boards.id, + publicId: boards.publicId, + name: boards.name, + }); return result; }; -export const hardDelete = async ( - db: SupabaseClient, - workspaceId: number, +export const update = async ( + db: dbClient, + boardInput: { + name: string | undefined; + slug: string | undefined; + visibility: BoardVisibilityStatus | undefined; + boardPublicId: string; + }, ) => { - const result = db.from("board").delete().eq("workspaceId", workspaceId); + const [result] = await db + .update(boards) + .set({ + name: boardInput.name, + slug: boardInput.slug, + visibility: boardInput.visibility, + updatedAt: new Date(), + }) + .where(eq(boards.publicId, boardInput.boardPublicId)) + .returning({ + publicId: boards.publicId, + name: boards.name, + }); + + return result; +}; + +export const softDelete = async ( + db: dbClient, + args: { + boardId: number; + deletedAt: Date; + deletedBy: string; + }, +) => { + const [result] = await db + .update(boards) + .set({ deletedAt: args.deletedAt, deletedBy: args.deletedBy }) + .where(and(eq(boards.id, args.boardId), isNull(boards.deletedAt))) + .returning({ + publicId: boards.publicId, + name: boards.name, + }); + + return result; +}; + +export const hardDelete = async (db: dbClient, workspaceId: number) => { + const [result] = await db + .delete(boards) + .where(eq(boards.workspaceId, workspaceId)) + .returning({ + publicId: boards.publicId, + name: boards.name, + }); return result; }; export const isSlugUnique = async ( - db: SupabaseClient, + db: dbClient, args: { slug: string; workspaceId: number }, ) => { - const { data } = await db - .from("board") - .select("slug") - .eq("slug", args.slug) - .eq("workspaceId", args.workspaceId) - .is("deletedAt", null) - .limit(1); + const result = await db.query.boards.findFirst({ + columns: { + slug: true, + }, + where: and( + eq(boards.slug, args.slug), + eq(boards.workspaceId, args.workspaceId), + isNull(boards.deletedAt), + ), + }); - return data?.length === 0; + return result === undefined; }; diff --git a/packages/db/src/schema/boards.ts b/packages/db/src/schema/boards.ts index 0f3f0306..6f1c2e4b 100644 --- a/packages/db/src/schema/boards.ts +++ b/packages/db/src/schema/boards.ts @@ -18,10 +18,12 @@ import { lists } from "./lists"; import { users } from "./users"; import { workspaces } from "./workspaces"; -export const boardVisibilityEnum = pgEnum("board_visibility", [ - "private", - "public", -]); +export const boardVisibilityStatuses = ["private", "public"] as const; +export type BoardVisibilityStatus = (typeof boardVisibilityStatuses)[number]; +export const boardVisibilityEnum = pgEnum( + "board_visibility", + boardVisibilityStatuses, +); export const boards = pgTable( "board",