kaneo (minimalist kanban) fork to experiment adding a tangled integration
github.com/usekaneo/kaneo
1import { and, eq } from "drizzle-orm";
2import db from "../../../database";
3import { externalLinkTable } from "../../../database/schema";
4import { updateExternalLink } from "../../github/services/link-manager";
5import {
6 findTaskById,
7 updateTaskStatus,
8} from "../../github/services/task-service";
9import type { GiteaConfig } from "../config";
10import {
11 findAllIntegrationsByGiteaRepo,
12 repoOwnerLogin,
13} from "../services/integration-lookup";
14import { resolveTargetStatus } from "../utils/resolve-column";
15import { baseUrlFromRepositoryHtmlUrl } from "../utils/webhook-repo";
16
17type PRClosedPayload = {
18 action: string;
19 pull_request: {
20 number: number;
21 title: string;
22 html_url: string;
23 state: string;
24 merged: boolean;
25 merged_at: string | null;
26 head: {
27 ref: string;
28 };
29 };
30 repository: {
31 owner: { login?: string; username?: string };
32 name: string;
33 html_url: string;
34 };
35};
36
37export async function handleGiteaPullRequestClosed(payload: PRClosedPayload) {
38 const { pull_request, repository } = payload;
39
40 const baseUrl = baseUrlFromRepositoryHtmlUrl(repository.html_url);
41 if (!baseUrl) return;
42
43 const owner = repoOwnerLogin(repository);
44 const integrations = await findAllIntegrationsByGiteaRepo(
45 baseUrl,
46 owner,
47 repository.name,
48 );
49
50 for (const integration of integrations) {
51 const config = JSON.parse(integration.config) as GiteaConfig;
52
53 const externalLink = await db.query.externalLinkTable.findFirst({
54 where: and(
55 eq(externalLinkTable.integrationId, integration.id),
56 eq(externalLinkTable.resourceType, "pull_request"),
57 eq(externalLinkTable.externalId, pull_request.number.toString()),
58 ),
59 });
60
61 if (!externalLink) {
62 continue;
63 }
64
65 const task = await findTaskById(externalLink.taskId);
66
67 if (!task) {
68 continue;
69 }
70
71 const existingMetadata = externalLink.metadata
72 ? JSON.parse(externalLink.metadata)
73 : {};
74
75 await updateExternalLink(externalLink.id, {
76 metadata: {
77 ...existingMetadata,
78 state: "closed",
79 merged: pull_request.merged,
80 mergedAt: pull_request.merged_at,
81 },
82 });
83
84 if (pull_request.merged) {
85 const allTaskPRs = await db.query.externalLinkTable.findMany({
86 where: and(
87 eq(externalLinkTable.taskId, task.id),
88 eq(externalLinkTable.resourceType, "pull_request"),
89 ),
90 });
91
92 const hasOpenPRs = allTaskPRs.some((pr) => {
93 if (pr.id === externalLink.id) return false;
94 const metadata = pr.metadata ? JSON.parse(pr.metadata) : {};
95 return metadata.state === "open";
96 });
97
98 if (!hasOpenPRs) {
99 const targetStatus = await resolveTargetStatus(
100 integration.projectId,
101 "pr_merged",
102 config.statusTransitions?.onPRMerge || "done",
103 );
104 await updateTaskStatus(task.id, targetStatus);
105 }
106 }
107
108 return;
109 }
110}