kaneo (minimalist kanban) fork to experiment adding a tangled integration github.com/usekaneo/kaneo
at main 182 lines 5.6 kB view raw
1import { HTTPException } from "hono/http-exception"; 2import { getGithubApp } from "../../plugins/github/utils/github-app"; 3 4async function verifyGithubInstallation({ 5 repositoryOwner, 6 repositoryName, 7}: { 8 repositoryOwner: string; 9 repositoryName: string; 10}) { 11 const githubApp = getGithubApp(); 12 13 try { 14 if (!githubApp) { 15 throw new HTTPException(500, { 16 message: "GitHub app not configured", 17 }); 18 } 19 20 const { data: installation } = 21 await githubApp.octokit.rest.apps.getRepoInstallation({ 22 owner: repositoryOwner, 23 repo: repositoryName, 24 }); 25 26 const octokit = await githubApp.getInstallationOctokit(installation.id); 27 const { data: repo } = await octokit.rest.repos.get({ 28 owner: repositoryOwner, 29 repo: repositoryName, 30 }); 31 32 const requiredPermissions = ["issues"]; 33 const hasRequiredPermissions = checkPermissions( 34 installation.permissions, 35 requiredPermissions, 36 ); 37 const missingPermissions = getMissingPermissions( 38 installation.permissions, 39 requiredPermissions, 40 ); 41 42 if (!hasRequiredPermissions) { 43 return { 44 isInstalled: true, 45 installationId: installation.id, 46 repositoryExists: true, 47 repositoryPrivate: repo.private, 48 permissions: installation.permissions, 49 hasRequiredPermissions: false, 50 missingPermissions, 51 message: `GitHub App is installed but missing required permissions: ${missingPermissions.join(", ")}`, 52 settingsUrl: `https://github.com/settings/installations/${installation.id}`, 53 installationUrl: process.env.GITHUB_APP_NAME 54 ? `https://github.com/apps/${process.env.GITHUB_APP_NAME}/installations/new/permissions?target_id=${repo.id}` 55 : undefined, 56 }; 57 } 58 59 return { 60 isInstalled: true, 61 installationId: installation.id, 62 repositoryExists: true, 63 repositoryPrivate: repo.private, 64 permissions: installation.permissions, 65 hasRequiredPermissions: true, 66 missingPermissions: [], 67 installationUrl: `https://github.com/apps/${process.env.GITHUB_APP_NAME}/installations/new/permissions?target_id=${repo.id}`, 68 message: 69 "GitHub App is properly installed and has all required permissions", 70 settingsUrl: `https://github.com/settings/installations/${installation.id}`, 71 }; 72 } catch (error) { 73 const githubError = error as { status?: number; message?: string }; 74 75 if (githubError.status === 404) { 76 try { 77 if (!githubApp) { 78 throw new HTTPException(500, { 79 message: "GitHub app not configured", 80 }); 81 } 82 83 await githubApp.octokit.rest.repos.get({ 84 owner: repositoryOwner, 85 repo: repositoryName, 86 }); 87 88 const repoId = await getRepositoryId(repositoryOwner, repositoryName); 89 90 return { 91 isInstalled: false, 92 installationId: null, 93 repositoryExists: true, 94 repositoryPrivate: null, 95 permissions: null, 96 hasRequiredPermissions: false, 97 missingPermissions: [], 98 message: "Repository exists but GitHub App is not installed", 99 installationUrl: process.env.GITHUB_APP_NAME 100 ? `https://github.com/apps/${process.env.GITHUB_APP_NAME}/installations/new/permissions?target_id=${repoId}` 101 : undefined, 102 settingsUrl: process.env.GITHUB_APP_NAME 103 ? `https://github.com/apps/${process.env.GITHUB_APP_NAME}` 104 : undefined, 105 }; 106 } catch (repoError) { 107 const repoGithubError = repoError as { 108 status?: number; 109 message?: string; 110 }; 111 112 if (repoGithubError.status === 404) { 113 return { 114 isInstalled: false, 115 installationId: null, 116 repositoryExists: false, 117 repositoryPrivate: null, 118 permissions: null, 119 hasRequiredPermissions: false, 120 missingPermissions: [], 121 settingsUrl: undefined, 122 installationUrl: undefined, 123 message: "Repository does not exist or is not accessible", 124 }; 125 } 126 throw new HTTPException(500, { 127 message: `Failed to verify GitHub installation: ${repoGithubError.status || repoGithubError.message || "Unknown error"}`, 128 }); 129 } 130 } 131 132 throw new HTTPException(500, { 133 message: `Failed to verify GitHub installation: ${githubError.message || "Unknown error"}`, 134 }); 135 } 136} 137 138function checkPermissions( 139 permissions: Record<string, string> | undefined, 140 required: string[], 141): boolean { 142 if (!permissions) return false; 143 144 return required.every((perm) => { 145 const permissionLevel = permissions[perm]; 146 return permissionLevel === "write" || permissionLevel === "admin"; 147 }); 148} 149 150function getMissingPermissions( 151 permissions: Record<string, string> | undefined, 152 required: string[], 153): string[] { 154 if (!permissions) return required; 155 156 return required.filter((perm) => { 157 const permissionLevel = permissions[perm]; 158 return permissionLevel !== "write" && permissionLevel !== "admin"; 159 }); 160} 161 162async function getRepositoryId(owner: string, repo: string): Promise<number> { 163 const githubApp = getGithubApp(); 164 165 try { 166 if (!githubApp) { 167 throw new HTTPException(500, { 168 message: "GitHub app not configured", 169 }); 170 } 171 172 const { data } = await githubApp.octokit.rest.repos.get({ 173 owner, 174 repo, 175 }); 176 return data.id; 177 } catch { 178 return 0; 179 } 180} 181 182export default verifyGithubInstallation;