diff --git a/.vscode/settings.json b/.vscode/settings.json index 082da5fb..e91d08d0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,6 +3,7 @@ "ABCDEFGHIJKLMNOP", "Acros", "affordance", + "apos", "ARROWLEFT", "ARROWRIGHT", "Astia", diff --git a/app/feed.json/route.ts b/app/feed.json/route.ts index 3078cdfd..19f83e2f 100644 --- a/app/feed.json/route.ts +++ b/app/feed.json/route.ts @@ -6,7 +6,8 @@ import { } from '@/app/config'; import { FEED_PHOTO_REQUEST_LIMIT, formatPhotoForFeedJson } from '@/app/feed'; -export const dynamic = 'force-static'; +// Cache for 24 hours +export const revalidate = 86_400; export async function GET() { if (SITE_FEEDS_ENABLED) { diff --git a/app/rss.xml/route.ts b/app/rss.xml/route.ts index 100065d3..4f67951e 100644 --- a/app/rss.xml/route.ts +++ b/app/rss.xml/route.ts @@ -8,7 +8,8 @@ import { import { createRssItems, FEED_PHOTO_REQUEST_LIMIT } from '@/app/feed'; import { ABSOLUTE_PATH_FOR_RSS_XML } from '@/app/paths'; -export const dynamic = 'force-static'; +// Cache for 24 hours +export const revalidate = 86_400; export async function GET() { if (SITE_FEEDS_ENABLED) { @@ -19,18 +20,19 @@ export async function GET() { const items = createRssItems(photos); - return new Response(` - - + ${META_TITLE} - + ${BASE_URL} ${META_DESCRIPTION} ${items.join('\n')} @@ -40,6 +42,6 @@ export async function GET() { { headers: { 'Content-Type': 'text/xml' } }, ); } else { - return new Response('RSS feed access disabled', { status: 404 }); + return new Response('Feed disabled', { status: 404 }); } } diff --git a/src/app/feed.ts b/src/app/feed.ts index cf9ddd9f..3395c7b0 100644 --- a/src/app/feed.ts +++ b/src/app/feed.ts @@ -5,6 +5,7 @@ import { NextImageSize, } from '@/platforms/next-image'; import { formatDate, formatDateFromPostgresString } from '@/utility/date'; +import { formatStringForXml } from '@/utility/string'; export const FEED_PHOTO_REQUEST_LIMIT = 40; @@ -12,44 +13,44 @@ export const FEED_PHOTO_WIDTH_SMALL = 200; export const FEED_PHOTO_WIDTH_MEDIUM = 640; export const FEED_PHOTO_WIDTH_LARGE = 1200; -interface PublicFeedMedia { +interface FeedMedia { url: string width: number height: number } -interface PublicFeedPhotoJson { +interface FeedPhotoJson { id: string - title?: string + title: string url: string make?: string model?: string tags?: string[] takenAtNaive: string - src: Record<'small' | 'medium' | 'large', PublicFeedMedia> + src: Record<'small' | 'medium' | 'large', FeedMedia> } -interface PublicFeedPhotoRss { +interface FeedPhotoRss { id: string - title?: string + title: string description?: string link: string - publicationDate: Date - media: Record<'content' | 'thumb', PublicFeedMedia> + pubDate: Date + media: Record<'content' | 'thumb', FeedMedia> } -export interface PublicFeedJson { +export interface FeedJson { meta: { title: string url: string } - photos: PublicFeedPhotoJson[] + photos: FeedPhotoJson[] } const generateFeedMedia = ( photo: Photo, size: NextImageSize, -): PublicFeedMedia => ({ +): FeedMedia => ({ url: getNextImageUrlForRequest({ imageUrl: photo.url, size }), width: size, height: Math.round(size / photo.aspectRatio), @@ -58,10 +59,10 @@ const generateFeedMedia = ( const getCoreFeedFields = (photo: Photo) => ({ id: photo.id, title: titleForPhoto(photo), - description: descriptionForPhoto(photo), + description: descriptionForPhoto(photo, true), }); -export const formatPhotoForFeedJson = (photo: Photo): PublicFeedPhotoJson => ({ +export const formatPhotoForFeedJson = (photo: Photo): FeedPhotoJson => ({ ...getCoreFeedFields(photo), url: absolutePathForPhoto({ photo }), ...photo.make && { make: photo.make }, @@ -75,37 +76,35 @@ export const formatPhotoForFeedJson = (photo: Photo): PublicFeedPhotoJson => ({ }, }); -const formatPhotoForFeedRss = (photo: Photo): PublicFeedPhotoRss => ({ +const formatPhotoForFeedRss = (photo: Photo): FeedPhotoRss => ({ ...getCoreFeedFields(photo), link: absolutePathForPhoto({ photo }), - publicationDate: photo.createdAt, + pubDate: photo.createdAt, media: { content: generateFeedMedia(photo, FEED_PHOTO_WIDTH_LARGE), thumb: generateFeedMedia(photo, FEED_PHOTO_WIDTH_MEDIUM), }, }); -const feedPhotoToXml = (photo: PublicFeedPhotoRss): string => { - const formattedDate = formatDate({ - date: photo.publicationDate, - length: 'rss', - }); +const feedPhotoToXml = (photo: FeedPhotoRss): string => { + const formattedDate = formatDate({ date: photo.pubDate, length: 'rss' }); return ` ${photo.title} ${photo.link} ${formattedDate} ${photo.link} - - - - ` + : ''} + diff --git a/src/utility/string.ts b/src/utility/string.ts index 71b3b633..46dd4527 100644 --- a/src/utility/string.ts +++ b/src/utility/string.ts @@ -35,6 +35,14 @@ export const parameterize = ( ) .toLocaleLowerCase(); +export const formatStringForXml = (string: string) => + string + .replace(/&/g, '&') + .replace(/'/g, ''') + .replace(/"/g, '"') + .replace(//g, '>'); + export const deparameterize = (string: string) => capitalizeWords(string.replaceAll('-', ' '));