Skip to content

Commit e5bb40d

Browse files
committed
fetch profile from gh
1 parent f702abe commit e5bb40d

File tree

8 files changed

+185
-4
lines changed

8 files changed

+185
-4
lines changed

app/api/profile/[login]/route.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { NextRequest, NextResponse } from 'next/server';
2+
import type { Session } from 'next-auth';
3+
4+
import { auth } from '@/auth';
5+
import { signedFetch } from '@/lib/signed-fetch';
6+
7+
export type AuthRequest = NextRequest & { auth: Session | null };
8+
9+
export const POST = auth(async function POST(req: AuthRequest, { params }) {
10+
if (!req.auth) {
11+
return NextResponse.json({ message: 'Not authenticated' }, { status: 401 });
12+
}
13+
14+
const { login } = await params!;
15+
const { user } = req.auth;
16+
17+
const response = await signedFetch('/user/fetch', {
18+
method: 'POST',
19+
headers: { 'Content-Type': 'application/json' },
20+
body: JSON.stringify({
21+
userLogin: user.githubLogin,
22+
loginToFetch: login,
23+
}),
24+
});
25+
26+
const responseData = await response.json();
27+
28+
if (!response.ok) {
29+
return NextResponse.json({ message: 'Failed to fetch user', error: responseData }, { status: response.status });
30+
}
31+
32+
return NextResponse.json(responseData);
33+
});
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
'use client';
2+
3+
import { useParams } from 'next/navigation';
4+
import { FC } from 'react';
5+
6+
import { Button } from '@/components/ui/button';
7+
8+
type FetchUserButtonProps = {
9+
label?: string;
10+
};
11+
12+
export const FetchUserButton: FC<FetchUserButtonProps> = ({ label = 'Fetch user from GitHub' }) => {
13+
const params = useParams<{ login: string }>();
14+
15+
const fetchUser = async () => {
16+
const res = await fetch(`/api/profile/${params.login}`, { method: 'POST' });
17+
18+
if (!res.ok) {
19+
throw new Error('Failed to fetch user');
20+
}
21+
22+
return res.json();
23+
};
24+
25+
return <Button onClick={fetchUser}>{label}</Button>;
26+
};

app/profile/[login]/not-found.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import { Page } from '@/components/page/page';
22

3+
// import { FetchUserButton } from './components/fetch-user-button';
4+
35
export default async function NotFound() {
46
return (
57
<Page className="gap-6 flex-col md:flex-row">
68
<div className="flex-grow flex flex-col gap-6">
79
<h1 className="text-2xl font-semibold">User not found</h1>
810
<p className="text-muted-foreground">The user you are looking for does not exist.</p>
11+
{/* <FetchUserButton /> */}
912
</div>
1013
</Page>
1114
);

lib/graphql/request.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import 'server-only';
2+
import { signedFetch } from '../signed-fetch';
23

34
export async function request(
45
query: string,
56
variables?: Record<string, unknown>,
67
params?: { revalidate?: number },
78
): Promise<{ data: unknown; status: number }> {
8-
const response = await fetch(process.env.GRAPHQL_URI!, {
9+
const response = await signedFetch('/graphql', {
910
method: 'POST',
1011
headers: {
1112
'Content-Type': 'application/json',
12-
'x-api-key': process.env.GRAPHQL_SECRET_KEY!,
1313
// Skip rate limit if custom header is present
1414
'nextjs-build-phase': String(process.env.NEXT_PHASE === 'phase-production-build'),
1515
},

lib/signed-fetch.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import 'server-only';
2+
import jwt from 'jsonwebtoken';
3+
4+
export async function signedFetch(path: string, init: RequestInit = {}) {
5+
const payload = {
6+
iss: 'nextjs-frontend',
7+
sub: 'service-call',
8+
iat: Math.floor(Date.now() / 1000),
9+
};
10+
11+
const token = jwt.sign(payload, process.env.INTERNAL_JWT_SECRET!, { expiresIn: '2m' });
12+
13+
const headers = new Headers(init.headers);
14+
headers.set('Authorization', `Bearer ${token}`);
15+
16+
return fetch(`${process.env.URI_GITRANKS}${path}`, { ...init, headers });
17+
}

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"class-variance-authority": "^0.7.1",
3131
"clsx": "^2.1.1",
3232
"date-fns": "^4.1.0",
33+
"jsonwebtoken": "^9.0.2",
3334
"lucide-react": "^0.494.0",
3435
"mongodb": "^6.15.0",
3536
"next": "15.3.1",
@@ -55,6 +56,7 @@
5556
"@graphql-codegen/typed-document-node": "^5.1.1",
5657
"@svgr/cli": "^8.1.0",
5758
"@tailwindcss/postcss": "^4.1.4",
59+
"@types/jsonwebtoken": "^9.0.9",
5860
"@types/node": "^22.14.1",
5961
"@types/react": "^19.1.2",
6062
"@types/react-dom": "^19.1.2",

pnpm-lock.yaml

Lines changed: 100 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

types/next-auth.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { DefaultSession } from 'next-auth';
22

33
declare module 'next-auth' {
44
interface User {
5-
githubId?: string;
5+
githubLogin?: string;
66
}
77

88
interface Session extends DefaultSession {
@@ -14,6 +14,6 @@ declare module 'next-auth' {
1414

1515
declare module 'next-auth/jwt' {
1616
interface JWT extends DefaultJWT {
17-
githubId?: string;
17+
githubLogin?: string;
1818
}
1919
}

0 commit comments

Comments
 (0)