refactor: update board slug prefix to use dynamic base URL from env (#38)

* refactor: update board slug prefix to use dynamic base URL from env

* feat: add board slug availability check with real-time validation UI

* refactor: consolidate board slug lookup by removing boardSlug repo and updating environment variables

* feat: allow board slug reuse across different workspaces

* chore: update environment settings and remove unused imports

* refactor: update board slug availability check to use boardPublicId instead of workspaceSlug

* refactor: simplify board slug availability check with direct SQL query
This commit is contained in:
LovelessCodes
2025-06-06 13:55:00 +02:00
committed by GitHub
parent e590de80c0
commit 805ef8cf43
3 changed files with 106 additions and 5 deletions

View File

@@ -352,4 +352,40 @@ export const boardRouter = createTRPCRouter({
return { success: true };
}),
checkSlugAvailability: publicProcedure
.meta({
openapi: {
summary: "Check if a board slug is available",
method: "GET",
path: "/boards/{boardPublicId}/check-slug-availability",
description: "Checks if a board slug is available",
tags: ["Boards"],
protect: true,
},
})
.input(
z.object({
boardSlug: z
.string()
.min(3)
.max(24)
.regex(/^(?![-]+$)[a-zA-Z0-9-]+$/),
boardPublicId: z.string().min(12),
}),
)
.output(
z.object({
isReserved: z.boolean(),
}),
)
.query(async ({ ctx, input }) => {
const isBoardSlugAvailable = await boardRepo.isBoardSlugAvailable(
ctx.db,
input.boardSlug,
input.boardPublicId,
);
return {
isReserved: !isBoardSlugAvailable,
};
}),
});

View File

@@ -1,4 +1,4 @@
import { and, asc, desc, eq, inArray, isNull, or } from "drizzle-orm";
import { and, asc, desc, eq, exists, inArray, isNull, or, sql } from "drizzle-orm";
import type { dbClient } from "@kan/db/client";
import type { BoardVisibilityStatus } from "@kan/db/schema";
@@ -477,3 +477,34 @@ export const getWorkspaceAndBoardIdByBoardPublicId = async (
return result;
};
export const isBoardSlugAvailable = async (
db: dbClient,
boardSlug: string,
boardPublicId: string,
) => {
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);
return result.length === 0;
};