Skip to content

Commit

Permalink
feat: connect github app to our web app
Browse files Browse the repository at this point in the history
  • Loading branch information
ShubhamPalriwala committed Feb 6, 2024
1 parent a974c1b commit 1352ae8
Show file tree
Hide file tree
Showing 10 changed files with 235 additions and 99 deletions.
7 changes: 7 additions & 0 deletions app/(dashboard)/select-repo/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"use server"

import { selectRepository } from "@/github/services/repository"

export const selectRepoActions = async (id: string) => {
return await selectRepository(id)
}
13 changes: 13 additions & 0 deletions app/(dashboard)/select-repo/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { DashboardHeader } from "@/components/header"
import { DashboardShell } from "@/components/shell"

export default function DashboardSettingsLoading() {
return (
<DashboardShell>
<DashboardHeader
heading="Open issues"
text="Comment on these issues to get assigned to work on them."
/>
</DashboardShell>
)
}
43 changes: 43 additions & 0 deletions app/(dashboard)/select-repo/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { redirect } from "next/navigation"
import { getRepositoriesForUser } from "@/github/services/repository"

import { authOptions } from "@/lib/auth"
import { db } from "@/lib/db"
import { getCurrentUser } from "@/lib/session"
import { DashboardHeader } from "@/components/header"
import { RepoSelector } from "@/components/repo-selecor"
import { DashboardShell } from "@/components/shell"

import { selectRepoActions } from "./actions"

export const metadata = {
title: "Connect a Repository",
description: "Comment on these issues to get assigned to work on them.",
}

export default async function SettingsPage() {
const user = await getCurrentUser()
if (!user) {
redirect(authOptions?.pages?.signIn || "/login")
}

const repos = await getRepositoriesForUser(user.id)

return (
<DashboardShell>
<DashboardHeader
heading="Connect a Repository"
text="Select the repository you want to integrate oss.gg with."
/>
<div className="space-y-2">
{repos.map((repo) => (
<RepoSelector
key={repo.id}
repo={repo}
selectRepoAction={selectRepoActions}
/>
))}
</div>
</DashboardShell>
)
}
2 changes: 1 addition & 1 deletion app/[profile]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export const metadata = {
title: "Profile Page",
}

