diff --git a/apps/web/src/views/board/components/UpdateBoardSlugForm.tsx b/apps/web/src/views/board/components/UpdateBoardSlugForm.tsx index 9d479b85..b68a702d 100644 --- a/apps/web/src/views/board/components/UpdateBoardSlugForm.tsx +++ b/apps/web/src/views/board/components/UpdateBoardSlugForm.tsx @@ -167,7 +167,8 @@ export function UpdateBoardSlugForm({ !isDirty || updateBoardSlug.isPending || errors.slug?.message !== undefined || - isBoardSlugAvailable?.isReserved + isBoardSlugAvailable?.isReserved || + checkBoardSlugAvailability.isLoading } > {t`Update`} diff --git a/packages/api/src/routers/board.ts b/packages/api/src/routers/board.ts index 41412d7a..b8fc5678 100644 --- a/packages/api/src/routers/board.ts +++ b/packages/api/src/routers/board.ts @@ -276,6 +276,21 @@ export const boardRouter = createTRPCRouter({ await assertUserInWorkspace(ctx.db, userId, board.workspaceId); + if (input.slug) { + const isBoardSlugAvailable = await boardRepo.isBoardSlugAvailable( + ctx.db, + input.slug, + board.workspaceId, + ); + + if (!isBoardSlugAvailable) { + throw new TRPCError({ + message: `Board slug ${input.slug} is not available`, + code: "BAD_REQUEST", + }); + } + } + const result = await boardRepo.update(ctx.db, { name: input.name, slug: input.slug, @@ -407,11 +422,23 @@ export const boardRouter = createTRPCRouter({ }), ) .query(async ({ ctx, input }) => { + const board = await boardRepo.getWorkspaceAndBoardIdByBoardPublicId( + ctx.db, + input.boardPublicId, + ); + + if (!board) + throw new TRPCError({ + message: `Board with public ID ${input.boardPublicId} not found`, + code: "NOT_FOUND", + }); + const isBoardSlugAvailable = await boardRepo.isBoardSlugAvailable( ctx.db, input.boardSlug, - input.boardPublicId, + board.workspaceId, ); + return { isReserved: !isBoardSlugAvailable, }; diff --git a/packages/db/src/repository/board.repo.ts b/packages/db/src/repository/board.repo.ts index f228ce2e..7a3c5125 100644 --- a/packages/db/src/repository/board.repo.ts +++ b/packages/db/src/repository/board.repo.ts @@ -1,4 +1,4 @@ -import { and, asc, desc, eq, exists, inArray, isNull, or, sql } from "drizzle-orm"; +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"; @@ -481,30 +481,18 @@ export const getWorkspaceAndBoardIdByBoardPublicId = async ( export const isBoardSlugAvailable = async ( db: dbClient, boardSlug: string, - boardPublicId: string, + workspaceId: number, ) => { - const result = await db - .select({ id: boards.id }) - .from(boards) - .where( - and( - eq(boards.publicId, boardPublicId), - exists( - db - .select({ id: boards.id }) - .from(boards) - .where( - and( - eq(boards.slug, boardSlug), - eq(boards.workspaceId, sql`${boards.workspaceId}`), // Reference outer query's workspaceId - isNull(boards.deletedAt), - ), - ) - .limit(1), - ), - ), - ) - .limit(1); + 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.length === 0; + return result === undefined; };