Cache all postgres requests
This commit is contained in:
parent
7bb1d7d6b4
commit
97bc58bd8a
@ -33,13 +33,18 @@ export default async function AdminPage({
|
||||
}) {
|
||||
const { offset, limit } = getPhotosLimitForQuery(searchParams.next);
|
||||
|
||||
const photos = await getPhotos('createdAt', limit);
|
||||
|
||||
const count = await getPhotosCount();
|
||||
const [
|
||||
photos,
|
||||
count,
|
||||
blobUploadUrls,
|
||||
] = await Promise.all([
|
||||
await getPhotos({ sortBy: 'createdAt', limit }),
|
||||
await getPhotosCount(),
|
||||
await getBlobUploadUrls(),
|
||||
]);
|
||||
|
||||
const showMorePhotos = count > photos.length;
|
||||
|
||||
const blobUploadUrls = await getBlobUploadUrls();
|
||||
const blobPhotoUrls = DEBUG_PHOTO_BLOBS
|
||||
? await getBlobPhotoUrls()
|
||||
: [];
|
||||
|
||||
@ -1,17 +1,22 @@
|
||||
import {
|
||||
getPhotosCached,
|
||||
getPhotosCountCached,
|
||||
getUniqueTagsCached,
|
||||
} from '@/cache';
|
||||
import AnimateItems from '@/components/AnimateItems';
|
||||
import MorePhotos from '@/components/MorePhotos';
|
||||
import SiteGrid from '@/components/SiteGrid';
|
||||
import { generateOgImageMetaForPhotos, getPhotosLimitForQuery } from '@/photo';
|
||||
import PhotoGrid from '@/photo/PhotoGrid';
|
||||
import PhotosEmptyState from '@/photo/PhotosEmptyState';
|
||||
import { getPhotos, getPhotosCount, getUniqueTags } from '@/services/postgres';
|
||||
import { MAX_PHOTOS_TO_SHOW_HOME } from '@/photo/image-response';
|
||||
import PhotoTag from '@/tag/PhotoTag';
|
||||
import { Metadata } from 'next';
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
const photos = await getPhotos();
|
||||
const photos = await getPhotosCached({ limit: MAX_PHOTOS_TO_SHOW_HOME});
|
||||
return generateOgImageMetaForPhotos(photos);
|
||||
}
|
||||
|
||||
@ -22,11 +27,11 @@ export default async function GridPage({
|
||||
}) {
|
||||
const { offset, limit } = getPhotosLimitForQuery(searchParams.next);
|
||||
|
||||
const photos = await getPhotos(undefined, limit);
|
||||
const photos = await getPhotosCached({ limit });
|
||||
|
||||
const count = await getPhotosCount();
|
||||
const count = await getPhotosCountCached();
|
||||
|
||||
const tags = await getUniqueTags();
|
||||
const tags = await getUniqueTagsCached();
|
||||
|
||||
const showMorePhotos = count > photos.length;
|
||||
|
||||
|
||||
@ -1,20 +1,18 @@
|
||||
import { auth } from '@/auth';
|
||||
import { getImageCacheHeadersForAuth } from '@/cache';
|
||||
import { getImageCacheHeadersForAuth, getPhotosCached } from '@/cache';
|
||||
import {
|
||||
IMAGE_OG_SMALL_SIZE,
|
||||
MAX_PHOTOS_TO_SHOW_HOME,
|
||||
} from '@/photo/image-response';
|
||||
import HomeImageResponse from '@/photo/image-response/HomeImageResponse';
|
||||
import { getPhotos } from '@/services/postgres';
|
||||
import { ImageResponse } from 'next/server';
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const photos = await getPhotos(
|
||||
undefined,
|
||||
MAX_PHOTOS_TO_SHOW_HOME,
|
||||
);
|
||||
const photos = await getPhotosCached({
|
||||
limit: MAX_PHOTOS_TO_SHOW_HOME,
|
||||
});
|
||||
|
||||
const headers = await getImageCacheHeadersForAuth(await auth());
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { getPhotosCached, getPhotosCountCached } from '@/cache';
|
||||
import MorePhotos from '@/components/MorePhotos';
|
||||
import { getPhotosLimitForQuery } from '@/photo';
|
||||
import StaggeredOgPhotos from '@/photo/StaggeredOgPhotos';
|
||||
import { getPhotos, getPhotosCount } from '@/services/postgres';
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
@ -12,9 +12,9 @@ export default async function GridPage({
|
||||
}) {
|
||||
const { offset, limit } = getPhotosLimitForQuery(searchParams.next);
|
||||
|
||||
const photos = await getPhotos(undefined, limit);
|
||||
const photos = await getPhotosCached({ limit });
|
||||
|
||||
const count = await getPhotosCount();
|
||||
const count = await getPhotosCountCached();
|
||||
|
||||
const showMorePhotos = count > photos.length;
|
||||
|
||||
|
||||
@ -1,15 +1,14 @@
|
||||
import { auth } from '@/auth';
|
||||
import { getImageCacheHeadersForAuth } from '@/cache';
|
||||
import { getImageCacheHeadersForAuth, getPhotoCached } from '@/cache';
|
||||
import { IMAGE_OG_SIZE } from '@/photo/image-response';
|
||||
import PhotoImageResponse from '@/photo/image-response/PhotoImageResponse';
|
||||
import { getPhoto } from '@/services/postgres';
|
||||
import { getIBMPlexMonoMedium } from '@/site/font';
|
||||
import { ImageResponse } from 'next/server';
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
export async function GET(request: Request, context: any){
|
||||
const photo = await getPhoto(context.params.photoId);
|
||||
const photo = await getPhotoCached(context.params.photoId);
|
||||
|
||||
const {
|
||||
fontFamily,
|
||||
|
||||
@ -4,14 +4,10 @@ import {
|
||||
titleForPhoto,
|
||||
} from '@/photo';
|
||||
import { Metadata } from 'next';
|
||||
import {
|
||||
getPhoto,
|
||||
getPhotosTakenAfterPhotoInclusive,
|
||||
getPhotosTakenBeforePhoto,
|
||||
} from '@/services/postgres';
|
||||
import { redirect } from 'next/navigation';
|
||||
import { absolutePathForPhoto, absolutePathForPhotoImage } from '@/site/paths';
|
||||
import PhotoDetailPage from '@/photo/PhotoDetailPage';
|
||||
import { getPhotoCached, getPhotosCached } from '@/cache';
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
@ -20,7 +16,7 @@ export async function generateMetadata({
|
||||
}: {
|
||||
params: { photoId: string }
|
||||
}): Promise<Metadata> {
|
||||
const photo = await getPhoto(photoId);
|
||||
const photo = await getPhotoCached(photoId);
|
||||
|
||||
if (!photo) { return {}; }
|
||||
|
||||
@ -54,15 +50,21 @@ export default async function PhotoPage({
|
||||
params: { photoId: string }
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
const photo = await getPhoto(photoId);
|
||||
const photo = await getPhotoCached(photoId);
|
||||
|
||||
if (!photo) { redirect('/'); }
|
||||
|
||||
const photosBefore = await getPhotosTakenBeforePhoto(photo, 1);
|
||||
const photosAfter = await getPhotosTakenAfterPhotoInclusive(
|
||||
photo,
|
||||
GRID_THUMBNAILS_TO_SHOW_MAX + 1,
|
||||
);
|
||||
const [
|
||||
photosBefore,
|
||||
photosAfter,
|
||||
] = await Promise.all([
|
||||
getPhotosCached({ takenBefore: photo.takenAt, limit: 1 }),
|
||||
getPhotosCached({
|
||||
takenAfterInclusive: photo.takenAt,
|
||||
limit: GRID_THUMBNAILS_TO_SHOW_MAX + 1,
|
||||
}),
|
||||
]);
|
||||
|
||||
const photos = photosBefore.concat(photosAfter);
|
||||
|
||||
return <>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { getPhotoCached } from '@/cache';
|
||||
import PhotoModal from '@/photo/PhotoModal';
|
||||
import { getPhoto } from '@/services/postgres';
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
export const runtime = 'edge';
|
||||
@ -9,7 +9,7 @@ export default async function Share({
|
||||
}: {
|
||||
params: { photoId: string }
|
||||
}) {
|
||||
const photo = await getPhoto(photoId);
|
||||
const photo = await getPhotoCached(photoId);
|
||||
|
||||
if (!photo) { return redirect('/'); }
|
||||
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
import { getPhotosCached, getPhotosCountCached } from '@/cache';
|
||||
import AnimateItems from '@/components/AnimateItems';
|
||||
import MorePhotos from '@/components/MorePhotos';
|
||||
import SiteGrid from '@/components/SiteGrid';
|
||||
import { generateOgImageMetaForPhotos, getPhotosLimitForQuery } from '@/photo';
|
||||
import PhotoLarge from '@/photo/PhotoLarge';
|
||||
import PhotosEmptyState from '@/photo/PhotosEmptyState';
|
||||
import { getPhotos, getPhotosCount } from '@/services/postgres';
|
||||
import { Metadata } from 'next';
|
||||
|
||||
export const dynamic = 'force-static';
|
||||
export const runtime = 'edge';
|
||||
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
const photos = await getPhotos();
|
||||
const photos = await getPhotosCached();
|
||||
return generateOgImageMetaForPhotos(photos);
|
||||
}
|
||||
|
||||
@ -21,9 +21,9 @@ export default async function HomePage({
|
||||
}) {
|
||||
const { offset, limit } = getPhotosLimitForQuery(searchParams.next, 12);
|
||||
|
||||
const photos = await getPhotos(undefined, limit);
|
||||
const photos = await getPhotosCached({ limit });
|
||||
|
||||
const count = await getPhotosCount();
|
||||
const count = await getPhotosCountCached();
|
||||
|
||||
const showMorePhotos = count > photos.length;
|
||||
|
||||
|
||||
@ -3,13 +3,10 @@ import {
|
||||
titleForPhoto,
|
||||
} from '@/photo';
|
||||
import { Metadata } from 'next';
|
||||
import {
|
||||
getPhoto,
|
||||
getPhotos,
|
||||
} from '@/services/postgres';
|
||||
import { redirect } from 'next/navigation';
|
||||
import { absolutePathForPhoto, absolutePathForPhotoImage } from '@/site/paths';
|
||||
import PhotoDetailPage from '@/photo/PhotoDetailPage';
|
||||
import { getPhotoCached, getPhotosCached } from '@/cache';
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
@ -18,7 +15,7 @@ export async function generateMetadata({
|
||||
}: {
|
||||
params: { photoId: string, tag: string }
|
||||
}): Promise<Metadata> {
|
||||
const photo = await getPhoto(photoId);
|
||||
const photo = await getPhotoCached(photoId);
|
||||
|
||||
if (!photo) { return {}; }
|
||||
|
||||
@ -52,11 +49,11 @@ export default async function PhotoTagPage({
|
||||
params: { photoId: string, tag: string }
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
const photo = await getPhoto(photoId);
|
||||
const photo = await getPhotoCached(photoId);
|
||||
|
||||
if (!photo) { redirect('/'); }
|
||||
|
||||
const photos = await getPhotos(undefined, undefined, undefined, tag);
|
||||
const photos = await getPhotosCached({ tag });
|
||||
|
||||
return <>
|
||||
{children}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { getPhotoCached } from '@/cache';
|
||||
import PhotoModal from '@/photo/PhotoModal';
|
||||
import { getPhoto } from '@/services/postgres';
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
export const runtime = 'edge';
|
||||
@ -9,7 +9,7 @@ export default async function Share({
|
||||
}: {
|
||||
params: { photoId: string, tag: string }
|
||||
}) {
|
||||
const photo = await getPhoto(photoId);
|
||||
const photo = await getPhotoCached(photoId);
|
||||
|
||||
if (!photo) { return redirect('/'); }
|
||||
|
||||
|
||||
@ -1,23 +1,20 @@
|
||||
import { auth } from '@/auth';
|
||||
import { getImageCacheHeadersForAuth } from '@/cache';
|
||||
import { getImageCacheHeadersForAuth, getPhotosCached } from '@/cache';
|
||||
import {
|
||||
IMAGE_OG_SMALL_SIZE,
|
||||
MAX_PHOTOS_TO_SHOW_PER_TAG,
|
||||
} from '@/photo/image-response';
|
||||
import TagImageResponse from '@/photo/image-response/TagImageResponse';
|
||||
import { getPhotos } from '@/services/postgres';
|
||||
import { getIBMPlexMonoMedium } from '@/site/font';
|
||||
import { ImageResponse } from 'next/server';
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
export async function GET(request: Request, context: any) {
|
||||
const photos = await getPhotos(
|
||||
undefined,
|
||||
MAX_PHOTOS_TO_SHOW_PER_TAG,
|
||||
undefined,
|
||||
context.params.tag,
|
||||
);
|
||||
const photos = await getPhotosCached({
|
||||
limit: MAX_PHOTOS_TO_SHOW_PER_TAG,
|
||||
tag: context.params.tag,
|
||||
});
|
||||
|
||||
const {
|
||||
fontFamily,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { getPhotosCached } from '@/cache';
|
||||
import SiteGrid from '@/components/SiteGrid';
|
||||
import PhotoGrid from '@/photo/PhotoGrid';
|
||||
import { getPhotos } from '@/services/postgres';
|
||||
import { absolutePathForTag, absolutePathForTagImage } from '@/site/paths';
|
||||
import { descriptionForTaggedPhotos, titleForTag } from '@/tag';
|
||||
import TagHeader from '@/tag/TagHeader';
|
||||
@ -13,7 +13,7 @@ interface TagProps {
|
||||
export async function generateMetadata({
|
||||
params: { tag },
|
||||
}: TagProps): Promise<Metadata> {
|
||||
const photos = await getPhotos(undefined, undefined, undefined, tag);
|
||||
const photos = await getPhotosCached({ tag });
|
||||
|
||||
const url = absolutePathForTag(tag);
|
||||
const title = titleForTag(tag, photos);
|
||||
@ -38,7 +38,7 @@ export async function generateMetadata({
|
||||
}
|
||||
|
||||
export default async function TagPage({ params: { tag } }: TagProps) {
|
||||
const photos = await getPhotos(undefined, undefined, undefined, tag);
|
||||
const photos = await getPhotosCached({ tag });
|
||||
|
||||
return (
|
||||
<SiteGrid
|
||||
|
||||
@ -1,19 +1,21 @@
|
||||
import { auth } from '@/auth';
|
||||
import { getImageCacheHeadersForAuth } from '@/cache';
|
||||
import { getImageCacheHeadersForAuth, getPhotosCached } from '@/cache';
|
||||
import {
|
||||
IMAGE_OG_SIZE,
|
||||
MAX_PHOTOS_TO_SHOW_TEMPLATE_TIGHT,
|
||||
} from '@/photo/image-response';
|
||||
import TemplateImageResponse from
|
||||
'@/photo/image-response/TemplateImageResponse';
|
||||
import { getPhotos } from '@/services/postgres';
|
||||
import { getIBMPlexMonoMedium } from '@/site/font';
|
||||
import { ImageResponse } from 'next/server';
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const photos = await getPhotos('priority', MAX_PHOTOS_TO_SHOW_TEMPLATE_TIGHT);
|
||||
const photos = await getPhotosCached({
|
||||
sortBy: 'priority',
|
||||
limit: MAX_PHOTOS_TO_SHOW_TEMPLATE_TIGHT,
|
||||
});
|
||||
|
||||
const {
|
||||
fontFamily,
|
||||
|
||||
@ -1,23 +1,27 @@
|
||||
import { auth } from '@/auth';
|
||||
import { getImageCacheHeadersForAuth } from '@/cache';
|
||||
import { getImageCacheHeadersForAuth, getPhotosCached } from '@/cache';
|
||||
import {
|
||||
GRID_OG_SIZE,
|
||||
MAX_PHOTOS_TO_SHOW_TEMPLATE,
|
||||
} from '@/photo/image-response';
|
||||
import TemplateImageResponse from
|
||||
'@/photo/image-response/TemplateImageResponse';
|
||||
import { getPhotos } from '@/services/postgres';
|
||||
import { getIBMPlexMonoMedium } from '@/site/font';
|
||||
import { ImageResponse } from 'next/server';
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const photos = await getPhotos('priority', MAX_PHOTOS_TO_SHOW_TEMPLATE);
|
||||
const photos = await getPhotosCached({
|
||||
sortBy: 'priority',
|
||||
limit: MAX_PHOTOS_TO_SHOW_TEMPLATE,
|
||||
});
|
||||
|
||||
const {
|
||||
fontFamily,
|
||||
fonts,
|
||||
} = await getIBMPlexMonoMedium();
|
||||
|
||||
const headers = await getImageCacheHeadersForAuth(await auth());
|
||||
|
||||
const { width, height } = GRID_OG_SIZE;
|
||||
|
||||
80
src/cache/index.ts
vendored
80
src/cache/index.ts
vendored
@ -1,40 +1,60 @@
|
||||
import { getPhoto, getPhotos } from '@/services/postgres';
|
||||
import type { Session } from 'next-auth/types';
|
||||
import { revalidatePath, revalidateTag, unstable_cache } from 'next/cache';
|
||||
import { revalidateTag, unstable_cache } from 'next/cache';
|
||||
import {
|
||||
GetPhotosOptions,
|
||||
getPhoto,
|
||||
getPhotos,
|
||||
getPhotosCount,
|
||||
getUniqueTags,
|
||||
} from '@/services/postgres';
|
||||
import { parseCachedPhotosDates, parseCachedPhotoDates } from '@/photo';
|
||||
|
||||
const TAG_PHOTOS = 'photos';
|
||||
const TAG_PHOTOS = 'photos';
|
||||
const TAG_PHOTOS_COUNT = 'photos-count';
|
||||
const TAG_TAGS = 'tags';
|
||||
|
||||
const PHOTO_PATHS = [
|
||||
'/',
|
||||
'/grid',
|
||||
'/p/[photoId]',
|
||||
'/p/[photoId]/share',
|
||||
'/p/[photoId]/image',
|
||||
'/t/[tag]',
|
||||
'/t/[tag]/[photoId]',
|
||||
'/t/[tag]/[photoId]/share',
|
||||
'/admin/photos',
|
||||
'/admin/photos/[photoId]',
|
||||
'/admin/photos/[photoId]/edit',
|
||||
];
|
||||
const getPhotosCacheTags = (options: GetPhotosOptions = {}) => {
|
||||
const tags = [TAG_PHOTOS];
|
||||
|
||||
const {
|
||||
sortBy,
|
||||
limit,
|
||||
offset,
|
||||
tag,
|
||||
takenAfterInclusive,
|
||||
takenBefore,
|
||||
} = options;
|
||||
|
||||
if (sortBy !== undefined) { tags.push(`sortBy-${sortBy}`); }
|
||||
if (limit !== undefined) { tags.push(`limit-${sortBy}`); }
|
||||
if (offset !== undefined) { tags.push(`offset-${sortBy}`); }
|
||||
if (tag !== undefined) { tags.push(`tag-${sortBy}`); }
|
||||
// eslint-disable-next-line max-len
|
||||
if (takenBefore !== undefined) { tags.push(`takenBefore-${takenBefore.toISOString()}`); }
|
||||
// eslint-disable-next-line max-len
|
||||
if (takenAfterInclusive !== undefined) { tags.push(`takenAfterInclusive-${takenAfterInclusive.toISOString()}`); }
|
||||
|
||||
return tags;
|
||||
};
|
||||
|
||||
const tagForPhoto = (photoId: string) => `photo-${photoId}`;
|
||||
|
||||
export const revalidatePhotosTag = (
|
||||
includePhotoPaths?: boolean
|
||||
) => {
|
||||
export const revalidatePhotosTag = () =>
|
||||
revalidateTag(TAG_PHOTOS);
|
||||
if (includePhotoPaths) { revalidateAllPhotoPaths(); }
|
||||
};
|
||||
|
||||
export const revalidateAllPhotoPaths = () =>
|
||||
PHOTO_PATHS.forEach(path => revalidatePath(path));
|
||||
|
||||
export const getPhotosCached: typeof getPhotos = (...args) =>
|
||||
unstable_cache(
|
||||
() => getPhotos(...args),
|
||||
[TAG_PHOTOS], {
|
||||
tags: [TAG_PHOTOS],
|
||||
getPhotosCacheTags(...args), {
|
||||
tags: getPhotosCacheTags(...args),
|
||||
}
|
||||
)().then(parseCachedPhotosDates);
|
||||
|
||||
export const getPhotosCountCached: typeof getPhotosCount = (...args) =>
|
||||
unstable_cache(
|
||||
() => getPhotosCount(...args),
|
||||
[TAG_PHOTOS, TAG_PHOTOS_COUNT], {
|
||||
tags: [TAG_PHOTOS, TAG_PHOTOS_COUNT],
|
||||
}
|
||||
)();
|
||||
|
||||
@ -44,6 +64,14 @@ export const getPhotoCached: typeof getPhoto = (...args) =>
|
||||
[TAG_PHOTOS, tagForPhoto(...args)], {
|
||||
tags: [TAG_PHOTOS, tagForPhoto(...args)],
|
||||
}
|
||||
)().then(photo => photo ? parseCachedPhotoDates(photo) : undefined);
|
||||
|
||||
export const getUniqueTagsCached: typeof getUniqueTags = (...args) =>
|
||||
unstable_cache(
|
||||
() => getUniqueTags(...args),
|
||||
[TAG_PHOTOS, TAG_TAGS], {
|
||||
tags: [TAG_PHOTOS, TAG_TAGS],
|
||||
}
|
||||
)();
|
||||
|
||||
export const getImageCacheHeadersForAuth = async (session?: Session) => {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { auth } from './auth';
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { NextApiRequest, NextApiResponse } from 'next';
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
|
||||
export default function middleware(req: NextRequest, res:NextResponse) {
|
||||
const pathname = req.nextUrl.pathname;
|
||||
|
||||
@ -25,7 +25,7 @@ export async function createPhotoAction(formData: FormData) {
|
||||
if (updatedUrl) { photo.url = updatedUrl; }
|
||||
await sqlInsertPhotoIntoDb(photo);
|
||||
|
||||
revalidatePhotosTag(true);
|
||||
revalidatePhotosTag();
|
||||
|
||||
redirect('/admin/photos');
|
||||
}
|
||||
@ -35,7 +35,7 @@ export async function updatePhotoAction(formData: FormData) {
|
||||
|
||||
await sqlUpdatePhotoInDb(photo);
|
||||
|
||||
revalidatePhotosTag(true);
|
||||
revalidatePhotosTag();
|
||||
|
||||
redirect('/admin/photos');
|
||||
}
|
||||
@ -44,7 +44,7 @@ export async function deletePhotoAction(formData: FormData) {
|
||||
await deleteBlobPhoto(formData.get('url') as string);
|
||||
await sqlDeletePhoto(formData.get('id') as string);
|
||||
|
||||
revalidatePhotosTag(true);
|
||||
revalidatePhotosTag();
|
||||
};
|
||||
|
||||
export async function deleteBlobPhotoAction(formData: FormData) {
|
||||
|
||||
@ -91,6 +91,16 @@ export const parsePhotoFromDb = (photoDbRaw: PhotoDb): Photo => {
|
||||
};
|
||||
};
|
||||
|
||||
export const parseCachedPhotoDates = (photo: Photo) => ({
|
||||
...photo,
|
||||
takenAt: new Date(photo.takenAt),
|
||||
updatedAt: new Date(photo.updatedAt),
|
||||
createdAt: new Date(photo.createdAt),
|
||||
});
|
||||
|
||||
export const parseCachedPhotosDates = (photos: Photo[]) =>
|
||||
photos.map(parseCachedPhotoDates);
|
||||
|
||||
export const convertPhotoToPhotoDbInsert = (
|
||||
photo: Photo,
|
||||
): PhotoDbInsert => ({
|
||||
|
||||
@ -136,8 +136,7 @@ const sqlGetPhotosFromDb = (
|
||||
SELECT * FROM photos
|
||||
ORDER BY taken_at DESC
|
||||
LIMIT ${limit} OFFSET ${offset}
|
||||
`
|
||||
.then(({ rows }) => rows.map(parsePhotoFromDb));
|
||||
`;
|
||||
|
||||
const sqlGetPhotosFromDbSortedByCreatedAt = (
|
||||
limit = PHOTO_DEFAULT_LIMIT,
|
||||
@ -147,8 +146,7 @@ const sqlGetPhotosFromDbSortedByCreatedAt = (
|
||||
SELECT * FROM photos
|
||||
ORDER BY created_at DESC
|
||||
LIMIT ${limit} OFFSET ${offset}
|
||||
`
|
||||
.then(({ rows }) => rows.map(parsePhotoFromDb));
|
||||
`;
|
||||
|
||||
const sqlGetPhotosFromDbSortedByPriority = (
|
||||
limit = PHOTO_DEFAULT_LIMIT,
|
||||
@ -158,8 +156,7 @@ const sqlGetPhotosFromDbSortedByPriority = (
|
||||
SELECT * FROM photos
|
||||
ORDER BY priority_order ASC, taken_at DESC
|
||||
LIMIT ${limit} OFFSET ${offset}
|
||||
`
|
||||
.then(({ rows }) => rows.map(parsePhotoFromDb));
|
||||
`;
|
||||
|
||||
const sqlGetPhotosFromDbByTag = (
|
||||
limit = PHOTO_DEFAULT_LIMIT,
|
||||
@ -170,43 +167,83 @@ const sqlGetPhotosFromDbByTag = (
|
||||
SELECT * FROM photos WHERE ${tag}=ANY(tags)
|
||||
ORDER BY taken_at ASC
|
||||
LIMIT ${limit} OFFSET ${offset}
|
||||
`
|
||||
.then(({ rows }) => rows.map(parsePhotoFromDb));
|
||||
`;
|
||||
|
||||
const sqlGetPhotosTakenAfterDateInclusive = (
|
||||
takenAt: Date,
|
||||
limit?: number,
|
||||
) =>
|
||||
sql<PhotoDb>`
|
||||
SELECT * FROM photos
|
||||
WHERE taken_at <= ${takenAt.toISOString()}
|
||||
ORDER BY taken_at DESC
|
||||
LIMIT ${limit}
|
||||
`;
|
||||
|
||||
const sqlGetPhotosTakenBeforeDate = (
|
||||
takenAt: Date,
|
||||
limit?: number,
|
||||
) =>
|
||||
sql<PhotoDb>`
|
||||
SELECT * FROM photos
|
||||
WHERE taken_at > ${takenAt.toISOString()}
|
||||
ORDER BY taken_at ASC
|
||||
LIMIT ${limit}
|
||||
`;
|
||||
|
||||
const sqlGetPhotoFromDb = (id: string) =>
|
||||
sql<PhotoDb>`SELECT * FROM photos WHERE id=${id} LIMIT 1`
|
||||
.then(({ rows }) => rows.map(parsePhotoFromDb));
|
||||
sql<PhotoDb>`SELECT * FROM photos WHERE id=${id} LIMIT 1`;
|
||||
|
||||
export type GetPhotosOptions = {
|
||||
sortBy?: 'createdAt' | 'takenAt' | 'priority'
|
||||
limit?: number
|
||||
offset?: number
|
||||
tag?: string
|
||||
takenBefore?: Date
|
||||
takenAfterInclusive?: Date
|
||||
}
|
||||
|
||||
export const getPhotos = async (options: GetPhotosOptions = {}) => {
|
||||
const {
|
||||
sortBy = 'takenAt',
|
||||
limit,
|
||||
offset,
|
||||
tag,
|
||||
takenBefore,
|
||||
takenAfterInclusive,
|
||||
} = options;
|
||||
|
||||
export const getPhotos = async (
|
||||
sortBy: 'createdAt' | 'takenAt' | 'priority' = 'takenAt',
|
||||
limit?: number,
|
||||
offset?: number,
|
||||
tag?: string,
|
||||
) => {
|
||||
let photos;
|
||||
|
||||
const getPhotosRequest = tag
|
||||
? () => sqlGetPhotosFromDbByTag(limit, offset, tag)
|
||||
: sortBy === 'createdAt'
|
||||
? () => sqlGetPhotosFromDbSortedByCreatedAt(limit, offset)
|
||||
: sortBy === 'priority'
|
||||
? () => sqlGetPhotosFromDbSortedByPriority(limit, offset)
|
||||
: () => sqlGetPhotosFromDb(limit, offset);
|
||||
const getPhotosRequest = takenBefore
|
||||
? () => sqlGetPhotosTakenBeforeDate(takenBefore, limit)
|
||||
: takenAfterInclusive
|
||||
? () => sqlGetPhotosTakenAfterDateInclusive(takenAfterInclusive, limit)
|
||||
: tag
|
||||
? () => sqlGetPhotosFromDbByTag(limit, offset, tag)
|
||||
: sortBy === 'createdAt'
|
||||
? () => sqlGetPhotosFromDbSortedByCreatedAt(limit, offset)
|
||||
: sortBy === 'priority'
|
||||
? () => sqlGetPhotosFromDbSortedByPriority(limit, offset)
|
||||
: () => sqlGetPhotosFromDb(limit, offset);
|
||||
|
||||
const getPhotosRequestAndParse = () =>
|
||||
getPhotosRequest().then(({ rows }) => rows.map(parsePhotoFromDb));
|
||||
|
||||
try {
|
||||
photos = await getPhotosRequest();
|
||||
photos = await getPhotosRequestAndParse();
|
||||
} catch (e: any) {
|
||||
if (/relation "photos" does not exist/i.test(e.message)) {
|
||||
console.log(
|
||||
'Creating table "photos" because it did not exist',
|
||||
);
|
||||
await sqlCreatePhotosTable();
|
||||
photos = await getPhotosRequest();
|
||||
photos = await getPhotosRequestAndParse();
|
||||
} else if (/endpoint is in transition/i.test(e.message)) {
|
||||
// Wait 5 seconds and try again
|
||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||
try {
|
||||
photos = await getPhotosRequest();
|
||||
photos = await getPhotosRequestAndParse();
|
||||
} catch (e: any) {
|
||||
console.log(`sql get error on retry (after 5000ms): ${e.message} `);
|
||||
throw e;
|
||||
@ -220,30 +257,6 @@ export const getPhotos = async (
|
||||
return photos;
|
||||
};
|
||||
|
||||
export const getPhotosTakenAfterPhotoInclusive = (
|
||||
photo: Photo,
|
||||
limit?: number,
|
||||
) =>
|
||||
sql<PhotoDb>`
|
||||
SELECT * FROM photos
|
||||
WHERE taken_at <= ${photo.takenAt.toISOString()}
|
||||
ORDER BY taken_at DESC
|
||||
LIMIT ${limit}
|
||||
`
|
||||
.then(({ rows }) => rows.map(parsePhotoFromDb));
|
||||
|
||||
export const getPhotosTakenBeforePhoto = (
|
||||
photo: Photo,
|
||||
limit?: number,
|
||||
) =>
|
||||
sql<PhotoDb>`
|
||||
SELECT * FROM photos
|
||||
WHERE taken_at > ${photo.takenAt.toISOString()}
|
||||
ORDER BY taken_at ASC
|
||||
LIMIT ${limit}
|
||||
`
|
||||
.then(({ rows }) => rows.map(parsePhotoFromDb));
|
||||
|
||||
export const getPhotosCount = async () => sql`
|
||||
SELECT COUNT(*) FROM photos
|
||||
`.then(({ rows }) => parseInt(rows[0].count, 10));
|
||||
@ -257,5 +270,6 @@ export const getPhoto = async (id: string): Promise<Photo | undefined> => {
|
||||
// and convert short ids to uuids
|
||||
const photoId = translatePhotoId(id);
|
||||
return sqlGetPhotoFromDb(photoId)
|
||||
.then(({ rows }) => rows.map(parsePhotoFromDb))
|
||||
.then(photos => photos.length > 0 ? photos[0] : undefined);
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user