kaneo (minimalist kanban) fork to experiment adding a tangled integration github.com/usekaneo/kaneo
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at cd7cada2f86b4e866a15b4323bb8d6d7ab5bba8b 144 lines 3.5 kB view raw
1import type { InferSelectModel } from "drizzle-orm"; 2import { and, eq } from "drizzle-orm"; 3import db from "../../../database"; 4import { 5 columnTable, 6 integrationTable, 7 taskTable, 8} from "../../../database/schema"; 9 10export type TaskRow = InferSelectModel<typeof taskTable>; 11 12export type UpdateTaskStatusResult = 13 | { applied: false } 14 | { applied: true; before: TaskRow; after: TaskRow }; 15 16const NON_COLUMN_STATUSES = new Set(["planned", "archived"]); 17 18export async function findTaskByNumber(projectId: string, taskNumber: number) { 19 return db.query.taskTable.findFirst({ 20 where: and( 21 eq(taskTable.projectId, projectId), 22 eq(taskTable.number, taskNumber), 23 ), 24 }); 25} 26 27export async function findTaskById(taskId: string) { 28 return db.query.taskTable.findFirst({ 29 where: eq(taskTable.id, taskId), 30 }); 31} 32 33export async function updateTaskStatus( 34 taskId: string, 35 newStatus: string, 36): Promise<UpdateTaskStatusResult> { 37 const task = await db.query.taskTable.findFirst({ 38 where: eq(taskTable.id, taskId), 39 }); 40 41 if (!task) { 42 return { applied: false }; 43 } 44 45 let columnId: string | null = null; 46 47 const column = await db.query.columnTable.findFirst({ 48 where: and( 49 eq(columnTable.projectId, task.projectId), 50 eq(columnTable.slug, newStatus), 51 ), 52 }); 53 54 if (column) { 55 columnId = column.id; 56 } else if (!NON_COLUMN_STATUSES.has(newStatus)) { 57 console.warn( 58 `[GitHub] Skipping status update for task ${taskId}: column "${newStatus}" not found in project ${task.projectId}`, 59 ); 60 return { applied: false }; 61 } 62 63 await db 64 .update(taskTable) 65 .set({ status: newStatus, columnId }) 66 .where(eq(taskTable.id, taskId)); 67 68 const after = await db.query.taskTable.findFirst({ 69 where: eq(taskTable.id, taskId), 70 }); 71 72 if (!after) { 73 return { applied: false }; 74 } 75 76 return { applied: true, before: task, after }; 77} 78 79export async function isTaskInFinalState(task: { 80 projectId: string; 81 status: string; 82 columnId: string | null; 83}): Promise<boolean> { 84 if (task.columnId) { 85 const columnById = await db.query.columnTable.findFirst({ 86 where: and( 87 eq(columnTable.id, task.columnId), 88 eq(columnTable.projectId, task.projectId), 89 ), 90 }); 91 92 if (columnById) { 93 return columnById.isFinal; 94 } 95 } 96 97 const columnByStatus = await db.query.columnTable.findFirst({ 98 where: and( 99 eq(columnTable.projectId, task.projectId), 100 eq(columnTable.slug, task.status), 101 ), 102 }); 103 104 if (columnByStatus) { 105 return columnByStatus.isFinal; 106 } 107 108 return task.status === "done"; 109} 110 111export async function getIntegrationWithProject(integrationId: string) { 112 return db.query.integrationTable.findFirst({ 113 where: eq(integrationTable.id, integrationId), 114 with: { 115 project: true, 116 }, 117 }); 118} 119 120export async function findIntegrationByRepo(owner: string, repo: string) { 121 const integrations = await findAllIntegrationsByRepo(owner, repo); 122 return integrations[0] || null; 123} 124 125export async function findAllIntegrationsByRepo(owner: string, repo: string) { 126 const integrations = await db.query.integrationTable.findMany({ 127 where: and( 128 eq(integrationTable.type, "github"), 129 eq(integrationTable.isActive, true), 130 ), 131 with: { 132 project: true, 133 }, 134 }); 135 136 return integrations.filter((integration) => { 137 try { 138 const config = JSON.parse(integration.config); 139 return config.repositoryOwner === owner && config.repositoryName === repo; 140 } catch { 141 return false; 142 } 143 }); 144}