kaneo (minimalist kanban) fork to experiment adding a tangled integration github.com/usekaneo/kaneo
at main 173 lines 4.8 kB view raw
1import { and, eq, sql } from "drizzle-orm"; 2import db from "../../database"; 3import { 4 externalLinkTable, 5 integrationTable, 6 taskTable, 7} from "../../database/schema"; 8import { defaultGitHubConfig } from "./config"; 9 10async function tableExists(tableName: string): Promise<boolean> { 11 try { 12 const result = await db.execute(sql` 13 SELECT EXISTS ( 14 SELECT FROM information_schema.tables 15 WHERE table_schema = 'public' 16 AND table_name = ${tableName} 17 ); 18 `); 19 return (result.rows[0] as { exists: boolean })?.exists === true; 20 } catch { 21 return false; 22 } 23} 24 25export async function migrateGitHubIntegration() { 26 const oldTableExists = await tableExists("github_integration"); 27 28 if (!oldTableExists) { 29 console.log("No old github_integration table found, skipping migration"); 30 return; 31 } 32 33 console.log("🔄 Starting GitHub integration migration..."); 34 35 try { 36 const oldIntegrations = await db.query.githubIntegrationTable.findMany(); 37 38 if (oldIntegrations.length === 0) { 39 console.log("No old integrations to migrate"); 40 await dropOldTable(); 41 return; 42 } 43 44 let migratedCount = 0; 45 46 for (const old of oldIntegrations) { 47 const existingIntegration = await db.query.integrationTable.findFirst({ 48 where: and( 49 eq(integrationTable.projectId, old.projectId), 50 eq(integrationTable.type, "github"), 51 ), 52 }); 53 54 if (existingIntegration) { 55 continue; 56 } 57 58 await db.insert(integrationTable).values({ 59 projectId: old.projectId, 60 type: "github", 61 config: JSON.stringify({ 62 repositoryOwner: old.repositoryOwner, 63 repositoryName: old.repositoryName, 64 installationId: old.installationId, 65 ...defaultGitHubConfig, 66 }), 67 isActive: old.isActive ?? true, 68 createdAt: old.createdAt, 69 updatedAt: old.updatedAt, 70 }); 71 72 migratedCount++; 73 } 74 75 console.log(`✓ Migrated ${migratedCount} integrations`); 76 77 await migrateTaskLinks(); 78 79 await dropOldTable(); 80 81 console.log("✅ GitHub integration migration complete!"); 82 } catch (error) { 83 console.error("Failed to migrate GitHub integration:", error); 84 throw error; 85 } 86} 87 88async function migrateTaskLinks() { 89 console.log("🔄 Migrating task links from descriptions..."); 90 91 const tasks = await db.query.taskTable.findMany(); 92 93 let linksCreated = 0; 94 let descriptionsUpdated = 0; 95 96 for (const task of tasks) { 97 if (!task.description) continue; 98 99 const linkMatch = task.description.match( 100 /(Linked to|Created from) GitHub issue: (https:\/\/github\.com\/([^/]+)\/([^/]+)\/issues\/(\d+))/, 101 ); 102 103 if (!linkMatch) continue; 104 105 const linkType = linkMatch[1]; 106 const url = linkMatch[2]; 107 const owner = linkMatch[3]; 108 const repo = linkMatch[4]; 109 const issueNumber = linkMatch[5]; 110 111 if (!url || !owner || !repo || !issueNumber) continue; 112 113 const integration = await db.query.integrationTable.findFirst({ 114 where: and( 115 eq(integrationTable.projectId, task.projectId), 116 eq(integrationTable.type, "github"), 117 ), 118 }); 119 120 if (!integration) continue; 121 122 const config = JSON.parse(integration.config); 123 if (config.repositoryOwner !== owner || config.repositoryName !== repo) { 124 continue; 125 } 126 127 const existingLink = await db.query.externalLinkTable.findFirst({ 128 where: and( 129 eq(externalLinkTable.taskId, task.id), 130 eq(externalLinkTable.integrationId, integration.id), 131 eq(externalLinkTable.resourceType, "issue"), 132 ), 133 }); 134 135 if (!existingLink) { 136 await db.insert(externalLinkTable).values({ 137 taskId: task.id, 138 integrationId: integration.id, 139 resourceType: "issue", 140 externalId: issueNumber, 141 url: url, 142 title: null, 143 metadata: JSON.stringify({ 144 migrated: true, 145 createdFrom: linkType === "Created from" ? "github" : "kaneo", 146 }), 147 }); 148 linksCreated++; 149 } 150 151 const cleanedDescription = task.description 152 .replace(/\n\n---\n\n\*.*GitHub issue:.*\*/g, "") 153 .replace(/\n---\n<sub>Task:.*<\/sub>/g, "") 154 .trim(); 155 156 if (cleanedDescription !== task.description) { 157 await db 158 .update(taskTable) 159 .set({ description: cleanedDescription || null }) 160 .where(eq(taskTable.id, task.id)); 161 descriptionsUpdated++; 162 } 163 } 164 165 console.log(`✓ Created ${linksCreated} external links`); 166 console.log(`✓ Cleaned ${descriptionsUpdated} task descriptions`); 167} 168 169async function dropOldTable() { 170 console.log("🗑️ Dropping old github_integration table..."); 171 await db.execute(sql`DROP TABLE IF EXISTS github_integration CASCADE`); 172 console.log("✓ Dropped github_integration table"); 173}