kaneo (minimalist kanban) fork to experiment adding a tangled integration
github.com/usekaneo/kaneo
1import { eq } from "drizzle-orm";
2import db from "../../../database";
3import { taskTable } from "../../../database/schema";
4import {
5 findExternalLink,
6 updateExternalLink,
7} from "../../github/services/link-manager";
8import { formatTaskDescriptionFromIssue } from "../../github/utils/format";
9import {
10 findAllIntegrationsByGiteaRepo,
11 repoOwnerLogin,
12} from "../services/integration-lookup";
13import { baseUrlFromRepositoryHtmlUrl } from "../utils/webhook-repo";
14
15type IssueEditedPayload = {
16 action: string;
17 issue: {
18 number: number;
19 title: string;
20 body: string | null;
21 html_url: string;
22 };
23 changes?: {
24 title?: { from: string };
25 body?: { from: string };
26 };
27 repository: {
28 owner: { login?: string; username?: string };
29 name: string;
30 html_url: string;
31 };
32};
33
34export async function handleGiteaIssueEdited(payload: IssueEditedPayload) {
35 const { issue, repository, changes } = payload;
36
37 if (!changes?.title && !changes?.body) {
38 return;
39 }
40
41 const baseUrl = baseUrlFromRepositoryHtmlUrl(repository.html_url);
42 if (!baseUrl) return;
43
44 const owner = repoOwnerLogin(repository);
45 const integrations = await findAllIntegrationsByGiteaRepo(
46 baseUrl,
47 owner,
48 repository.name,
49 );
50
51 for (const integration of integrations) {
52 const externalLink = await findExternalLink(
53 integration.id,
54 "issue",
55 issue.number.toString(),
56 );
57
58 if (!externalLink) {
59 continue;
60 }
61
62 const task = await db.query.taskTable.findFirst({
63 where: eq(taskTable.id, externalLink.taskId),
64 });
65
66 if (!task) {
67 continue;
68 }
69
70 const metadata = externalLink.metadata
71 ? JSON.parse(externalLink.metadata)
72 : {};
73
74 const updateData: Record<string, unknown> = {};
75 const updatedMetadata = { ...metadata };
76
77 if (!updatedMetadata.lastSync) {
78 updatedMetadata.lastSync = {};
79 }
80
81 if (changes.title) {
82 const lastTitleSync = metadata.lastSync?.title;
83
84 let shouldUpdateTitle = true;
85
86 if (lastTitleSync) {
87 if (
88 lastTitleSync.value === issue.title &&
89 lastTitleSync.source === "kaneo"
90 ) {
91 shouldUpdateTitle = false;
92 }
93
94 const timeSinceLastSync =
95 Date.now() - new Date(lastTitleSync.timestamp).getTime();
96 if (timeSinceLastSync < 2000 && shouldUpdateTitle) {
97 shouldUpdateTitle = false;
98 }
99 }
100
101 if (shouldUpdateTitle) {
102 updateData.title = issue.title;
103 updatedMetadata.lastSync.title = {
104 timestamp: new Date().toISOString(),
105 source: "gitea",
106 value: issue.title,
107 };
108 }
109 }
110
111 if (changes.body) {
112 const lastDescSync = metadata.lastSync?.description;
113 const formattedDescription = formatTaskDescriptionFromIssue(issue.body);
114
115 let shouldUpdateDescription = true;
116
117 if (lastDescSync) {
118 if (
119 lastDescSync.value === formattedDescription &&
120 lastDescSync.source === "kaneo"
121 ) {
122 shouldUpdateDescription = false;
123 }
124
125 const timeSinceLastSync =
126 Date.now() - new Date(lastDescSync.timestamp).getTime();
127 if (timeSinceLastSync < 2000 && shouldUpdateDescription) {
128 shouldUpdateDescription = false;
129 }
130 }
131
132 if (shouldUpdateDescription) {
133 updateData.description = formattedDescription;
134 updatedMetadata.lastSync.description = {
135 timestamp: new Date().toISOString(),
136 source: "gitea",
137 value: formattedDescription,
138 };
139 }
140 }
141
142 if (Object.keys(updateData).length > 0) {
143 await db
144 .update(taskTable)
145 .set(updateData)
146 .where(eq(taskTable.id, task.id));
147
148 await updateExternalLink(externalLink.id, {
149 title: issue.title,
150 metadata: updatedMetadata,
151 });
152 }
153
154 return;
155 }
156}