Skip to content

Commit f508b98

Browse files
committed
ability to revalidate from feed page
1 parent 77fdc3b commit f508b98

File tree

4 files changed

+147
-58
lines changed

4 files changed

+147
-58
lines changed

src/app/api/revalidate/route.ts

Lines changed: 16 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import { NextResponse } from 'next/server';
2-
import { revalidatePath, revalidateTag } from 'next/cache';
3-
import { AVAILABLE_LOCALES } from '../../../i18n/routing';
42
import { nonEmpty } from '../../utils/config';
3+
import {
4+
revalidateFullSite,
5+
revalidateAllFeeds,
6+
revalidateAllGbfsFeeds,
7+
revalidateAllGtfsFeeds,
8+
revalidateAllGtfsRtFeeds,
9+
revalidateSpecificFeeds,
10+
} from '../../utils/revalidate-feeds';
511

612
type RevalidateTypes =
713
| 'full'
@@ -46,8 +52,7 @@ export async function GET(req: Request): Promise<NextResponse> {
4652
}
4753

4854
try {
49-
revalidateTag('guest-feeds', 'max');
50-
revalidatePath('/[locale]/feeds/[feedDataType]/[feedId]', 'layout');
55+
revalidateAllFeeds();
5156
console.log(
5257
'[cron] revalidate /api/revalidate: all-feeds revalidation triggered',
5358
);
@@ -104,60 +109,13 @@ export async function POST(req: Request): Promise<NextResponse> {
104109
// revalidateTag = triggers revalidation for API calls using `unstable_cache` with matching tags (e.g., feed-123, guest-feeds)
105110

106111
try {
107-
// clears cache for entire site
108-
if (payload.type === 'full') {
109-
revalidateTag('guest-feeds', 'max');
110-
revalidatePath('/', 'layout');
111-
}
112-
113-
// clears cache for all feed pages (ISR-cached layout)
114-
if (payload.type === 'all-feeds') {
115-
revalidateTag('guest-feeds', 'max');
116-
revalidatePath('/[locale]/feeds/[feedDataType]/[feedId]', 'layout');
117-
}
118-
119-
// clears cache for all GBFS feed pages (ISR-cached layout)
120-
if (payload.type === 'all-gbfs-feeds') {
121-
revalidateTag('feed-type-gbfs', 'max');
122-
revalidatePath('/[locale]/feeds/gbfs/[feedId]', 'layout');
123-
}
124-
125-
// clears cache for all GTFS feed pages (ISR-cached layout)
126-
if (payload.type === 'all-gtfs-feeds') {
127-
revalidateTag('feed-type-gtfs', 'max');
128-
revalidatePath('/[locale]/feeds/gtfs/[feedId]', 'layout');
129-
}
130-
131-
// clears cache for all GTFS RT feed pages (ISR-cached layout)
132-
if (payload.type === 'all-gtfs-rt-feeds') {
133-
revalidateTag('feed-type-gtfs_rt', 'max');
134-
revalidatePath('/[locale]/feeds/gtfs_rt/[feedId]', 'layout');
135-
}
136-
137-
// clears cache for specific feed pages (ISR-cached page) + localized paths
138-
if (payload.type === 'specific-feeds') {
139-
const localPaths = AVAILABLE_LOCALES.filter((loc) => loc !== 'en');
140-
const pathsToRevalidate: string[] = [];
141-
142-
payload.feedIds.forEach((id) => {
143-
revalidateTag(`feed-${id}`, 'max');
144-
// The id will try to revalidate all feed types with that id, but that's necessary since we don't know the feed type here and it's not a big deal if we revalidate some non-existent pages
145-
pathsToRevalidate.push(`/feeds/gtfs/${id}`);
146-
pathsToRevalidate.push(`/feeds/gtfs_rt/${id}`);
147-
pathsToRevalidate.push(`/feeds/gbfs/${id}`);
148-
});
149-
150-
console.log('Revalidating paths:', pathsToRevalidate);
151-
152-
pathsToRevalidate.forEach((path) => {
153-
revalidatePath(path);
154-
revalidatePath(path + '/map');
155-
localPaths.forEach((loc) => {
156-
revalidatePath(`/${loc}${path}`);
157-
revalidatePath(`/${loc}${path}/map`);
158-
});
159-
});
160-
}
112+
if (payload.type === 'full') revalidateFullSite();
113+
if (payload.type === 'all-feeds') revalidateAllFeeds();
114+
if (payload.type === 'all-gbfs-feeds') revalidateAllGbfsFeeds();
115+
if (payload.type === 'all-gtfs-feeds') revalidateAllGtfsFeeds();
116+
if (payload.type === 'all-gtfs-rt-feeds') revalidateAllGtfsRtFeeds();
117+
if (payload.type === 'specific-feeds')
118+
revalidateSpecificFeeds(payload.feedIds);
161119

162120
return NextResponse.json({
163121
ok: true,

src/app/screens/Feed/actions.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
'use server';
2+
import {
3+
getCurrentUserFromCookie,
4+
isMobilityDatabaseAdmin,
5+
} from '../../utils/auth-server';
6+
import { revalidateSpecificFeeds } from '../../utils/revalidate-feeds';
7+
8+
export async function revalidateFeedCache(
9+
feedId: string,
10+
): Promise<{ ok: boolean; message: string }> {
11+
const user = await getCurrentUserFromCookie();
12+
if (!isMobilityDatabaseAdmin(user?.email)) {
13+
return { ok: false, message: 'Unauthorized: admin access required' };
14+
}
15+
16+
revalidateSpecificFeeds([feedId]);
17+
return { ok: true, message: `Cache revalidated for feed ${feedId}` };
18+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
'use client';
2+
import { useState, useTransition } from 'react';
3+
import Box from '@mui/material/Box';
4+
import Button from '@mui/material/Button';
5+
import Typography from '@mui/material/Typography';
6+
import { revalidateFeedCache } from '../actions';
7+
8+
interface Props {
9+
feedId: string;
10+
}
11+
12+
export default function RevalidateCacheButton({
13+
feedId,
14+
}: Props): React.ReactElement {
15+
const [isPending, startTransition] = useTransition();
16+
const [result, setResult] = useState<{
17+
ok: boolean;
18+
message: string;
19+
} | null>(null);
20+
21+
const handleClick = (): void => {
22+
setResult(null);
23+
startTransition(async () => {
24+
const res = await revalidateFeedCache(feedId);
25+
setResult(res);
26+
});
27+
};
28+
29+
return (
30+
<Box
31+
sx={{
32+
display: 'flex',
33+
flexDirection: 'column',
34+
gap: 1,
35+
width: 'fit-content',
36+
}}
37+
>
38+
<Button variant='contained' onClick={handleClick} disabled={isPending}>
39+
{isPending ? 'Revalidating…' : 'Revalidate The Cache of This Page'}
40+
</Button>
41+
{result != null && !isPending && (
42+
<Typography
43+
variant='caption'
44+
color={result.ok ? 'success.main' : 'error.main'}
45+
>
46+
{result.message}
47+
</Typography>
48+
)}
49+
</Box>
50+
);
51+
}

src/app/utils/revalidate-feeds.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import 'server-only';
2+
import { revalidatePath, revalidateTag } from 'next/cache';
3+
import { AVAILABLE_LOCALES } from '../../i18n/routing';
4+
5+
/**
6+
* Revalidates the ISR cache for specific feed pages.
7+
* Applies to all feed types (gtfs, gtfs_rt, gbfs) since we don't know the type from the id alone.
8+
* Also revalidates localized paths and /map sub-routes.
9+
*/
10+
export function revalidateSpecificFeeds(feedIds: string[]): void {
11+
const localPaths = AVAILABLE_LOCALES.filter((loc) => loc !== 'en');
12+
const pathsToRevalidate: string[] = [];
13+
14+
feedIds.forEach((id) => {
15+
revalidateTag(`feed-${id}`, 'max');
16+
// The id will try to revalidate all feed types with that id, but that's necessary since we don't know the feed type here and it's not a big deal if we revalidate some non-existent pages
17+
pathsToRevalidate.push(`/feeds/gtfs/${id}`);
18+
pathsToRevalidate.push(`/feeds/gtfs_rt/${id}`);
19+
pathsToRevalidate.push(`/feeds/gbfs/${id}`);
20+
});
21+
22+
console.log('Revalidating paths:', pathsToRevalidate);
23+
24+
pathsToRevalidate.forEach((path) => {
25+
revalidatePath(path);
26+
revalidatePath(path + '/map');
27+
localPaths.forEach((loc) => {
28+
revalidatePath(`/${loc}${path}`);
29+
revalidatePath(`/${loc}${path}/map`);
30+
});
31+
});
32+
}
33+
34+
/** Clears cache for all feed pages (ISR-cached layout). */
35+
export function revalidateAllFeeds(): void {
36+
revalidateTag('guest-feeds', 'max');
37+
revalidatePath('/[locale]/feeds/[feedDataType]/[feedId]', 'layout');
38+
}
39+
40+
/** Clears cache for all GBFS feed pages (ISR-cached layout). */
41+
export function revalidateAllGbfsFeeds(): void {
42+
revalidateTag('feed-type-gbfs', 'max');
43+
revalidatePath('/[locale]/feeds/gbfs/[feedId]', 'layout');
44+
}
45+
46+
/** Clears cache for all GTFS feed pages (ISR-cached layout). */
47+
export function revalidateAllGtfsFeeds(): void {
48+
revalidateTag('feed-type-gtfs', 'max');
49+
revalidatePath('/[locale]/feeds/gtfs/[feedId]', 'layout');
50+
}
51+
52+
/** Clears cache for all GTFS-RT feed pages (ISR-cached layout). */
53+
export function revalidateAllGtfsRtFeeds(): void {
54+
revalidateTag('feed-type-gtfs_rt', 'max');
55+
revalidatePath('/[locale]/feeds/gtfs_rt/[feedId]', 'layout');
56+
}
57+
58+
/** Clears cache for the entire site. */
59+
export function revalidateFullSite(): void {
60+
revalidateTag('guest-feeds', 'max');
61+
revalidatePath('/', 'layout');
62+
}

0 commit comments

Comments
 (0)