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 176 lines 4.1 kB view raw
1import { eq } from "drizzle-orm"; 2import db from "../../../database"; 3import { externalLinkTable } from "../../../database/schema"; 4import type { GiteaConfig } from "../config"; 5import { createGiteaClient } from "./gitea-api"; 6 7const namedColorToHex: Record<string, string> = { 8 red: "EF4444", 9 orange: "F97316", 10 amber: "F59E0B", 11 yellow: "EAB308", 12 lime: "84CC16", 13 green: "22C55E", 14 emerald: "10B981", 15 teal: "14B8A6", 16 cyan: "06B6D4", 17 sky: "0EA5E9", 18 blue: "3B82F6", 19 indigo: "6366F1", 20 violet: "8B5CF6", 21 purple: "A855F7", 22 fuchsia: "D946EF", 23 pink: "EC4899", 24 rose: "F43F5E", 25 gray: "6B7280", 26 slate: "64748B", 27 zinc: "71717A", 28 neutral: "737373", 29 stone: "78716C", 30}; 31 32function toHexColor(color: string): string { 33 const lower = color.toLowerCase().replace(/^#/, ""); 34 if (namedColorToHex[lower]) { 35 return namedColorToHex[lower]; 36 } 37 if (/^[0-9a-f]{6}$/i.test(lower)) { 38 return lower; 39 } 40 if (/^[0-9a-f]{3}$/i.test(lower)) { 41 const [r, g, b] = lower.split(""); 42 return `${r}${r}${g}${g}${b}${b}`; 43 } 44 return "6B7280"; 45} 46 47async function getGiteaIssueContext(taskId: string) { 48 const externalLinks = await db.query.externalLinkTable.findMany({ 49 where: eq(externalLinkTable.taskId, taskId), 50 with: { 51 integration: true, 52 }, 53 }); 54 55 const externalLink = externalLinks.find( 56 (link) => 57 link.resourceType === "issue" && link.integration?.type === "gitea", 58 ); 59 60 if (!externalLink) { 61 return null; 62 } 63 64 const integration = externalLink.integration; 65 if (!integration) { 66 return null; 67 } 68 69 let config: GiteaConfig; 70 try { 71 config = JSON.parse(integration.config) as GiteaConfig; 72 } catch { 73 return null; 74 } 75 76 if (!config.accessToken || !config.baseUrl) { 77 return null; 78 } 79 80 const client = createGiteaClient(config); 81 const issueNumber = Number.parseInt(externalLink.externalId, 10); 82 if (Number.isNaN(issueNumber)) { 83 console.warn("Invalid Gitea issue externalId for label sync", { 84 externalLinkId: externalLink.id, 85 externalId: externalLink.externalId, 86 taskId, 87 }); 88 return null; 89 } 90 91 return { 92 client, 93 config, 94 issueNumber, 95 }; 96} 97 98export async function syncLabelToGitea( 99 taskId: string, 100 labelName: string, 101 labelColor: string, 102) { 103 const ctx = await getGiteaIssueContext(taskId); 104 if (!ctx) return; 105 106 const { client, config, issueNumber } = ctx; 107 const color = toHexColor(labelColor); 108 109 const labels = await client.listLabels( 110 config.repositoryOwner, 111 config.repositoryName, 112 ); 113 let label = labels.find((l) => l.name === labelName); 114 115 if (!label) { 116 try { 117 label = await client.createLabel( 118 config.repositoryOwner, 119 config.repositoryName, 120 labelName, 121 color, 122 ); 123 } catch (error) { 124 console.error(`Failed to create label "${labelName}" in Gitea:`, error); 125 return; 126 } 127 } 128 129 try { 130 const issue = await client.getIssue( 131 config.repositoryOwner, 132 config.repositoryName, 133 issueNumber, 134 ); 135 const existingIds = (issue.labels ?? []).map((l) => l.id); 136 if (existingIds.includes(label.id)) { 137 return; 138 } 139 await client.addLabelsToIssue( 140 config.repositoryOwner, 141 config.repositoryName, 142 issueNumber, 143 [label.id], 144 ); 145 } catch (error) { 146 console.error(`Failed to add label "${labelName}" to Gitea issue:`, error); 147 } 148} 149 150export async function removeLabelFromGitea(taskId: string, labelName: string) { 151 const ctx = await getGiteaIssueContext(taskId); 152 if (!ctx) return; 153 154 const { client, config, issueNumber } = ctx; 155 156 const labels = await client.listLabels( 157 config.repositoryOwner, 158 config.repositoryName, 159 ); 160 const label = labels.find((l) => l.name === labelName); 161 if (!label) return; 162 163 try { 164 await client.removeLabelFromIssue( 165 config.repositoryOwner, 166 config.repositoryName, 167 issueNumber, 168 label.id, 169 ); 170 } catch (error) { 171 console.error( 172 `Failed to remove label "${labelName}" from Gitea issue:`, 173 error, 174 ); 175 } 176}