import { and, asc, desc, eq, inArray, isNull, or } from "drizzle-orm"; import type { dbClient } from "@kan/db/client"; import type { BoardVisibilityStatus } from "@kan/db/schema"; import { boards, cards, cardsToLabels, cardToWorkspaceMembers, checklistItems, checklists, labels, lists, workspaceMembers, } from "@kan/db/schema"; import { generateUID } from "@kan/shared/utils"; export const getAllByWorkspaceId = (db: dbClient, workspaceId: number) => { return db.query.boards.findMany({ columns: { publicId: true, name: true, }, where: and(eq(boards.workspaceId, workspaceId), isNull(boards.deletedAt)), }); }; export const getIdByPublicId = async (db: dbClient, boardPublicId: string) => { const board = await db.query.boards.findFirst({ columns: { id: true, }, where: eq(boards.publicId, boardPublicId), }); return board; }; export const getByPublicId = async ( db: dbClient, boardPublicId: string, filters: { members: string[]; labels: string[]; }, ) => { let cardIds: string[] = []; if (filters.labels.length > 0 || filters.members.length > 0) { const filteredCards = await db .select({ publicId: cards.publicId, }) .from(cards) .leftJoin(cardsToLabels, eq(cards.id, cardsToLabels.cardId)) .leftJoin(labels, eq(cardsToLabels.labelId, labels.id)) .leftJoin( cardToWorkspaceMembers, eq(cards.id, cardToWorkspaceMembers.cardId), ) .leftJoin( workspaceMembers, eq(cardToWorkspaceMembers.workspaceMemberId, workspaceMembers.id), ) .where( and( isNull(cards.deletedAt), or( filters.labels.length > 0 ? inArray(labels.publicId, filters.labels) : undefined, filters.members.length > 0 ? inArray(workspaceMembers.publicId, filters.members) : undefined, ), ), ); cardIds = filteredCards.map((card) => card.publicId); } 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, email: true, }, with: { user: { columns: { name: true, email: true, image: true, }, }, }, where: isNull(workspaceMembers.deletedAt), }, }, }, labels: { columns: { publicId: true, name: true, colourCode: true, }, where: isNull(labels.deletedAt), }, 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, }, }, }, }, members: { with: { member: { columns: { publicId: true, email: true, deletedAt: true, }, with: { user: { columns: { name: true, email: true, image: true, }, }, }, }, }, }, checklists: { columns: { publicId: true, name: true, index: true, }, where: isNull(checklists.deletedAt), orderBy: asc(checklists.index), with: { items: { columns: { publicId: true, title: true, completed: true, index: true, }, where: isNull(checklistItems.deletedAt), orderBy: asc(checklistItems.index), }, }, }, }, where: and( cardIds.length > 0 ? inArray(cards.publicId, cardIds) : undefined, isNull(cards.deletedAt), ), orderBy: [asc(cards.index)], }, }, where: isNull(lists.deletedAt), orderBy: [asc(lists.index)], }, }, where: and(eq(boards.publicId, boardPublicId), isNull(boards.deletedAt)), }); if (!board) return null; const formattedResult = { ...board, lists: board.lists.map((list) => ({ ...list, cards: list.cards.map((card) => ({ ...card, labels: card.labels.map((label) => label.label), members: card.members .map((member) => member.member) .filter((member) => member.deletedAt === null), })), })), }; return formattedResult; }; export const getBySlug = async ( db: dbClient, boardSlug: string, workspaceId: number, filters: { members: string[]; labels: string[]; }, ) => { let cardIds: string[] = []; if (filters.labels.length) { const filteredCards = await db .select({ publicId: cards.publicId, }) .from(cards) .leftJoin(cardsToLabels, eq(cards.id, cardsToLabels.cardId)) .leftJoin(labels, eq(cardsToLabels.labelId, labels.id)) .where( and( isNull(cards.deletedAt), filters.labels.length > 0 ? inArray(labels.publicId, filters.labels) : undefined, ), ); cardIds = filteredCards.map((card) => card.publicId); } const board = await db.query.boards.findFirst({ columns: { publicId: true, name: true, slug: true, visibility: true, }, with: { workspace: { columns: { publicId: true, name: true, slug: true, }, }, labels: { columns: { publicId: true, name: true, colourCode: true, }, where: isNull(labels.deletedAt), }, 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, }, }, }, }, checklists: { columns: { publicId: true, name: true, index: true, }, where: isNull(checklists.deletedAt), orderBy: asc(checklists.index), with: { items: { columns: { publicId: true, title: true, completed: true, index: true, }, where: isNull(checklistItems.deletedAt), orderBy: asc(checklistItems.index), }, }, }, }, where: and( cardIds.length > 0 ? inArray(cards.publicId, cardIds) : undefined, isNull(cards.deletedAt), ), orderBy: [asc(cards.index)], }, }, where: isNull(lists.deletedAt), orderBy: [asc(lists.index)], }, }, where: and( eq(boards.slug, boardSlug), eq(boards.workspaceId, workspaceId), isNull(boards.deletedAt), eq(boards.visibility, "public"), ), }); if (!board) return null; const formattedResult = { ...board, lists: board.lists.map((list) => ({ ...list, cards: list.cards.map((card) => ({ ...card, labels: card.labels.map((label) => label.label), })), })), }; return formattedResult; }; export const getWithListIdsByPublicId = ( db: dbClient, boardPublicId: string, ) => { return db.query.boards.findFirst({ columns: { id: true, workspaceId: true, }, with: { lists: { columns: { id: true, }, }, }, where: eq(boards.publicId, boardPublicId), }); }; export const getWithLatestListIndexByPublicId = ( db: dbClient, boardPublicId: string, ) => { return db.query.boards.findFirst({ columns: { id: true, workspaceId: 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: dbClient, boardInput: { publicId?: string; name: string; createdBy: string; workspaceId: number; importId?: number; slug: string; }, ) => { const [result] = await db .insert(boards) .values({ publicId: boardInput.publicId ?? generateUID(), name: boardInput.name, createdBy: boardInput.createdBy, workspaceId: boardInput.workspaceId, importId: boardInput.importId, slug: boardInput.slug, }) .returning({ id: boards.id, publicId: boards.publicId, name: boards.name, }); return result; }; export const update = async ( db: dbClient, boardInput: { name: string | undefined; slug: string | undefined; visibility: BoardVisibilityStatus | undefined; boardPublicId: string; }, ) => { 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: dbClient, args: { slug: string; workspaceId: number }, ) => { 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 result === undefined; }; export const getWorkspaceAndBoardIdByBoardPublicId = async ( db: dbClient, boardPublicId: string, ) => { const result = await db.query.boards.findFirst({ columns: { id: true, workspaceId: true, }, where: eq(boards.publicId, boardPublicId), }); return result; }; export const isBoardSlugAvailable = async ( db: dbClient, boardSlug: string, workspaceId: number, ) => { const result = await db.query.boards.findFirst({ columns: { id: true, }, where: and( eq(boards.slug, boardSlug), eq(boards.workspaceId, workspaceId), isNull(boards.deletedAt), ), }); return result === undefined; };