Skip to content

Commit 1c07617

Browse files
author
Nicklas Knell
committed
feat: add netlify integration
1 parent 5bfc2a5 commit 1c07617

File tree

12 files changed

+227
-6
lines changed

12 files changed

+227
-6
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,6 @@ dist-ssr
2525
*.njsproj
2626
*.sln
2727
*.sw?
28+
29+
# Local Netlify folder
30+
.netlify

README.md

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,32 @@
1-
# github-viewer
1+
# github-viewer
2+
3+
## github app setup
4+
5+
Follow [this guide](https://docs.github.com/en/apps/creating-github-apps/registering-a-github-app/registering-a-github-app) on how to create a GitHub App and add the homepage URL and callback URL according to your netlify app.
6+
7+
**Homepage URL**: https://your-netlify-prefix.netlify.app/
8+
9+
**Callback URL**: https://your-netlify-prefix.app/callback
10+
11+
12+
## netlify setup
13+
14+
For this you can follow [this getting started page](https://docs.netlify.com/cli/get-started).
15+
16+
But these three commands should be enough:
17+
18+
```bash
19+
npm install netlify-cli --save-dev
20+
netlify login
21+
netlify init
22+
```
23+
24+
After this add these env vars to netlify:
25+
26+
**Any env var without the prefix VITE will not be available on the client side**
27+
28+
```
29+
GITHUB_CLIENT_SECRET=<githubAppSecret>
30+
VITE_GITHUB_CLIENT_ID=<githubAppId>
31+
VITE_CODERS_INITIAL=<githubCoders> // username1,displayName1|username2,displayName2|...
32+
```

bun.lockb

8.98 KB
Binary file not shown.

netlify.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[build]
2+
command = "bun run build"
3+
functions = "netlify/functions"
4+
publish = "dist"
5+
6+
[[redirects]]
7+
from = "/*"
8+
to = "/index.html"
9+
status = 200
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import fetch from 'node-fetch';
2+
import { Handler, HandlerEvent } from '@netlify/functions';
3+
4+
const handler: Handler = async (event: HandlerEvent) => {
5+
const client_id = process.env.VITE_GITHUB_CLIENT_ID!;
6+
const client_secret = process.env.GITHUB_CLIENT_SECRET!;
7+
const code = event.body ? JSON.parse(event.body).code : null;
8+
9+
if (!code) {
10+
return {
11+
statusCode: 400,
12+
body: JSON.stringify({ error: 'Code is required' })
13+
};
14+
}
15+
16+
try {
17+
const response = await fetch('https://github.com/login/oauth/access_token', {
18+
method: 'POST',
19+
headers: {
20+
'Content-Type': 'application/json',
21+
'Accept': 'application/json'
22+
},
23+
body: JSON.stringify({
24+
client_id,
25+
client_secret,
26+
code
27+
})
28+
});
29+
30+
const data: any = await response.json();
31+
32+
if (data.error) {
33+
return {
34+
statusCode: 400,
35+
body: JSON.stringify({ error: data.error })
36+
};
37+
}
38+
39+
return {
40+
statusCode: 200,
41+
body: JSON.stringify(data)
42+
};
43+
} catch (error) {
44+
return {
45+
statusCode: 500,
46+
body: JSON.stringify({ error: 'Internal Server Error' })
47+
};
48+
}
49+
};
50+
51+
export { handler };

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@
1919
"@mui/icons-material": "5.14.12",
2020
"@mui/joy": "5.0.0-beta.9",
2121
"@mui/material": "5.14.12",
22+
"@netlify/functions": "2.7.0",
2223
"axios": "1.5.1",
2324
"graphql": "16.8.1",
2425
"graphql-request": "7.0.1",
2526
"immer": "10.0.3",
27+
"node-fetch": "3.3.2",
2628
"react": "18.2.0",
2729
"react-dom": "18.2.0",
2830
"react-router-dom": "6.23.1",

src/App.tsx

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,34 @@
11
import { CssVarsProvider } from "@mui/joy/styles";
2-
import { Box, CssBaseline } from "@mui/joy";
2+
import { Box, Button, CssBaseline } from "@mui/joy";
33
import { PullRequestView } from "./pr";
4+
import { OpenInNew } from "@mui/icons-material";
5+
import { RouterProvider, createBrowserRouter } from "react-router-dom";
6+
import { AuthCallback } from "./AuthCallback";
7+
8+
const router = createBrowserRouter([
9+
{
10+
path: "/",
11+
element: <PullRequestView />,
12+
},
13+
{
14+
path: "/login",
15+
element: (
16+
<Button
17+
component="a"
18+
href={`https://github.com/login/oauth/authorize?client_id=${
19+
import.meta.env.VITE_GITHUB_CLIENT_ID
20+
}`}
21+
startDecorator={<OpenInNew />}
22+
>
23+
Login with Github
24+
</Button>
25+
),
26+
},
27+
{
28+
path: "/callback",
29+
element: <AuthCallback />,
30+
},
31+
]);
432

533
export function App() {
634
return (
@@ -21,7 +49,7 @@ export function App() {
2149
overflowY: "hidden",
2250
}}
2351
>
24-
<PullRequestView />
52+
<RouterProvider router={router} />
2553
</Box>
2654
</Box>
2755
</CssVarsProvider>

src/AuthCallback.tsx

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { Box, Typography } from "@mui/joy";
2+
import { useEffect, useState } from "react";
3+
import { useNavigate, useSearchParams } from "react-router-dom";
4+
import { storeToken } from "./utils";
5+
6+
export const AuthCallback = () => {
7+
const navigate = useNavigate();
8+
const [urlSearchParams] = useSearchParams();
9+
const [loading, setLoading] = useState(false);
10+
const [errors, setErrors] = useState<string[]>([]);
11+
12+
useEffect(() => {
13+
if (urlSearchParams.get("code") != null) {
14+
setLoading(true);
15+
fetch("/.netlify/functions/exchange-token", {
16+
method: "POST",
17+
body: JSON.stringify({ code: urlSearchParams.get("code") }),
18+
})
19+
.then((response) => response.json())
20+
.then((data) => {
21+
setLoading(false);
22+
storeToken(data)
23+
navigate("/");
24+
})
25+
.catch((error) => {
26+
setLoading(false);
27+
setErrors([error.message])
28+
});
29+
} else {
30+
setErrors(["No code found in URL."])
31+
}
32+
33+
}, [urlSearchParams]);
34+
35+
return (
36+
<Box sx={{ display: "flex", minHeight: "100dvh", overflowY: "hidden" }}>
37+
<Box
38+
sx={{
39+
flex: 1,
40+
display: "flex",
41+
flexDirection: "column",
42+
height: "100dvh",
43+
justifyContent: "center",
44+
alignItems: "center",
45+
}}
46+
>
47+
{loading && errors.length === 0 ? "Loading..." : null}
48+
{!loading && errors.length !== 0 ? errors.map(error => <Typography>{error}</Typography>) : null}
49+
</Box>
50+
</Box>
51+
);
52+
};

src/api/api.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import { GithubGraphqlClient } from "./GithubGraphqlClient.ts";
2+
import { getAccessToken } from "../utils";
23

3-
const AUTH_TOKEN = import.meta.env.VITE_AUTH_TOKEN;
4+
let api: GithubGraphqlClient;
45

5-
export const api = new GithubGraphqlClient(AUTH_TOKEN);
6+
export async function getApi() {
7+
if (!api) {
8+
api = new GithubGraphqlClient(await getAccessToken());
9+
}
10+
return api;
11+
}

src/api/fetchAllOpenPrs.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1+
import { getApi } from "./api.ts";
12
import { mapResult } from "./mapResult.ts";
23
import { PullRequest } from "./types.ts";
3-
import { api } from "./api.ts";
44

55
export async function fetchAllOpenPrs(
66
usernames: string[],
@@ -11,6 +11,7 @@ export async function fetchAllOpenPrs(
1111
const openPRs: PullRequest[] = [];
1212

1313
while (hasNextPage) {
14+
const api = await getApi();
1415
const response = await api.getPullRequests(usernames, "open", 50, after);
1516
const mappedResult = mapResult(response, baseUsername);
1617

0 commit comments

Comments
 (0)