Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,22 @@ jobs:
node-version: 20

- name: Install dependencies
run: cd server && npm install
run: cd Server && npm install

- name: Generate Prisma client
run: cd server && npx prisma generate
run: cd Server && npx prisma generate

- name: Update tsconfig lib for ES2022
run: |
node -e "
const fs = require('fs');
const path = 'Server/tsconfig.json';
const tsconfig = JSON.parse(fs.readFileSync(path, 'utf8'));
tsconfig.compilerOptions = tsconfig.compilerOptions || {};
tsconfig.compilerOptions.lib = ['ES2022', 'DOM'];
tsconfig.compilerOptions.target = 'ES2022';
fs.writeFileSync(path, JSON.stringify(tsconfig, null, 2));
"

- name: Build TypeScript
run: cd server && npx tsc --noEmit
run: cd Server && npx tsc --noEmit
5 changes: 4 additions & 1 deletion .reviewrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,8 @@
],
"performance": [
"avoid nested loops over large arrays"
]
],
"ci": {
"install_directory": "backend"
}
}
2 changes: 0 additions & 2 deletions Server/app/api-service/controllers/dashboard.controllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ import { Request, Response } from "express";
import { db } from "../../../package/db/prisma.js";

async function getDbUserId(req: Request): Promise<string | null> {
// console.log("user Id ------> " , req)
const githubId = (req as any).githubUser?.id;
console.log(githubId)
if (!githubId) return null;
const user = await db.user.findUnique({ where: { githubId } });
return user?.id ?? null;
Expand Down
29 changes: 29 additions & 0 deletions Server/app/api-service/controllers/deployment.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Request, Response } from "express";
import { db } from "../../../package/db/prisma.js";
import { applyDeploymentFix } from "../../worker-service/services/deployment.service.js";

export async function getDeployments(req: Request<{ repoId: string }>, res: Response) {
const { repoId } = req.params;

const deployments = await db.deployment.findMany({
where: { repositoryId: repoId },
orderBy: { createdAt: "desc" },
include: { fixes: true },
});

res.json(deployments);
}

export async function fixDeployment(req: Request<{ id: string }>, res: Response) {
const { id } = req.params;

const deployment = await db.deployment.findUnique({ where: { id } });

if (deployment?.status !== "failed") {
res.status(400).json({ error: "deployment is not failed" });
return;
}

const result = await applyDeploymentFix(id);
res.json(result);
}
3 changes: 0 additions & 3 deletions Server/app/api-service/controllers/repo.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,14 @@ import { Request, Response } from "express";
import { db } from "../../../package/db/prisma.js";

async function getDbUserId(req: Request): Promise<string | null> {
console.log(req)
const githubId = (req as any).githubUser?.id;
console.log("from user Id", githubId)
if (!githubId) return null;
const user = await db.user.findUnique({ where: { githubId } });
return user?.id ?? null;
}

export async function allRepos(req: Request, res: Response) {
const userId = await getDbUserId(req);
console.log("from get all repo", userId)
if (!userId) { res.status(401).json({ error: "unauthorized" }); return; }

const repos = await db.repository.findMany({
Expand Down
4 changes: 3 additions & 1 deletion Server/app/api-service/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import "dotenv/config";
import express, {Request, Response } from "express";
import express from "express";
import cors from 'cors';

import dashboardRoutes from "./routes/dashboard.routes.js"
import repoRoutes from "./routes/repos.route.js"
import prRoutes from "./routes/pr.routes.js"
import userRoutes from "./routes/user.routes.js"
import deploymentRoutes from "./routes/deployment.routes.js"

const app = express()

Expand All @@ -22,5 +23,6 @@ app.use("/api/dashboard/", dashboardRoutes)
app.use("/api/repo/", repoRoutes)
app.use("/api/pr/", prRoutes)
app.use("/api/user/", userRoutes)
app.use("/api/deployment/", deploymentRoutes)

app.listen(3001, () => console.log("API service listening on port 3001"))
10 changes: 10 additions & 0 deletions Server/app/api-service/routes/deployment.routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Router } from "express";
import { getDeployments, fixDeployment } from "../controllers/deployment.controller.js";
import { requireAuth } from "../auth/auth.js";

const router = Router();

router.get("/get-deployments/:repoId", requireAuth, getDeployments);
router.post("/fix/:id", requireAuth, fixDeployment);

export default router;
1 change: 0 additions & 1 deletion Server/app/webhook-service/middleware/verifySignature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { Request } from "express";

export function verifySignature(req: Request, secret: string): boolean {
const signature = req.headers["x-hub-signature-256"];
console.log("-----------signature is --------- \n", signature)
if (!signature || typeof signature !== "string") {
return false;
}
Expand Down
40 changes: 34 additions & 6 deletions Server/app/webhook-service/routes/github.webhook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,22 +59,19 @@ router.post("/webhook/github", async (req: Request, res: Response) => {
create: { owner, name, installationId, userId: user.id },
});
}

console.log(`installation created: ${payload.repositories?.length ?? 0} repos stored`);
}
}

