kaneo (minimalist kanban) fork to experiment adding a tangled integration
github.com/usekaneo/kaneo
1import { and, eq } from "drizzle-orm";
2import { HTTPException } from "hono/http-exception";
3import db from "../../database";
4import { integrationTable, projectTable } from "../../database/schema";
5import { defaultGitHubConfig } from "../../plugins/github/config";
6import { getGithubApp } from "../../plugins/github/utils/github-app";
7
8async function createGithubIntegration({
9 projectId,
10 repositoryOwner,
11 repositoryName,
12}: {
13 projectId: string;
14 repositoryOwner: string;
15 repositoryName: string;
16}) {
17 const githubApp = getGithubApp();
18
19 if (!githubApp) {
20 throw new HTTPException(500, {
21 message: "GitHub app not configured",
22 });
23 }
24
25 const project = await db.query.projectTable.findFirst({
26 where: eq(projectTable.id, projectId),
27 });
28
29 if (!project) {
30 throw new HTTPException(404, { message: "Project not found" });
31 }
32
33 const allGitHubIntegrations = await db.query.integrationTable.findMany({
34 where: eq(integrationTable.type, "github"),
35 });
36
37 for (const integration of allGitHubIntegrations) {
38 if (integration.projectId === projectId) {
39 continue;
40 }
41
42 try {
43 const config = JSON.parse(integration.config);
44 if (
45 config.repositoryOwner === repositoryOwner &&
46 config.repositoryName === repositoryName
47 ) {
48 throw new HTTPException(409, {
49 message: `Repository ${repositoryOwner}/${repositoryName} is already linked to another project`,
50 });
51 }
52 } catch (error) {
53 if (error instanceof HTTPException) {
54 throw error;
55 }
56 }
57 }
58
59 let installationId: number | null = null;
60 try {
61 const { data: installation } =
62 await githubApp.octokit.rest.apps.getRepoInstallation({
63 owner: repositoryOwner,
64 repo: repositoryName,
65 });
66 installationId = installation.id;
67 } catch (error) {
68 console.warn("Could not get installation ID for repository:", error);
69 }
70
71 const existingIntegration = await db.query.integrationTable.findFirst({
72 where: and(
73 eq(integrationTable.projectId, projectId),
74 eq(integrationTable.type, "github"),
75 ),
76 });
77
78 const config = {
79 repositoryOwner,
80 repositoryName,
81 installationId,
82 ...defaultGitHubConfig,
83 };
84
85 if (existingIntegration) {
86 const [updatedIntegration] = await db
87 .update(integrationTable)
88 .set({
89 config: JSON.stringify(config),
90 isActive: true,
91 updatedAt: new Date(),
92 })
93 .where(
94 and(
95 eq(integrationTable.projectId, projectId),
96 eq(integrationTable.type, "github"),
97 ),
98 )
99 .returning();
100
101 return {
102 id: updatedIntegration?.id,
103 projectId: updatedIntegration?.projectId,
104 repositoryOwner,
105 repositoryName,
106 installationId,
107 isActive: updatedIntegration?.isActive,
108 createdAt: updatedIntegration?.createdAt,
109 updatedAt: updatedIntegration?.updatedAt,
110 };
111 }
112
113 const [newIntegration] = await db
114 .insert(integrationTable)
115 .values({
116 projectId,
117 type: "github",
118 config: JSON.stringify(config),
119 isActive: true,
120 })
121 .returning();
122
123 return {
124 id: newIntegration?.id,
125 projectId: newIntegration?.projectId,
126 repositoryOwner,
127 repositoryName,
128 installationId,
129 isActive: newIntegration?.isActive,
130 createdAt: newIntegration?.createdAt,
131 updatedAt: newIntegration?.updatedAt,
132 };
133}
134
135export default createGithubIntegration;