feat: board slugs

This commit is contained in:
Henry
2025-01-05 16:21:11 +00:00
parent fa9be9d280
commit d98df1d2c6
13 changed files with 1795 additions and 60 deletions

View File

@@ -138,13 +138,20 @@ export const boardRouter = createTRPCRouter({
.input(
z.object({
boardPublicId: z.string().min(12),
name: z.string().min(1),
name: z.string().min(1).optional(),
slug: z
.string()
.min(3)
.max(60)
.regex(/^(?![-]+$)[a-zA-Z0-9-]+$/)
.optional(),
}),
)
.output(z.custom<Awaited<ReturnType<typeof boardRepo.update>>>())
.mutation(async ({ ctx, input }) => {
const result = await boardRepo.update(ctx.db, {
name: input.name,
slug: input.slug,
boardPublicId: input.boardPublicId,
});

View File

@@ -77,7 +77,15 @@ export const workspaceRouter = createTRPCRouter({
protect: true,
},
})
.input(z.object({ workspaceSlug: z.string().min(3).max(24) }))
.input(
z.object({
workspaceSlug: z
.string()
.min(3)
.max(24)
.regex(/^(?![-]+$)[a-zA-Z0-9-]+$/),
}),
)
.output(
z.custom<Awaited<ReturnType<typeof workspaceRepo.getBySlugWithBoards>>>(),
)
@@ -144,7 +152,12 @@ export const workspaceRouter = createTRPCRouter({
z.object({
workspacePublicId: z.string().min(12),
name: z.string().min(3).max(24).optional(),
slug: z.string().min(3).max(24).optional(),
slug: z
.string()
.min(3)
.max(24)
.regex(/^(?![-]+$)[a-zA-Z0-9-]+$/)
.optional(),
description: z.string().min(3).max(280).optional(),
}),
)
@@ -225,7 +238,15 @@ export const workspaceRouter = createTRPCRouter({
protect: true,
},
})
.input(z.object({ workspaceSlug: z.string().min(3).max(24) }))
.input(
z.object({
workspaceSlug: z
.string()
.min(3)
.max(24)
.regex(/^(?![-]+$)[a-zA-Z0-9-]+$/),
}),
)
.output(
z.object({
isAvailable: z.boolean(),

View File

@@ -0,0 +1,12 @@
-- First add the columns without NOT NULL constraint
ALTER TABLE "board" ADD COLUMN "description" text;--> statement-breakpoint
ALTER TABLE "board" ADD COLUMN "slug" varchar(255);--> statement-breakpoint
-- Update existing records with a default slug (using board id to ensure uniqueness)
UPDATE "board" SET "slug" = 'board-' || id::text;--> statement-breakpoint
-- Now add the NOT NULL constraint
ALTER TABLE "board" ALTER COLUMN "slug" SET NOT NULL;--> statement-breakpoint
-- Finally create the unique index
CREATE UNIQUE INDEX IF NOT EXISTS "unique_slug_per_workspace" ON "board" USING btree ("workspaceId","slug");

File diff suppressed because it is too large Load Diff

View File

@@ -29,6 +29,13 @@
"when": 1736084845433,
"tag": "0003_fine_bedlam",
"breakpoints": true
},
{
"idx": 4,
"version": "7",
"when": 1736087559461,
"tag": "0004_eminent_living_mummy",
"breakpoints": true
}
]
}

View File

@@ -30,6 +30,7 @@ export const getByPublicId = async (
`
publicId,
name,
slug,
workspace (
publicId,
members:workspace_members (
@@ -151,14 +152,19 @@ export const create = async (
export const update = async (
db: SupabaseClient<Database>,
boardInput: { name: string; boardPublicId: string },
boardInput: {
name: string | undefined;
slug: string | undefined;
boardPublicId: string;
},
) => {
const { data } = await db
.from("board")
.update({ name: boardInput.name })
.update({ name: boardInput.name, slug: boardInput.slug })
.eq("publicId", boardInput.boardPublicId)
.select(`publicId, name`)
.limit(1)
.order("id", { ascending: false })
.single();
return data;

View File

@@ -123,6 +123,7 @@ export const getBySlugWithBoards = async (
slug,
boards: board (
publicId,
slug,
name
)
`,

View File

@@ -8,6 +8,7 @@ import {
primaryKey,
text,
timestamp,
uniqueIndex,
uuid,
varchar,
} from "drizzle-orm/pg-core";
@@ -46,22 +47,35 @@ export const workspacePlanEnum = pgEnum("workspace_plan", [
"enterprise",
]);
export const boards = pgTable("board", {
id: bigserial("id", { mode: "number" }).primaryKey(),
publicId: varchar("publicId", { length: 12 }).notNull().unique(),
name: varchar("name", { length: 255 }).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),
importId: bigint("importId", { mode: "number" }).references(() => imports.id),
workspaceId: bigint("workspaceId", { mode: "number" })
.notNull()
.references(() => workspaces.id, { onDelete: "cascade" }),
});
export const boards = pgTable(
"board",
{
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(),
createdBy: uuid("createdBy")
.notNull()
.references(() => users.id),
createdAt: timestamp("createdAt").defaultNow().notNull(),
updatedAt: timestamp("updatedAt"),
deletedAt: timestamp("deletedAt"),
deletedBy: uuid("deletedBy").references(() => users.id),
importId: bigint("importId", { mode: "number" }).references(
() => imports.id,
),
workspaceId: bigint("workspaceId", { mode: "number" })
.notNull()
.references(() => workspaces.id, { onDelete: "cascade" }),
},
(table) => ({
uniqueSlugPerWorkspace: uniqueIndex("unique_slug_per_workspace").on(
table.workspaceId,
table.slug,
),
}),
);
export const boardsRelations = relations(boards, ({ one, many }) => ({
createdBy: one(users, {

View File

@@ -75,10 +75,12 @@ export type Database = {
createdBy: string
deletedAt: string | null
deletedBy: string | null
description: string | null
id: number
importId: number | null
name: string
publicId: string
slug: string
updatedAt: string | null
workspaceId: number
}
@@ -87,10 +89,12 @@ export type Database = {
createdBy: string
deletedAt?: string | null
deletedBy?: string | null
description?: string | null
id?: number
importId?: number | null
name: string
publicId: string
slug: string
updatedAt?: string | null
workspaceId: number
}
@@ -99,10 +103,12 @@ export type Database = {
createdBy?: string
deletedAt?: string | null
deletedBy?: string | null
description?: string | null
id?: number
importId?: number | null
name?: string
publicId?: string
slug?: string
updatedAt?: string | null
workspaceId?: number
}