Skip to content
Open
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
24 changes: 24 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Next.js: debug server",
"runtimeExecutable": "npm",
"runtimeArgs": ["run", "dev"],
"port": 9229,
"console": "integratedTerminal",
"env": {
"NODE_OPTIONS": "--inspect"
}
},
{
"type": "chrome",
"request": "launch",
"name": "Chrome: debug frontend",
"url": "http://localhost:5000",
"webRoot": "${workspaceFolder}"
}
]
}
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,21 @@
"@mui/icons-material": "^7.0.2",
"@mui/joy": "^5.0.0-beta.52",
"@mui/material": "^7.0.2",
"@octokit/auth-app": "^7.2.1",
"@octokit/auth-oauth-app": "^8.1.4",
"@octokit/rest": "^21.1.1",
"bytez.js": "^1.1.2",
"dotenv": "^16.5.0",
"firebase": "^11.6.1",
"firebase-admin": "^13.3.0",
"highlight.js": "^11.11.1",
"langchain": "^0.3.24",
"luxon": "^3.6.1",
"next": "^15.3.1",
"react": "^19.0.0",
"react-dom": "^19.0.0"
"react-dom": "^19.0.0",
"react-markdown": "^10.1.0",
"rehype-highlight": "^7.0.2"
},
"devDependencies": {
"@eslint/eslintrc": "^3.0.0",
Expand Down
93 changes: 93 additions & 0 deletions src/app/api/webhook/github/route.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { Octokit } from "@octokit/rest";
import { createAppAuth } from "@octokit/auth-app";
import {
firestore,
collection,
getDocs,
query,
where,
update,
arrayUnion,
} from "@/service/firebase/firestore";

export async function POST(req) {
try {
const body = await req.json();

if (body.action === "created" || body.action === "deleted") {
return new Response("success", {
status: 200,
});
}

const {
action,
installation,
issue,
repository: { full_name },
} = body;

if (action === "opened" || action === "deleted") {
const usersRef = collection(firestore, "users");

const querySnapshot = await getDocs(
query(usersRef, where("installation_id", "==", installation.id))
);
const users = [];
querySnapshot.forEach((doc) => {
users.push({ id: doc.id, ...doc.data() });
});

const [user] = users;

const { html_url: url, title, body, number: issueNumber } = issue;

if (action === "opened") {
// INSERT MODEL THAT FINDS PROBLEMS AND TAGS AN APPROPRIATE MAINTAINER HERE

const response = "We're on it, @inf3rnus check this out 👀";

// Add comment on GitHub
const octokit = new Octokit({
authStrategy: createAppAuth,
auth: {
appId: process.env.GITHUB_APP_ID,
privateKey: process.env.GITHUB_APP_PRIVATE_KEY.replace(
/\\n/g,
"\n"
),
installationId: installation.id,
},
});

await octokit.issues.createComment({
owner: full_name.split("/")[0],
repo: full_name.split("/")[1],
issue_number: issueNumber,
body: response,
});

const updatedIssue = { url, title, body, response };

await update(`users/${user.id}`, {
issues: arrayUnion(updatedIssue),
});
} else {
const newIssues = user.issues.filter((issue) => issue.url !== url);

await update(`users/${user.id}`, {
issues: newIssues,
});
}
}

return new Response("success", {
status: 200,
});
} catch (error) {
console.error(error);
return new Response("failed", {
status: 500,
});
}
}
20 changes: 20 additions & 0 deletions src/app/api/webhook/github_install_app/route.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { update } from "@/service/firebase/firestore";

// Redirect users to GitHub's authorization page to begin the app installation flow
export async function GET(req) {
const url = new URL(req.url);
const uid = url.searchParams.get("uid");

const githubAuthUrl = new URL(
`https://github.com/apps/${process.env.GITHUB_APP_NAME}/installations/new`
);

const githubAppInstallState = crypto.randomUUID();

await update(`users/${uid}`, { githubAppInstallState });

githubAuthUrl.searchParams.append("state", githubAppInstallState);

// Redirect to GitHub
return Response.redirect(githubAuthUrl.toString());
}
57 changes: 57 additions & 0 deletions src/app/api/webhook/github_install_app_callback/route.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Octokit } from "@octokit/rest";
import { createAppAuth } from "@octokit/auth-app";
import {
firestore,
collection,
getDocs,
query,
where,
update,
deleteField,
} from "@/service/firebase/firestore";

