From 061d3bb03b2497103456ca924a31aca8dc81f4b4 Mon Sep 17 00:00:00 2001 From: Sam Becker Date: Sat, 23 Sep 2023 00:46:12 -0500 Subject: [PATCH] Start actively managing blob cache --- src/app/(auth-state)/admin/photos/page.tsx | 17 ++++++------ .../(auth-state)/admin/uploads/blob/route.tsx | 7 +++-- src/cache/index.ts | 26 +++++++++++++++++++ src/photo/actions.ts | 11 +++++--- 4 files changed, 47 insertions(+), 14 deletions(-) diff --git a/src/app/(auth-state)/admin/photos/page.tsx b/src/app/(auth-state)/admin/photos/page.tsx index b2949403..edfb4745 100644 --- a/src/app/(auth-state)/admin/photos/page.tsx +++ b/src/app/(auth-state)/admin/photos/page.tsx @@ -12,15 +12,16 @@ import { } from '@/photo/actions'; import { FaRegEdit } from 'react-icons/fa'; import SubmitButtonWithStatus from '@/components/SubmitButtonWithStatus'; -import { - pathForBlobUrl, - getBlobPhotoUrls, - getBlobUploadUrls, -} from '@/services/blob'; +import { pathForBlobUrl } from '@/services/blob'; import { pathForPhoto, pathForPhotoEdit } from '@/site/paths'; import { getPhotosLimitForQuery, titleForPhoto } from '@/photo'; import MorePhotos from '@/components/MorePhotos'; -import { getPhotosCached, getPhotosCountCached } from '@/cache'; +import { + getBlobPhotoUrlsCached, + getBlobUploadUrlsCached, + getPhotosCached, + getPhotosCountCached, +} from '@/cache'; export const runtime = 'edge'; @@ -41,8 +42,8 @@ export default async function AdminPage({ ] = await Promise.all([ getPhotosCached({ sortBy: 'createdAt', limit }), getPhotosCountCached(), - getBlobUploadUrls(), - DEBUG_PHOTO_BLOBS ? getBlobPhotoUrls() : [], + getBlobUploadUrlsCached(), + DEBUG_PHOTO_BLOBS ? getBlobPhotoUrlsCached() : [], ]); const showMorePhotos = count > photos.length; diff --git a/src/app/(auth-state)/admin/uploads/blob/route.tsx b/src/app/(auth-state)/admin/uploads/blob/route.tsx index fff0f44c..1eaa8be6 100644 --- a/src/app/(auth-state)/admin/uploads/blob/route.tsx +++ b/src/app/(auth-state)/admin/uploads/blob/route.tsx @@ -1,9 +1,9 @@ +import { revalidatePhotosAndBlobTag } from '@/cache'; import { ACCEPTED_PHOTO_FILE_TYPES, isUploadPathnameValid, } from '@/services/blob'; import { handleUpload, type HandleUploadBody } from '@vercel/blob/client'; -import { revalidatePath } from 'next/cache'; import { NextResponse } from 'next/server'; export const runtime = 'edge'; @@ -25,12 +25,15 @@ export async function POST(request: Request): Promise { throw new Error('Invalid upload'); } }, + // This argument is required, but doesn't seem to fire onUploadCompleted: async () => { - revalidatePath('admin/photos'); + revalidatePhotosAndBlobTag(); }, }); + revalidatePhotosAndBlobTag(); return NextResponse.json(jsonResponse); } catch (error) { + revalidatePhotosAndBlobTag(); return NextResponse.json( { error: (error as Error).message }, { status: 400 }, diff --git a/src/cache/index.ts b/src/cache/index.ts index 3c7e43c1..c622a2d5 100644 --- a/src/cache/index.ts +++ b/src/cache/index.ts @@ -8,10 +8,12 @@ import { getUniqueTags, } from '@/services/postgres'; import { parseCachedPhotosDates, parseCachedPhotoDates } from '@/photo'; +import { getBlobPhotoUrls, getBlobUploadUrls } from '@/services/blob'; const TAG_PHOTOS = 'photos'; const TAG_PHOTOS_COUNT = 'photos-count'; const TAG_TAGS = 'tags'; +const TAG_BLOB = 'blob'; const getPhotosCacheTags = (options: GetPhotosOptions = {}) => { const tags = []; @@ -42,6 +44,14 @@ const getPhotoCacheTag = (photoId: string) => `photo-${photoId}`; export const revalidatePhotosTag = () => revalidateTag(TAG_PHOTOS); +export const revalidateBlobTag = () => + revalidateTag(TAG_BLOB); + +export const revalidatePhotosAndBlobTag = () => { + revalidateTag(TAG_PHOTOS); + revalidateTag(TAG_BLOB); +}; + export const getPhotosCached: typeof getPhotos = (...args) => unstable_cache( () => getPhotos(...args), @@ -74,6 +84,22 @@ export const getUniqueTagsCached: typeof getUniqueTags = (...args) => } )(); +export const getBlobUploadUrlsCached: typeof getBlobUploadUrls = (...args) => + unstable_cache( + () => getBlobUploadUrls(...args), + [TAG_BLOB], { + tags: [TAG_BLOB], + } + )(); + +export const getBlobPhotoUrlsCached: typeof getBlobPhotoUrls = (...args) => + unstable_cache( + () => getBlobPhotoUrls(...args), + [TAG_BLOB], { + tags: [TAG_BLOB], + } + )(); + export const getImageCacheHeadersForAuth = (session?: Session) => { return { 'Cache-Control': !session?.user diff --git a/src/photo/actions.ts b/src/photo/actions.ts index c9b6293a..3ea24b75 100644 --- a/src/photo/actions.ts +++ b/src/photo/actions.ts @@ -1,6 +1,5 @@ 'use server'; -import { revalidatePath } from 'next/cache'; import { sqlDeletePhoto, sqlInsertPhoto, @@ -12,7 +11,11 @@ import { convertUploadToPhoto, deleteBlobPhoto, } from '@/services/blob'; -import { revalidatePhotosTag } from '@/cache'; +import { + revalidateBlobTag, + revalidatePhotosAndBlobTag, + revalidatePhotosTag, +} from '@/cache'; export async function createPhotoAction(formData: FormData) { const photo = convertFormDataToPhoto(formData, true); @@ -26,7 +29,7 @@ export async function createPhotoAction(formData: FormData) { await sqlInsertPhoto(photo); - revalidatePhotosTag(); + revalidatePhotosAndBlobTag(); redirect('/admin/photos'); } @@ -53,5 +56,5 @@ export async function deletePhotoAction(formData: FormData) { export async function deleteBlobPhotoAction(formData: FormData) { await deleteBlobPhoto(formData.get('url') as string); - revalidatePath('/admin/photos'); + revalidateBlobTag(); };