feat: board slugs
This commit is contained in:
@@ -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,
|
||||
});
|
||||
|
||||
|
||||
@@ -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(),
|
||||
|
||||
12
packages/db/migrations/0004_eminent_living_mummy.sql
Normal file
12
packages/db/migrations/0004_eminent_living_mummy.sql
Normal 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");
|
||||
1548
packages/db/migrations/meta/0004_snapshot.json
Normal file
1548
packages/db/migrations/meta/0004_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -123,6 +123,7 @@ export const getBySlugWithBoards = async (
|
||||
slug,
|
||||
boards: board (
|
||||
publicId,
|
||||
slug,
|
||||
name
|
||||
)
|
||||
`,
|
||||
|
||||
@@ -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, {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user