kaneo (minimalist kanban) fork to experiment adding a tangled integration
github.com/usekaneo/kaneo
1import { eq, sql } from "drizzle-orm";
2import { HTTPException } from "hono/http-exception";
3import db from "../../database";
4import { columnTable } from "../../database/schema";
5import { VIRTUAL_STATUSES } from "../../task/validate-task-fields";
6
7function toSlug(name: string): string {
8 return name
9 .toLowerCase()
10 .trim()
11 .replace(/[^a-z0-9]+/g, "-")
12 .replace(/^-+|-+$/g, "");
13}
14
15async function createColumn({
16 projectId,
17 name,
18 icon,
19 color,
20 isFinal,
21}: {
22 projectId: string;
23 name: string;
24 icon?: string;
25 color?: string;
26 isFinal?: boolean;
27}) {
28 const slug = toSlug(name);
29
30 if (!slug) {
31 throw new HTTPException(400, {
32 message: "Column name must contain at least one alphanumeric character",
33 });
34 }
35
36 if ((VIRTUAL_STATUSES as readonly string[]).includes(slug)) {
37 throw new HTTPException(409, {
38 message: `Column slug "${slug}" is reserved for virtual task statuses`,
39 });
40 }
41
42 const existing = await db
43 .select({ id: columnTable.id })
44 .from(columnTable)
45 .where(
46 sql`${columnTable.projectId} = ${projectId} AND ${columnTable.slug} = ${slug}`,
47 );
48
49 if (existing.length > 0) {
50 throw new HTTPException(409, {
51 message: `Column with slug "${slug}" already exists in this project`,
52 });
53 }
54
55 const [maxPos] = await db
56 .select({
57 maxPosition: sql<number>`COALESCE(MAX(${columnTable.position}), -1)`,
58 })
59 .from(columnTable)
60 .where(eq(columnTable.projectId, projectId));
61
62 const position = (maxPos?.maxPosition ?? -1) + 1;
63
64 const [created] = await db
65 .insert(columnTable)
66 .values({
67 projectId,
68 name,
69 slug,
70 position,
71 icon: icon || null,
72 color: color || null,
73 isFinal: isFinal ?? false,
74 })
75 .returning();
76
77 if (!created) {
78 throw new HTTPException(500, { message: "Failed to create column" });
79 }
80
81 return created;
82}
83
84export default createColumn;