kaneo (minimalist kanban) fork to experiment adding a tangled integration
github.com/usekaneo/kaneo
1import { and, eq } from "drizzle-orm";
2import { HTTPException } from "hono/http-exception";
3import db from "../../database";
4import { columnTable, projectTable, taskTable } from "../../database/schema";
5import { publishEvent } from "../../events";
6import {
7 coercePriority,
8 coerceStatus,
9 getValidTaskStatuses,
10} from "../validate-task-fields";
11import getNextTaskNumber from "./get-next-task-number";
12
13type ImportTask = {
14 title: string;
15 description?: string;
16 status: string;
17 priority?: string;
18 startDate?: string;
19 dueDate?: string;
20 userId?: string | null;
21};
22
23async function importTasks(projectId: string, tasksToImport: ImportTask[]) {
24 const project = await db.query.projectTable.findFirst({
25 where: eq(projectTable.id, projectId),
26 });
27
28 if (!project) {
29 throw new HTTPException(404, {
30 message: "Project not found",
31 });
32 }
33
34 const nextTaskNumber = await getNextTaskNumber(projectId);
35 let taskNumber = nextTaskNumber;
36 const validStatuses = await getValidTaskStatuses(projectId);
37
38 const results = [];
39
40 for (const taskData of tasksToImport) {
41 try {
42 const { status, warning: statusWarning } = coerceStatus(
43 taskData.status,
44 validStatuses,
45 );
46 const { priority, warning: priorityWarning } = coercePriority(
47 taskData.priority || "low",
48 );
49 const warnings = [statusWarning, priorityWarning].filter(Boolean);
50
51 const column = await db.query.columnTable.findFirst({
52 where: and(
53 eq(columnTable.projectId, projectId),
54 eq(columnTable.slug, status),
55 ),
56 });
57
58 const [createdTask] = await db
59 .insert(taskTable)
60 .values({
61 projectId,
62 userId: taskData.userId || null,
63 title: taskData.title,
64 status,
65 columnId: column?.id ?? null,
66 startDate: taskData.startDate ? new Date(taskData.startDate) : null,
67 dueDate: taskData.dueDate ? new Date(taskData.dueDate) : null,
68 description: taskData.description || "",
69 priority,
70 number: ++taskNumber,
71 })
72 .returning();
73
74 if (createdTask) {
75 await publishEvent("task.created", {
76 taskId: createdTask.id,
77 userId: createdTask.userId ?? "",
78 type: "create",
79 content: "imported the task",
80 });
81
82 results.push({
83 success: true,
84 task: createdTask,
85 ...(warnings.length > 0 && { warnings }),
86 });
87 } else {
88 results.push({
89 success: false,
90 error: "Failed to create task",
91 task: taskData,
92 });
93 }
94 } catch (error) {
95 if (error instanceof HTTPException) {
96 throw error;
97 }
98 results.push({
99 success: false,
100 error: error instanceof Error ? error.message : "Unknown error",
101 task: taskData,
102 });
103 }
104 }
105
106 return {
107 importedAt: new Date().toISOString(),
108 project: {
109 id: project.id,
110 name: project.name,
111 slug: project.slug,
112 },
113 results: {
114 total: tasksToImport.length,
115 successful: results.filter((r) => r.success).length,
116 failed: results.filter((r) => !r.success).length,
117 tasks: results,
118 },
119 };
120}
121
122export default importTasks;