Add rss.xml and feed.json endpoints
This commit is contained in:
parent
28b1c92edb
commit
3c4adc2f9e
28
app/feed.json/route.ts
Normal file
28
app/feed.json/route.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { getPhotosCached } from '@/photo/cache';
|
||||
import { INFINITE_SCROLL_FEED_INITIAL } from '@/photo';
|
||||
import { formatPhotoForFeed } from '@/app/feed';
|
||||
import {
|
||||
BASE_URL,
|
||||
PUBLIC_FEED_ENABLED,
|
||||
META_TITLE,
|
||||
} from '@/app/config';
|
||||
|
||||
export const dynamic = 'force-static';
|
||||
|
||||
export async function GET() {
|
||||
if (PUBLIC_FEED_ENABLED) {
|
||||
const photos = await getPhotosCached({
|
||||
limit: INFINITE_SCROLL_FEED_INITIAL,
|
||||
sortBy: 'createdAt',
|
||||
});
|
||||
return Response.json({
|
||||
meta: {
|
||||
title: META_TITLE,
|
||||
url: BASE_URL,
|
||||
},
|
||||
photos: photos.map(formatPhotoForFeed),
|
||||
});
|
||||
} else {
|
||||
return new Response('Feed disabled', { status: 404 });
|
||||
}
|
||||
}
|
||||
48
app/rss.xml/route.ts
Normal file
48
app/rss.xml/route.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import { INFINITE_SCROLL_FEED_INITIAL } from '@/photo';
|
||||
import { getPhotosCached } from '@/photo/cache';
|
||||
import {
|
||||
BASE_URL,
|
||||
META_DESCRIPTION,
|
||||
META_TITLE,
|
||||
PUBLIC_FEED_ENABLED,
|
||||
} from '@/app/config';
|
||||
import { feedPhotoToXml, formatPhotoForFeed } from '@/app/feed';
|
||||
|
||||
export const dynamic = 'force-static';
|
||||
|
||||
export async function GET() {
|
||||
if (PUBLIC_FEED_ENABLED) {
|
||||
const photos = await getPhotosCached({
|
||||
limit: INFINITE_SCROLL_FEED_INITIAL,
|
||||
sortBy: 'createdAt',
|
||||
});
|
||||
const items = photos.map(formatPhotoForFeed).map(feedPhotoToXml);
|
||||
|
||||
return new Response(
|
||||
`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"
|
||||
xmlns:atom="http://www.w3.org/2005/Atom"
|
||||
xmlns:media="http://search.yahoo.com/mrss/">
|
||||
|
||||
<channel>
|
||||
<title>${META_TITLE}</title>
|
||||
<atom:link href="${BASE_URL}/rss.xml"
|
||||
rel="self" type="application/rss+xml" />
|
||||
<link>${BASE_URL}</link>
|
||||
<description>${META_DESCRIPTION}</description>
|
||||
|
||||
${items.join('\n\n ')}
|
||||
|
||||
</channel>
|
||||
|
||||
</rss>`,
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'text/xml',
|
||||
},
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return new Response('RSS feed access disabled', { status: 404 });
|
||||
}
|
||||
}
|
||||
87
src/app/feed.ts
Normal file
87
src/app/feed.ts
Normal file
@ -0,0 +1,87 @@
|
||||
import { Photo } from '@/photo';
|
||||
import { absolutePathForPhoto } from './paths';
|
||||
import { getNextImageUrlForRequest } from '@/platforms/next-image';
|
||||
import { formatDate } from '@/utility/date';
|
||||
|
||||
export const FEED_PHOTO_WIDTH_CONTENT = 1080;
|
||||
export const FEED_PHOTO_WIDTH_THUMB = 640;
|
||||
|
||||
export interface PublicFeed {
|
||||
meta: {
|
||||
title: string
|
||||
url: string
|
||||
}
|
||||
photos: PublicFeedPhoto[]
|
||||
}
|
||||
|
||||
interface PublicFeedMedia {
|
||||
url: string
|
||||
width: number
|
||||
height: number
|
||||
}
|
||||
|
||||
interface PublicFeedPhoto {
|
||||
id: string
|
||||
title?: string
|
||||
description?: string
|
||||
link: string
|
||||
publicationDate: Date
|
||||
media: Record<
|
||||
'content' | 'thumb',
|
||||
PublicFeedMedia
|
||||
>
|
||||
}
|
||||
|
||||
export const formatPhotoForFeed = (photo: Photo): PublicFeedPhoto => ({
|
||||
id: photo.id,
|
||||
title: photo.title,
|
||||
description: photo.caption,
|
||||
link: absolutePathForPhoto({ photo }),
|
||||
publicationDate: photo.createdAt,
|
||||
media: {
|
||||
content: {
|
||||
url: getNextImageUrlForRequest({
|
||||
imageUrl: photo.url,
|
||||
size: FEED_PHOTO_WIDTH_CONTENT,
|
||||
}),
|
||||
width: FEED_PHOTO_WIDTH_CONTENT,
|
||||
height: Math.round(FEED_PHOTO_WIDTH_CONTENT / photo.aspectRatio),
|
||||
},
|
||||
thumb: {
|
||||
url: getNextImageUrlForRequest({
|
||||
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 => {
|
||||
const formattedDate = formatDate({
|
||||
date: photo.publicationDate,
|
||||
length: 'rss',
|
||||
});
|
||||
const description = photo.description ?
|
||||
`<description>
|
||||
<![CDATA[${photo.description}]]>
|
||||
</description>` : '';
|
||||
|
||||
return ` <item>
|
||||
<title>${photo.title}</title>
|
||||
<link>${photo.link}</link>
|
||||
<pubDate>${formattedDate}</pubDate>
|
||||
<guid isPermaLink="true">${photo.link}</guid>
|
||||
${description}
|
||||
<media:content url="${photo.media.content.url.replace(/&/g, '&')}"
|
||||
type="image/jpeg"
|
||||
medium="image"
|
||||
width="${photo.media.content.width}"
|
||||
height="${photo.media.content.height}">
|
||||
<media:thumbnail url="${photo.media.thumb.url.replace(/&/g, '&')}"
|
||||
width="${photo.media.thumb.width}"
|
||||
height="${photo.media.thumb.height}" />
|
||||
</media:content>
|
||||
</item>`;
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user