Skip to content

Commit 76c7974

Browse files
authored
Implement route to invalidate data cache (#2916)
1 parent 13514bf commit 76c7974

File tree

4 files changed

+84
-0
lines changed

4 files changed

+84
-0
lines changed

.changeset/lemon-pugs-smell.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'gitbook-v2': minor
3+
---
4+
5+
Add route to revalidate cached data
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { type NextRequest, NextResponse } from 'next/server';
2+
3+
import { withVerifySignature } from '@v2/lib/routes';
4+
import { revalidateTag } from 'next/cache';
5+
6+
interface JsonBody {
7+
tags: string[];
8+
}
9+
10+
/**
11+
* Revalidate cached data based on tags.
12+
* The body should be a JSON with { tags: string[] }
13+
*/
14+
export async function POST(req: NextRequest) {
15+
return withVerifySignature<JsonBody>(req, async (body) => {
16+
if (!body.tags || !Array.isArray(body.tags)) {
17+
return NextResponse.json(
18+
{
19+
error: 'tags must be an array',
20+
},
21+
{ status: 400 }
22+
);
23+
}
24+
25+
body.tags.forEach((tag) => {
26+
revalidateTag(tag);
27+
});
28+
29+
return NextResponse.json({
30+
success: true,
31+
});
32+
});
33+
}

packages/gitbook-v2/src/lib/env.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,8 @@ export const GITBOOK_INTEGRATIONS_HOST =
5858
export const GITBOOK_IMAGE_RESIZE_URL = process.env.GITBOOK_IMAGE_RESIZE_URL ?? null;
5959
export const GITBOOK_IMAGE_RESIZE_SIGNING_KEY =
6060
process.env.GITBOOK_IMAGE_RESIZE_SIGNING_KEY ?? null;
61+
62+
/**
63+
* Secret used to validate requests from the GitBook app.
64+
*/
65+
export const GITBOOK_SECRET = process.env.GITBOOK_SECRET ?? null;

packages/gitbook-v2/src/lib/routes.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import crypto from 'node:crypto';
2+
import { NextResponse } from 'next/server';
3+
import { GITBOOK_SECRET } from './env';
4+
5+
/**
6+
* Verify the signature of the request and call the function with the body.
7+
*/
8+
export async function withVerifySignature<T>(
9+
request: Request,
10+
fn: (body: T) => Promise<NextResponse>
11+
) {
12+
try {
13+
const rawBody = await request.text();
14+
const body = JSON.parse(rawBody) as T;
15+
16+
if (GITBOOK_SECRET) {
17+
// Retrieve the signature header from the request
18+
const incomingSignature = request.headers.get('x-gitbook-signature');
19+
if (!incomingSignature) {
20+
return NextResponse.json({ error: 'Missing signature header' }, { status: 400 });
21+
}
22+
23+
const computedSignature = crypto
24+
.createHmac('sha256', GITBOOK_SECRET)
25+
.update(rawBody)
26+
.digest('hex');
27+
28+
// Compare incoming signature to computed signature
29+
if (incomingSignature !== computedSignature) {
30+
return NextResponse.json({ error: 'Invalid signature' }, { status: 401 });
31+
}
32+
}
33+
34+
return await fn(body);
35+
} catch (_error) {
36+
return NextResponse.json(
37+
{ error: 'Invalid request or unable to parse JSON' },
38+
{ status: 400 }
39+
);
40+
}
41+
}

0 commit comments

Comments
 (0)