From b49d7a7cf5388ad0203feaf43b6c25b2fc3b809f Mon Sep 17 00:00:00 2001 From: Richie McIlroy <33632126+richiemcilroy@users.noreply.github.com> Date: Wed, 27 Nov 2024 17:02:54 +0000 Subject: [PATCH] feat: Revalidate on reupload --- apps/web/app/api/playlist/route.ts | 22 ++++-- apps/web/app/api/revalidate/route.ts | 74 +++++++++++++++++++++ apps/web/app/api/upload/signed/route.ts | 16 +++++ apps/web/app/api/video/playlistUrl/route.ts | 11 ++- apps/web/app/s/[videoId]/page.tsx | 5 +- apps/web/utils/helpers.ts | 6 ++ 6 files changed, 127 insertions(+), 7 deletions(-) create mode 100644 apps/web/app/api/revalidate/route.ts diff --git a/apps/web/app/api/playlist/route.ts b/apps/web/app/api/playlist/route.ts index de55e963..de0609f1 100644 --- a/apps/web/app/api/playlist/route.ts +++ b/apps/web/app/api/playlist/route.ts @@ -13,7 +13,7 @@ import { generateM3U8Playlist, generateMasterPlaylist, } from "@/utils/video/ffmpeg/helpers"; -import { getHeaders } from "@/utils/helpers"; +import { getHeaders, CACHE_CONTROL_HEADERS } from "@/utils/helpers"; import { createS3Client, getS3Bucket } from "@/utils/s3"; export const revalidate = 3599; @@ -88,6 +88,7 @@ export async function GET(request: NextRequest) { headers: { ...getHeaders(origin), Location: `https://v.cap.so/${userId}/${videoId}/result.mp4`, + ...CACHE_CONTROL_HEADERS, }, }); } @@ -98,6 +99,7 @@ export async function GET(request: NextRequest) { headers: { ...getHeaders(origin), Location: `https://v.cap.so/${userId}/${videoId}/output/video_recording_000.m3u8`, + ...CACHE_CONTROL_HEADERS, }, }); } @@ -108,6 +110,7 @@ export async function GET(request: NextRequest) { headers: { ...getHeaders(origin), Location: playlistUrl, + ...CACHE_CONTROL_HEADERS, }, }); } @@ -131,6 +134,7 @@ export async function GET(request: NextRequest) { status: 200, headers: { ...getHeaders(origin), + ...CACHE_CONTROL_HEADERS, "Content-Type": "text/vtt", }, }); @@ -182,7 +186,10 @@ export async function GET(request: NextRequest) { return new Response(playlist, { status: 200, - headers: getHeaders(origin), + headers: { + ...getHeaders(origin), + ...CACHE_CONTROL_HEADERS, + }, }); } @@ -201,6 +208,7 @@ export async function GET(request: NextRequest) { headers: { ...getHeaders(origin), Location: playlistUrl, + ...CACHE_CONTROL_HEADERS, }, }); } @@ -279,7 +287,10 @@ export async function GET(request: NextRequest) { return new Response(generatedPlaylist, { status: 200, - headers: getHeaders(origin), + headers: { + ...getHeaders(origin), + ...CACHE_CONTROL_HEADERS, + }, }); } @@ -317,7 +328,10 @@ export async function GET(request: NextRequest) { return new Response(generatedPlaylist, { status: 200, - headers: getHeaders(origin), + headers: { + ...getHeaders(origin), + ...CACHE_CONTROL_HEADERS, + }, }); } catch (error) { console.error("Error generating video segment URLs", error); diff --git a/apps/web/app/api/revalidate/route.ts b/apps/web/app/api/revalidate/route.ts new file mode 100644 index 00000000..e759925c --- /dev/null +++ b/apps/web/app/api/revalidate/route.ts @@ -0,0 +1,74 @@ +import { revalidatePath } from 'next/cache'; +import { type NextRequest } from "next/server"; +import { getHeaders, CACHE_CONTROL_HEADERS } from "@/utils/helpers"; +import { db } from "@cap/database"; +import { videos } from "@cap/database/schema"; +import { eq } from "drizzle-orm"; + +export async function POST(request: NextRequest) { + const origin = request.headers.get("origin") as string; + + try { + const { videoId } = await request.json(); + + if (!videoId) { + return new Response( + JSON.stringify({ error: "Missing videoId" }), + { + status: 400, + headers: { + ...getHeaders(origin), + ...CACHE_CONTROL_HEADERS, + }, + } + ); + } + + const [video] = await db.select().from(videos).where(eq(videos.id, videoId)); + + if (!video) { + return new Response( + JSON.stringify({ error: "Video not found" }), + { + status: 404, + headers: { + ...getHeaders(origin), + ...CACHE_CONTROL_HEADERS, + }, + } + ); + } + + // Revalidate the specific video page + revalidatePath(`/s/${videoId}`); + + return new Response( + JSON.stringify({ + revalidated: true, + now: Date.now(), + path: `/s/${videoId}` + }), + { + headers: { + ...getHeaders(origin), + ...CACHE_CONTROL_HEADERS, + }, + } + ); + } catch (err) { + console.error("Revalidation error:", err); + return new Response( + JSON.stringify({ + error: "Error revalidating", + details: err instanceof Error ? err.message : String(err) + }), + { + status: 500, + headers: { + ...getHeaders(origin), + ...CACHE_CONTROL_HEADERS, + }, + } + ); + } +} \ No newline at end of file diff --git a/apps/web/app/api/upload/signed/route.ts b/apps/web/app/api/upload/signed/route.ts index a0f53d8d..12ed10d4 100644 --- a/apps/web/app/api/upload/signed/route.ts +++ b/apps/web/app/api/upload/signed/route.ts @@ -146,6 +146,22 @@ export async function POST(request: NextRequest) { console.log("Presigned URL created successfully"); + // After successful presigned URL creation, trigger revalidation + const videoId = fileKey.split('/')[1]; // Assuming fileKey format is userId/videoId/... + if (videoId) { + try { + await fetch(`${process.env.NEXT_PUBLIC_URL}/api/revalidate`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ videoId }), + }); + } catch (revalidateError) { + console.error('Failed to revalidate page:', revalidateError); + } + } + return new Response(JSON.stringify({ presignedPostData }), { headers: { "Content-Type": "application/json", diff --git a/apps/web/app/api/video/playlistUrl/route.ts b/apps/web/app/api/video/playlistUrl/route.ts index 2c2638b3..a85f73b9 100644 --- a/apps/web/app/api/video/playlistUrl/route.ts +++ b/apps/web/app/api/video/playlistUrl/route.ts @@ -3,6 +3,7 @@ import { db } from "@cap/database"; import { videos } from "@cap/database/schema"; import { eq } from "drizzle-orm"; import { getHeaders } from "@/utils/helpers"; +import { CACHE_CONTROL_HEADERS } from "@/utils/helpers"; export const revalidate = 0; @@ -63,7 +64,10 @@ export async function GET(request: NextRequest) { JSON.stringify({ playlistOne: playlistUrl, playlistTwo: null }), { status: 200, - headers: getHeaders(origin), + headers: { + ...getHeaders(origin), + ...CACHE_CONTROL_HEADERS, + }, } ); } @@ -75,7 +79,10 @@ export async function GET(request: NextRequest) { }), { status: 200, - headers: getHeaders(origin), + headers: { + ...getHeaders(origin), + ...CACHE_CONTROL_HEADERS, + }, } ); } diff --git a/apps/web/app/s/[videoId]/page.tsx b/apps/web/app/s/[videoId]/page.tsx index 8c0f2b88..5ec14b3d 100644 --- a/apps/web/app/s/[videoId]/page.tsx +++ b/apps/web/app/s/[videoId]/page.tsx @@ -1,4 +1,3 @@ -"use server"; import { Share } from "./Share"; import { db } from "@cap/database"; import { eq } from "drizzle-orm"; @@ -8,6 +7,10 @@ import type { Metadata, ResolvingMetadata } from "next"; import { notFound } from "next/navigation"; import { ImageViewer } from "./_components/ImageViewer"; +export const dynamic = "auto"; +export const dynamicParams = true; +export const revalidate = 30; + type Props = { params: { [key: string]: string | string[] | undefined }; }; diff --git a/apps/web/utils/helpers.ts b/apps/web/utils/helpers.ts index d9c49534..7ecdac7a 100644 --- a/apps/web/utils/helpers.ts +++ b/apps/web/utils/helpers.ts @@ -60,3 +60,9 @@ export function rateLimitMiddleware( return request; } + +export const CACHE_CONTROL_HEADERS = { + 'Cache-Control': 'no-store, no-cache, must-revalidate, proxy-revalidate', + 'Pragma': 'no-cache', + 'Expires': '0', +};