codeflash-internal/js/cf-api/index.ts
2023-12-07 15:06:53 -08:00

176 lines
5.1 KiB
TypeScript

import { createNodeMiddleware } from "octokit"
import express from "express"
import suggester from "code-suggester"
import { type FileDiffContent } from "code-suggester/build/src/types"
import { determineValidHunks, objectToMap } from "./utils"
import { createPullRequestFromDiffContents } from "./create-pr-from-diffcontents"
import { githubApp, webhookApiPath } from "./github-app"
import functions from "@codeflash-ai/common/token-functions"
import dotenv from "dotenv"
import { userNickname } from "./auth0"
const userForAPIKey = functions.userForAPIKey
const port = process.env.PORT || 3001
if (process.env.NODE_ENV !== "production") {
dotenv.config({ path: ".env.local" })
}
const appExpress = express()
// Mount the github webhook middleware onto the express application
appExpress.use(createNodeMiddleware(githubApp.webhooks, { path: webhookApiPath }))
appExpress.use(express.json())
// Middleware to check for valid API key
appExpress.use(async (req, res, next) => {
const authHeader = req.headers.authorization
if (!authHeader) {
return res.status(401).send("Authorization header is missing")
}
const apiKey = authHeader.replace(/^Bearer\s+/, "")
const userId = await userForAPIKey(apiKey)
if (userId == null) {
return res.status(403).send("Invalid API key")
}
req.userId = userId
next()
})
appExpress.get("/healthcheck", async (req, res) => {
return res.status(200).send("OK")
})
appExpress.post("/api/suggest-pr-changes", async (req, res) => {
try {
const { owner, repo, pullNumber, diffContents, prComment } = req.body
const userId = req.userId
if (!repo || !owner || !pullNumber) {
return res.status(400).send("Missing required fields")
}
const nickname = await userNickname(userId)
if (!nickname) {
return res.status(401).send("Unauthorized") // Error getting user nickname
}
const repoInstallation = await githubApp.octokit.rest.apps.getRepoInstallation({
owner,
repo,
})
const installationId = repoInstallation.data.id
const installationOctokit = await githubApp.getInstallationOctokit(installationId)
try {
// Check if the user is a collaborator on the repository
const response = await installationOctokit.rest.repos.checkCollaborator({
owner,
repo,
username: nickname,
})
// If the request is successful, the user is a collaborator and we can continue
if (response.status == 204) {
console.log(`${nickname} is a collaborator on ${owner}/${repo}`)
}
} catch (error) {
if (error.status === 404) {
return res.status(401).send("Unauthorized") // User is not a collaborator
}
// Handle other potential errors
return res.status(500).send(`Error`) // Error checking collaborator status
}
// Suggest changes
const diffContentsMap: Map<string, FileDiffContent> = objectToMap(diffContents)
const { validHunks, invalidHunks } = await determineValidHunks(
installationOctokit.rest,
{ owner, repo },
pullNumber,
100,
diffContentsMap,
)
if (invalidHunks.size > 0) {
// we can't suggest all of the hunks for this PR, because some of them are invalid (out of scope for this PR).
// so instead, let's make a new branch, commit the changes,
// and make a new PR from that branch onto the PR's branch
const newPrData = await createPullRequestFromDiffContents(
installationOctokit,
owner,
repo,
pullNumber,
diffContentsMap,
)
// Add explanation comment
const result = await installationOctokit.rest.issues.createComment({
owner,
repo,
issue_number: newPrData.data.number,
body: prComment,
})
// Respond with the new PR details
res.json(newPrData.data.number)
} else {
// all suggestions are valid and can be made on this PR.
// let's make the suggestions
const reviewNumber = await suggester.reviewPullRequest(
installationOctokit.rest,
diffContentsMap,
{
repo,
owner,
pullNumber,
pageSize: 100,
},
)
// Add explanation comment
const result = await installationOctokit.rest.issues.createComment({
owner,
repo,
issue_number: pullNumber,
body: prComment,
})
res.json(reviewNumber)
}
} catch (error) {
res.status(500).send(`Error creating pull request: ${error.message}`)
}
})
// WORKS without express.json() vvvvv
// const router = express.Router();
// // router.use(express.json());
// router.use(createNodeMiddleware(ghApp.webhooks, { path: "/api/github/webhooks" }));
//
// const app = express();
// app.use('/', router);
//
// app.listen(port)
// also works vvvvv
// const server = http.createServer(createNodeMiddleware(ghApp.webhooks));
// server.listen()
// \/\/\/\/\//\/\
// const server = http.createServer(appExpress.);
// Start the server
appExpress.listen(port, () => {
console.log(
`Server is listening on ${port} for CF API requests. Github App webhook events mounted at ${webhookApiPath}`,
)
console.log("Press Ctrl + C to quit.")
})