if (event === "pull_request") {
console.log("i am in")
const payload = req.body as PullRequestEvent;
console.log(payload)
const installationId = payload.installation.id;
const owner = payload.repository.owner.login;
const repo = payload.repository.name;
const prNumber = payload.pull_request.number;
console.log("------------------------------------------ All data i get in terms of repository ------------------------------------------ \n" , payload.repository)

console.log("pull_request action:", payload.action, `${owner}/${repo}#${prNumber}`);

if (payload.action === "opened") {
if (payload.action === "opened" || payload.action === "synchronize") {
const repoOwner = payload.repository.owner;

const user = await db.user.upsert({
Expand Down Expand Up @@ -102,6 +99,37 @@ router.post("/webhook/github", async (req: Request, res: Response) => {
console.log("pushed to queue, queue length:", pushed);
}
}

console.log("running the workflowwwwww")
if (event === "workflow_run") {
const payload = req.body as any;
console.log("payload recieved , here is its id", payload.workflow_run.id)

if (payload.workflow_run?.conclusion === "failure") {
const installationId = payload.installation?.id;
const owner = payload.repository.owner.login;
const repo = payload.repository.name;
const runId = payload.workflow_run.id;
const branch = payload.workflow_run.head_branch;
const prNumber = payload.workflow_run.pull_requests?.[0]?.number ?? null;

await client.lPush(
"deploymentQueue",
JSON.stringify({
provider: "github_actions",
owner,
repo,
prNumber,
branch,
deploymentId: String(runId),
runId,
installationId,
})
);

console.log(`workflow_run failure queued: ${owner}/${repo} run ${runId}`);
}
}

res.sendStatus(200);
});
Expand Down
6 changes: 4 additions & 2 deletions Server/app/worker-service/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import "dotenv/config";
import {workersOn} from "./workers/review.worker.js"
import { workersOn } from "./workers/review.worker.js";
import { deploymentWorkerOn } from "./workers/deployment.worker.js";

workersOn()
workersOn();
deploymentWorkerOn();
26 changes: 26 additions & 0 deletions Server/app/worker-service/log-fetchers/github.logs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { getOctokit } from "../../../package/github/client.js";
import AdmZip from "adm-zip";

export async function fetchGithubActionLogs(installationId: number, owner: string, repo: string, runId: number) {
const octokit = await getOctokit(installationId);

const response = await octokit.rest.actions.downloadWorkflowRunLogs({
owner,
repo,
run_id: runId,
});

const buffer = Buffer.from(response.data as ArrayBuffer);
const zip = new AdmZip(buffer);
const entries = zip.getEntries();

let logs = "";
for (const entry of entries) {
if (!entry.isDirectory) {
logs += `\n--- ${entry.entryName} ---\n`;
logs += zip.readAsText(entry);
}
}

return logs.slice(0, 20000);
}
7 changes: 7 additions & 0 deletions Server/app/worker-service/processors/deployment.processor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { runDeploymentAnalysis, DeploymentJobData } from "../services/deployment.service.js";

export async function processDeploymentJob(data: DeploymentJobData) {
console.log(`Processing deployment analysis: ${data.owner}/${data.repo} — ${data.provider}`);
const result = await runDeploymentAnalysis(data);
return result;
}
146 changes: 146 additions & 0 deletions Server/app/worker-service/services/deployment.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { db } from "../../../package/db/prisma.js";
import { analyzeDeploymentLogs, generateDeploymentFix } from "../../../package/ai/deployment.js";
import { fetchGithubActionLogs } from "../log-fetchers/github.logs.js";
import { getOctokit } from "../../../package/github/client.js";

export type DeploymentJobData = {
provider: string;
owner: string;
repo: string;
prNumber?: number;
branch?: string;
deploymentId: string;
runId?: number;
installationId?: number;
};

export async function runDeploymentAnalysis(data: DeploymentJobData) {
const { provider, owner, repo, prNumber, branch, deploymentId } = data;

const repository = await db.repository.findUnique({
where: { owner_name: { owner, name: repo } },
});

if (!repository) {
console.log(`repo ${owner}/${repo} not found — skipping deployment analysis`);
return;
}

let logs = "";

if (provider === "github_actions" && data.runId && data.installationId) {
logs = await fetchGithubActionLogs(data.installationId, owner, repo, data.runId);
}

const analysis = await analyzeDeploymentLogs(logs, provider);

const saved = await db.deployment.create({
data: {
repositoryId: repository.id,
provider,
status: "failed",
deploymentId,
prNumber: prNumber ?? null,
branch: branch ?? null,
cause: analysis?.cause ?? null,
fix: analysis?.fix ?? null,
rawLogs: logs,
},
});

console.log(`deployment analysis saved for ${owner}/${repo} — ${provider}`);
return saved;
}

export async function applyDeploymentFix(deploymentDbId: string) {
const deployment = await db.deployment.findUnique({
where: { id: deploymentDbId },
include: { repository: true },
});

if (!deployment || !deployment.fix || !deployment.cause) {
throw new Error("deployment not found or has no fix");
}

const octokit = await getOctokit(deployment.repository.installationId);

const savedFix = await db.deploymentFix.create({
data: {
deploymentId: deploymentDbId,
status: "pending",
},
});

try {
const branch = deployment.branch ?? "main";

const branchRef = await octokit.rest.repos.getBranch({
owner: deployment.repository.owner,
repo: deployment.repository.name,
branch,
});

const tree = await octokit.rest.git.getTree({
owner: deployment.repository.owner,
repo: deployment.repository.name,
tree_sha: branchRef.data.commit.sha,
recursive: "1",
});

const filesToFix = tree.data.tree.filter((f) => {
if (!f.path || !f.type) return false;
if (f.type !== "blob") return false;
const ext = [".ts", ".tsx", ".js", ".jsx", ".py", ".go", ".json", ".yaml", ".yml"];
return ext.some((e) => f.path!.endsWith(e));
});

let commitSha = null;
let totalDiff = "";

for (const file of filesToFix.slice(0, 5)) {
const fileRes = await octokit.rest.repos.getContent({
owner: deployment.repository.owner,
repo: deployment.repository.name,
path: file.path!,
ref: branch,
});

const fileData = fileRes.data as { content: string; sha: string };
const currentContent = Buffer.from(fileData.content, "base64").toString("utf-8");

const result = await generateDeploymentFix(currentContent, deployment.cause!, deployment.fix!, file.path!);

if (!result || !result.fixedContent || result.fixedContent === currentContent) continue;

const updateRes = await octokit.rest.repos.createOrUpdateFileContents({
owner: deployment.repository.owner,
repo: deployment.repository.name,
path: file.path!,
message: `fix: resolve deployment failure — ${deployment.cause!.slice(0, 60)}`,
content: Buffer.from(result.fixedContent).toString("base64"),
sha: fileData.sha,
branch,
});

commitSha = updateRes.data.commit.sha ?? null;
totalDiff += `\n--- ${file.path} ---\n${result.explanation}\n`;
}

await db.deploymentFix.update({
where: { id: savedFix.id },
data: {
status: commitSha ? "applied" : "failed",
commitSha,
diff: totalDiff || null,
},
});

return { commitSha, diff: totalDiff };
} catch (err) {
await db.deploymentFix.update({
where: { id: savedFix.id },
data: { status: "failed" },
});
throw err;
}
}
Loading
Loading