async function fetchGithubUserData(userName) {
async function fetchGithubUserData(userName: string) {
const res = await fetch(`https://api.github.com/users/${userName}`)
const data = await res.json()
return data
Expand Down
21 changes: 21 additions & 0 deletions components/repo-selecor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"use client"

import { useToast } from "@/components/ui/use-toast"

export const RepoSelector = ({ repo, selectRepoAction }) => {
const { toast } = useToast()
return (
<div
className="rounded-md p-3 flex space-x-3 items-center hover:bg-slate-10 hover:scale-102 border border-transparent hover:border-slate-200 transition-all hover:cursor-pointer ease-in-out duration-150"
onClick={() => {
selectRepoAction(repo.id)
toast({
title: `${repo.name} selected`,
description: "Next steps to be built",
})
}}
>
{repo.name}
</div>
)
}
40 changes: 40 additions & 0 deletions github/services/repository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { db } from "@/lib/db"

export const selectRepository = async (id: string) => {
try {
const selectedRepository = await db.repository.update({
where: {
id,
},
data: {
configured: true,
},
})
return selectedRepository
} catch (error) {
throw new Error(`Failed to select repository: ${error}`)
}
}

export const getRepositoriesForUser = async (userId: string) => {
try {
const installationIds = await db.membership.findMany({
where: {
userId,
},
})

const repos = await db.repository.findMany({
where: {
installationId: {
in: installationIds.map((id) => id.installationId),
},
configured: false,
},
})
repos.sort((a, b) => a.name.localeCompare(b.name))
return repos
} catch (error) {
throw new Error(`Failed to get repositories for user: ${error}`)
}
}
195 changes: 99 additions & 96 deletions github/services/user.ts
Original file line number Diff line number Diff line change
@@ -1,129 +1,132 @@
import { readFileSync } from "fs"
import path from "path"
import { createAppAuth } from "@octokit/auth-app"
import { Octokit } from "@octokit/rest"
import { App } from "octokit"

import { env } from "@/env.mjs"
import { db } from "@/lib/db"

const privateKeyPath = "../../../../key.pem"
const resolvedPath = path.resolve(__dirname, privateKeyPath)
const privateKeyPath =
"/home/shubham/Downloads/ossgg-test.2024-01-18.private-key.pem"
// const resolvedPath = path.resolve(__dirname, privateKeyPath)
const resolvedPath = path.resolve(privateKeyPath)
const privateKey = readFileSync(resolvedPath, "utf8")

export const sendInstallationDetails = async (
installationId: number,
appId: number,
repos: any,
repos:
| {
id: number
node_id: string
name: string
full_name: string
private: boolean
}[]
| undefined,
installation: any
): Promise<unknown> => {
): Promise<void> => {
try {
const octokit = new Octokit({
authStrategy: createAppAuth,
auth: {
appId,
installationId,
privateKey,
const config = {
appId: appId,
privateKey: privateKey,
webhooks: {
secret: env.GITHUB_WEBHOOK_SECRET!,
},
})
}
const app = new App(config)
const octokit = await app.getInstallationOctokit(installationId)

const installationPrisma = await db.installation.create({
data: {
githubId: installationId,
type: installation?.account?.type.toLowerCase(),
},
})
const result = await db.$transaction(async (tx) => {
const installationPrisma = await tx.installation.upsert({
where: { githubId: installationId },
update: {
githubId: installationId,
type: installation?.account?.type.toLowerCase(),
},
create: {
githubId: installationId,
type: installation?.account?.type.toLowerCase(),
},
})

if (installationPrisma.type === "organization") {
try {
if (installation?.account?.type.toLowerCase() === "organization") {
const membersOfOrg = await octokit.rest.orgs.listMembers({
org: installation?.account?.login,
role: "all",
})

membersOfOrg.data.map(async (member) => {
// check if this member exists in the database
// if not, create a new member

const memberInDatabase = await db.user.findUnique({
where: {
for (const member of membersOfOrg.data) {
const newUser = await tx.user.upsert({
where: { githubId: member.id },
update: {},
create: {
githubId: member.id,
login: member.login,
},
})

if (!memberInDatabase) {
const newUser = await db.user.upsert({
where: {
githubId: member.id,
},
create: {
githubId: member.id,
login: member.login,
name: member.name,
email: member.email,
},
update: {
githubId: member.id,
login: member.login,
name: member.name,
email: member.email,
},
})

// create a new membership
const newMembership = await db.membership.create({
data: {
user: {
connect: {
id: newUser.id,
},
},
installation: { connect: { id: installationPrisma.id } },
role: "member",
await tx.membership.upsert({
where: {
userId_installationId: {
userId: newUser.id,
installationId: installationPrisma.id,
},
})
}

// check if this member has a membership
// if not, create a new membership
},
update: {},
create: {
userId: newUser.id,
installationId: installationPrisma.id,
role: "member",
},
})
}
} else {
const user = installation.account
const newUser = await tx.user.upsert({
where: {
githubId: user.id,
},
update: {},
create: {
githubId: user.id,
login: user.login,
name: user.name,
email: user.email,
},
})
} catch (error) {
console.error({ error })
}
} else {
const user = installation.account

const newUser = await db.user.upsert({
where: {
githubId: user.id,
},
create: {
githubId: user.id,
login: user.login,
name: user.name,
email: user.email,
},
update: {
githubId: user.id,
login: user.login,
name: user.name,
email: user.email,
},
})

// create a new membership
const newMembership = await db.membership.create({
data: {
user: {
connect: {
id: newUser.id,
await tx.membership.upsert({
where: {
userId_installationId: {
userId: newUser.id,
installationId: installationPrisma.id,
},
},
installation: { connect: { id: installationPrisma.id } },
role: "owner",
},
})
}
update: {},
create: {
userId: newUser.id,
installationId: installationPrisma.id,
role: "owner",
},
})
}
if (!repos) {
return
}
for (const repo of repos) {
await tx.repository.upsert({
where: { githubId: repo.id },
update: {},
create: {
githubId: repo.id,
name: repo.name,
installationId: installationPrisma.id,
},
})
}
})

return { data: "data" }
return result
} catch (error) {
throw new Error(`Failed to post installation details: ${error}`)
}
Expand Down
7 changes: 6 additions & 1 deletion github/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ import { Octokit } from "@octokit/rest"

import { GITHUB_APP_APP_ID } from "./constants"

const resolvedPath = path.resolve(__dirname, "../../../../key.pem")
// const resolvedPath = path.resolve(__dirname, "../../../../key.pem")
const privateKeyPath =
"/home/shubham/Downloads/ossgg-test.2024-01-18.private-key.pem"
// const resolvedPath = path.resolve(__dirname, privateKeyPath)
const resolvedPath = path.resolve(privateKeyPath)

const privateKey = readFileSync(resolvedPath, "utf8")

export const getOctokitInstance = (installationId: number) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-- AlterTable
ALTER TABLE "repositories" ADD COLUMN "configured" BOOLEAN NOT NULL DEFAULT false,
ALTER COLUMN "default_branch" DROP NOT NULL;
3 changes: 2 additions & 1 deletion prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,9 @@ model Repository {
description String?
homepage String?
topics String[]
default_branch String
default_branch String?
installationId String
configured Boolean @default(false)
levels Json @default("[]")
pointTransactions PointTransaction[]
Expand Down

0 comments on commit 1352ae8

Please sign in to comment.