export async function GET(req) {
try {
const url = new URL(req.url);
const installation_id = Number(url.searchParams.get("installation_id"));
const state = url.searchParams.get("state");

const usersRef = collection(firestore, "users");

const querySnapshot = await getDocs(
query(usersRef, where("githubAppInstallState", "==", state))
);
const users = [];
querySnapshot.forEach((doc) => {
users.push({ id: doc.id, ...doc.data() });
});

const [user] = users;

const octokit = new Octokit({
authStrategy: createAppAuth,
auth: {
appId: process.env.GITHUB_APP_ID,
privateKey: process.env.GITHUB_APP_PRIVATE_KEY.replace(/\\n/g, "\n"),
installationId: installation_id,
},
});

const { data } = await octokit.request("GET /installation/repositories");

const [repoData] = data.repositories;

await update(`users/${user.id}`, {
installation_id,
repoData,
repo: repoData.html_url,
githubAppInstallState: deleteField(),
});

return Response.redirect(`${process.env.NEXT_PUBLIC_APP_URL}/dashboard`);
} catch (error) {
console.error("Error fetching repositories:", error);

return new Response("Error fetching repositories", { status: 500 });
}
}
94 changes: 74 additions & 20 deletions src/app/dashboard/page.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
"use client";
import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import { Typography, Box, Input, Button, Stack } from "@mui/joy";
import { getAuth } from "firebase/auth";
import {
Typography,
Box,
Button,
Card,
CardContent,
CardActions,
Stack,
Input,
} from "@mui/joy";
import { Check as CheckIcon } from "@mui/icons-material";
import { getAuth, onAuthStateChanged } from "firebase/auth";

import Sidebar from "@/component/Sidebar";
import { listen, set } from "@/service/firebase/firestore";
Expand All @@ -12,24 +21,23 @@ export default function Dashboard() {
const [user, setUser] = useState();
const [repo, setRepo] = useState("");
const [saving, setSaving] = useState(false);
const router = useRouter();

useEffect(() => {
const { currentUser } = getAuth();

if (currentUser === null) {
return router.replace("/");
}

setSession(currentUser);
return onAuthStateChanged(getAuth(), (session) => {
setSession(session);
});
}, []);

return listen(`users/${currentUser.uid}`, (doc) => {
const userData = doc.data();
useEffect(() => {
if (session) {
return listen(`users/${session.uid}`, (doc) => {
const userData = doc.data();

setUser(userData);
setRepo(userData?.repo);
});
}, [router]);
setUser(userData);
setRepo(userData?.repo);
});
}
}, [session]);

return session ? (
<Box sx={{ display: "flex", minHeight: "100dvh" }}>
Expand Down Expand Up @@ -64,9 +72,55 @@ export default function Dashboard() {
justifyContent: "space-between",
}}
>
<Typography level="h2" component="h1">
Save your repo
</Typography>
<Card sx={{ maxWidth: 400 }}>
<Typography level="h2" fontSize="lg" mb={1}>
Install Github App
</Typography>
{repo ? (
<CardContent>
<Typography>Agent installed:</Typography>
<Typography endDecorator={<CheckIcon />}>
<Typography
style={{ textDecoration: "underline", cursor: "pointer" }}
onClick={() => window.open(repo)}
>
{repo}
</Typography>
</Typography>
<Button
variant="solid"
color="primary"
onClick={() => {
const githubAppAuthUrl = `${process.env.NEXT_PUBLIC_APP_URL}/api/webhook/github_install_app?uid=${session.uid}`;
window.open(githubAppAuthUrl);
}}
>
Force Reinstall
</Button>
</CardContent>
) : (
<>
<CardContent>
<Typography>
To begin using the changelog and issues feature, you will
need to install the github app into your repo
</Typography>
</CardContent>
<CardActions>
<Button
variant="solid"
color="primary"
onClick={() => {
const githubAppAuthUrl = `${process.env.NEXT_PUBLIC_APP_URL}/api/webhook/github_install_app?uid=${session.uid}`;
window.open(githubAppAuthUrl);
}}
>
Install
</Button>
</CardActions>
</>
)}
</Card>
</Box>
<form
onSubmit={async (event) => {
Expand Down
Loading