import { userNickname } from "../auth0-mgmt.js" import { getInstallationOctokitByOwner, isUserCollaborator } from "../github/github-utils.js" import { githubApp } from "../github/github-app.js" import { Request, Response } from "express" import { AuthorizedUserReq } from "../types.js" import { logger } from "../utils/logger.js" import { missingRequiredFields, validationFailure, unauthorized, githubInstallationError, githubNotCollaborator, githubInstallationNotFound, internalServerError, } from "../exceptions/index.js" // Dependencies interface for easier testing export interface IsGitHubAppInstalledDependencies { userNickname: typeof userNickname getInstallationOctokitByOwner: typeof getInstallationOctokitByOwner isUserCollaborator: typeof isUserCollaborator githubApp: typeof githubApp } // Default dependencies let dependencies: IsGitHubAppInstalledDependencies = { userNickname, getInstallationOctokitByOwner, isUserCollaborator, githubApp, } // For testing - allow dependency injection export function setIsGitHubAppInstalledDependencies( deps: Partial, ) { dependencies = { ...dependencies, ...deps } } export function resetIsGitHubAppInstalledDependencies() { dependencies = { userNickname, getInstallationOctokitByOwner, isUserCollaborator, githubApp, } } export async function isGitHubAppInstalled(req: Request, res: Response): Promise { const { owner, repo } = req.query if (!owner || !repo) { throw missingRequiredFields("owner, repo") } const ownerStr = String(owner).trim() const repoStr = String(repo).trim() if (ownerStr === "" || repoStr === "") { throw validationFailure("owner and repo cannot be empty") } try { const nickname = await dependencies.userNickname((req as AuthorizedUserReq).userId) if (nickname == null) { throw unauthorized("") } const installationOctokit = await dependencies.getInstallationOctokitByOwner( dependencies.githubApp, ownerStr, repoStr, (req as AuthorizedUserReq).userId, ) if (installationOctokit instanceof Error) { throw githubInstallationError(installationOctokit.message) } const isCollaborator = await dependencies.isUserCollaborator( installationOctokit, ownerStr, repoStr, nickname, ) if (!isCollaborator) { throw githubNotCollaborator(`${ownerStr}/${repoStr}`) } logger.info("GitHub App installation and collaborator status verified", req, { repo: `${ownerStr}/${repoStr}`, nickname, }) res.json(true) } catch (error: any) { if (error && typeof error === "object" && "getHttpStatus" in error) { throw error } if (error.status === 404) { logger.warn("GitHub App not installed on repository", req, { repo: `${ownerStr}/${repoStr}`, }) throw githubInstallationNotFound(`${ownerStr}/${repoStr}`) } logger.errorWithSentry( "Error checking GitHub App installation or collaborator status:", req, { repo: `${ownerStr}/${repoStr}` }, error, ) throw internalServerError("Error checking GitHub App installation or collaborator status") } }