Begin combining feed and api

This commit is contained in:
Sam Becker 2025-06-12 16:31:45 -05:00
parent 0236461d99
commit ec698b61de
2 changed files with 91 additions and 53 deletions

View File

@ -1,11 +1,11 @@
import { getPhotosCached } from '@/photo/cache'; import { getPhotosCached } from '@/photo/cache';
import { INFINITE_SCROLL_FEED_INITIAL } from '@/photo'; import { INFINITE_SCROLL_FEED_INITIAL } from '@/photo';
import { formatPhotoForFeed } from '@/app/feed';
import { import {
BASE_URL, BASE_URL,
PUBLIC_FEED_ENABLED, PUBLIC_FEED_ENABLED,
META_TITLE, META_TITLE,
} from '@/app/config'; } from '@/app/config';
import { formatPhotoForFeedJson } from '@/app/feed';
export const dynamic = 'force-static'; export const dynamic = 'force-static';
@ -20,7 +20,7 @@ export async function GET() {
title: META_TITLE, title: META_TITLE,
url: BASE_URL, url: BASE_URL,
}, },
photos: photos.map(formatPhotoForFeed), photos: photos.map(formatPhotoForFeedJson),
}); });
} else { } else {
return new Response('Feed disabled', { status: 404 }); return new Response('Feed disabled', { status: 404 });

View File

@ -1,17 +1,31 @@
import { Photo } from '@/photo'; import { descriptionForPhoto, Photo, titleForPhoto } from '@/photo';
import { absolutePathForPhoto } from './paths'; import { absolutePathForPhoto } from './paths';
import { getNextImageUrlForRequest } from '@/platforms/next-image'; import {
import { formatDate } from '@/utility/date'; getNextImageUrlForRequest,
NextImageSize,
} from '@/platforms/next-image';
import { formatDate, formatDateFromPostgresString } from '@/utility/date';
export const FEED_PHOTO_WIDTH_CONTENT = 1080; export const API_PHOTO_REQUEST_LIMIT = 40;
export const FEED_PHOTO_WIDTH_THUMB = 640;
export interface PublicFeed { export const FEED_PHOTO_WIDTH_SMALL = 200;
export const FEED_PHOTO_WIDTH_MEDIUM = 640;
export const FEED_PHOTO_WIDTH_LARGE = 1200;
export interface PublicFeedJson {
meta: { meta: {
title: string title: string
url: string url: string
} }
photos: PublicFeedPhoto[] photos: PublicFeedPhotoJson[]
}
export interface PublicFeedRss {
meta: {
title: string
url: string
}
photos: PublicFeedPhotoRss[]
} }
interface PublicFeedMedia { interface PublicFeedMedia {
@ -20,7 +34,21 @@ interface PublicFeedMedia {
height: number height: number
} }
interface PublicFeedPhoto { interface PublicFeedPhotoJson {
id: string
title?: string
url: string
make?: string
model?: string
tags?: string[]
takenAtNaive: string
src: Record<
'small' | 'medium' | 'large',
PublicFeedMedia
>
}
interface PublicFeedPhotoRss {
id: string id: string
title?: string title?: string
description?: string description?: string
@ -32,56 +60,66 @@ interface PublicFeedPhoto {
> >
} }
export const formatPhotoForFeed = (photo: Photo): PublicFeedPhoto => ({ const generateFeedMedia = (
photo: Photo,
size: NextImageSize,
): PublicFeedMedia => ({
url: getNextImageUrlForRequest({ imageUrl: photo.url, size }),
width: size,
height: Math.round(size / photo.aspectRatio),
});
const getCoreFeedFields = (photo: Photo) => ({
id: photo.id, id: photo.id,
title: photo.title, title: titleForPhoto(photo),
description: photo.caption, description: descriptionForPhoto(photo),
link: absolutePathForPhoto({ photo }), });
publicationDate: photo.createdAt,
media: { export const formatPhotoForFeedJson = (photo: Photo): PublicFeedPhotoJson => ({
content: { ...getCoreFeedFields(photo),
url: getNextImageUrlForRequest({ url: absolutePathForPhoto({ photo }),
imageUrl: photo.url, ...photo.make && { make: photo.make },
size: FEED_PHOTO_WIDTH_CONTENT, ...photo.model && { model: photo.model },
}), ...photo.tags.length > 0 && { tags: photo.tags },
width: FEED_PHOTO_WIDTH_CONTENT, takenAtNaive: formatDateFromPostgresString(photo.takenAtNaive),
height: Math.round(FEED_PHOTO_WIDTH_CONTENT / photo.aspectRatio), src: {
}, small: generateFeedMedia(photo, FEED_PHOTO_WIDTH_SMALL),
thumb: { medium: generateFeedMedia(photo, FEED_PHOTO_WIDTH_MEDIUM),
url: getNextImageUrlForRequest({ large: generateFeedMedia(photo, FEED_PHOTO_WIDTH_LARGE),
imageUrl: photo.url,
size: FEED_PHOTO_WIDTH_THUMB,
}),
width: FEED_PHOTO_WIDTH_THUMB,
height: Math.round(FEED_PHOTO_WIDTH_THUMB / photo.aspectRatio),
},
}, },
}); });
export const feedPhotoToXml = (photo: PublicFeedPhoto): string => { export const formatPhotoForFeedRss = (photo: Photo): PublicFeedPhotoRss => ({
...getCoreFeedFields(photo),
link: absolutePathForPhoto({ photo }),
publicationDate: photo.createdAt,
media: {
content: generateFeedMedia(photo, FEED_PHOTO_WIDTH_LARGE),
thumb: generateFeedMedia(photo, FEED_PHOTO_WIDTH_MEDIUM),
},
});
export const feedPhotoToXml = (photo: PublicFeedPhotoRss): string => {
const formattedDate = formatDate({ const formattedDate = formatDate({
date: photo.publicationDate, date: photo.publicationDate,
length: 'rss', length: 'rss',
}); });
const description = photo.description ? return `<item>
`<description> <title>${photo.title}</title>
<link>${photo.link}</link>
<pubDate>${formattedDate}</pubDate>
<guid isPermaLink="true">${photo.link}</guid>
<description>
<![CDATA[${photo.description}]]> <![CDATA[${photo.description}]]>
</description>` : ''; </description>
<media:content url="${photo.media.content.url.replace(/&/g, '&amp;')}"
return ` <item> type="image/jpeg"
<title>${photo.title}</title> medium="image"
<link>${photo.link}</link> width="${photo.media.content.width}"
<pubDate>${formattedDate}</pubDate> height="${photo.media.content.height}">
<guid isPermaLink="true">${photo.link}</guid> <media:thumbnail url="${photo.media.thumb.url.replace(/&/g, '&amp;')}"
${description} width="${photo.media.thumb.width}"
<media:content url="${photo.media.content.url.replace(/&/g, '&amp;')}" height="${photo.media.thumb.height}" />
type="image/jpeg" </media:content>
medium="image" </item>`;
width="${photo.media.content.width}"
height="${photo.media.content.height}">
<media:thumbnail url="${photo.media.thumb.url.replace(/&/g, '&amp;')}"
width="${photo.media.thumb.width}"
height="${photo.media.thumb.height}" />
</media:content>
</item>`;
}; };