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