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