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 main 175 lines 4.8 kB view raw
1import { and, eq, sql } from "drizzle-orm"; 2import db from "../database"; 3import { 4 columnTable, 5 integrationTable, 6 projectTable, 7 taskTable, 8 workflowRuleTable, 9} from "../database/schema"; 10 11const DEFAULT_COLUMNS = [ 12 { name: "To Do", slug: "to-do", position: 0, isFinal: false }, 13 { name: "In Progress", slug: "in-progress", position: 1, isFinal: false }, 14 { name: "In Review", slug: "in-review", position: 2, isFinal: false }, 15 { name: "Done", slug: "done", position: 3, isFinal: true }, 16]; 17 18const EVENT_MAPPING: Record<string, string> = { 19 onBranchPush: "branch_push", 20 onPROpen: "pr_opened", 21 onPRMerge: "pr_merged", 22}; 23 24export async function migrateColumns() { 25 console.log("🔄 Starting column migration..."); 26 27 const projects = await db.select().from(projectTable); 28 29 if (projects.length === 0) { 30 console.log("No projects found, skipping column migration"); 31 return; 32 } 33 34 for (const project of projects) { 35 const projectColumns = await db 36 .select({ 37 id: columnTable.id, 38 slug: columnTable.slug, 39 }) 40 .from(columnTable) 41 .where(eq(columnTable.projectId, project.id)); 42 43 const columnMap = new Map<string, string>( 44 projectColumns.map((column) => [column.slug, column.id]), 45 ); 46 47 // Only seed missing default slugs for legacy projects that have no columns yet. 48 // If the project already has columns, missing slugs are intentional (user removed them); 49 // re-inserting on every startup would undo deletions after each API restart. 50 if (projectColumns.length === 0) { 51 for (const defaultColumn of DEFAULT_COLUMNS) { 52 if (columnMap.has(defaultColumn.slug)) { 53 continue; 54 } 55 56 const [inserted] = await db 57 .insert(columnTable) 58 .values({ 59 projectId: project.id, 60 name: defaultColumn.name, 61 slug: defaultColumn.slug, 62 position: defaultColumn.position, 63 isFinal: defaultColumn.isFinal, 64 }) 65 .returning({ id: columnTable.id, slug: columnTable.slug }); 66 67 if (inserted) { 68 columnMap.set(inserted.slug, inserted.id); 69 } 70 } 71 } 72 73 for (const [slug, columnId] of columnMap) { 74 await db 75 .update(taskTable) 76 .set({ columnId }) 77 .where( 78 sql`${taskTable.projectId} = ${project.id} 79 AND ${taskTable.status} = ${slug} 80 AND ${taskTable.columnId} IS DISTINCT FROM ${columnId}`, 81 ); 82 } 83 84 const integrations = await db.query.integrationTable.findMany({ 85 where: eq(integrationTable.projectId, project.id), 86 }); 87 88 for (const integration of integrations) { 89 if ( 90 (integration.type !== "github" && integration.type !== "gitea") || 91 !integration.isActive 92 ) { 93 continue; 94 } 95 96 const forgeType = integration.type as "github" | "gitea"; 97 98 try { 99 const config = JSON.parse(integration.config); 100 const transitions = config.statusTransitions || {}; 101 102 for (const [configKey, eventType] of Object.entries(EVENT_MAPPING)) { 103 const targetSlug = transitions[configKey]; 104 if (!targetSlug) continue; 105 106 const targetColumnId = columnMap.get(targetSlug); 107 if (!targetColumnId) continue; 108 109 await ensureMigrationWorkflowRule( 110 project.id, 111 forgeType, 112 eventType as string, 113 targetColumnId, 114 ); 115 } 116 117 // Add default rules for issue events 118 const todoColumnId = columnMap.get("to-do"); 119 const doneColumnId = columnMap.get("done"); 120 121 if (todoColumnId) { 122 await ensureMigrationWorkflowRule( 123 project.id, 124 forgeType, 125 "issue_opened", 126 todoColumnId, 127 ); 128 } 129 130 if (doneColumnId) { 131 await ensureMigrationWorkflowRule( 132 project.id, 133 forgeType, 134 "issue_closed", 135 doneColumnId, 136 ); 137 } 138 } catch { 139 console.error( 140 `Failed to migrate workflow rules for integration ${integration.id}`, 141 ); 142 } 143 } 144 } 145 146 console.log( 147 `✅ Column migration complete! Migrated ${projects.length} projects`, 148 ); 149} 150 151async function ensureMigrationWorkflowRule( 152 projectId: string, 153 integrationType: "github" | "gitea", 154 eventType: string, 155 columnId: string, 156) { 157 const existing = await db.query.workflowRuleTable.findFirst({ 158 where: and( 159 eq(workflowRuleTable.projectId, projectId), 160 eq(workflowRuleTable.integrationType, integrationType), 161 eq(workflowRuleTable.eventType, eventType), 162 ), 163 }); 164 165 if (existing) { 166 return; 167 } 168 169 await db.insert(workflowRuleTable).values({ 170 projectId, 171 integrationType, 172 eventType, 173 columnId, 174 }); 175}