Finalize feed behavior
This commit is contained in:
parent
099fcdec8b
commit
9eb04f6015
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@ -3,6 +3,7 @@
|
|||||||
"ABCDEFGHIJKLMNOP",
|
"ABCDEFGHIJKLMNOP",
|
||||||
"Acros",
|
"Acros",
|
||||||
"affordance",
|
"affordance",
|
||||||
|
"apos",
|
||||||
"ARROWLEFT",
|
"ARROWLEFT",
|
||||||
"ARROWRIGHT",
|
"ARROWRIGHT",
|
||||||
"Astia",
|
"Astia",
|
||||||
|
|||||||
@ -6,7 +6,8 @@ import {
|
|||||||
} from '@/app/config';
|
} from '@/app/config';
|
||||||
import { FEED_PHOTO_REQUEST_LIMIT, formatPhotoForFeedJson } from '@/app/feed';
|
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() {
|
export async function GET() {
|
||||||
if (SITE_FEEDS_ENABLED) {
|
if (SITE_FEEDS_ENABLED) {
|
||||||
|
|||||||
@ -8,7 +8,8 @@ import {
|
|||||||
import { createRssItems, FEED_PHOTO_REQUEST_LIMIT } from '@/app/feed';
|
import { createRssItems, FEED_PHOTO_REQUEST_LIMIT } from '@/app/feed';
|
||||||
import { ABSOLUTE_PATH_FOR_RSS_XML } from '@/app/paths';
|
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() {
|
export async function GET() {
|
||||||
if (SITE_FEEDS_ENABLED) {
|
if (SITE_FEEDS_ENABLED) {
|
||||||
@ -19,18 +20,19 @@ export async function GET() {
|
|||||||
|
|
||||||
const items = createRssItems(photos);
|
const items = createRssItems(photos);
|
||||||
|
|
||||||
return new Response(`
|
return new Response(`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<rss version="2.0"
|
||||||
<rss
|
|
||||||
version="2.0"
|
|
||||||
xmlns:content="http://purl.org/rss/1.0/modules/content/"
|
xmlns:content="http://purl.org/rss/1.0/modules/content/"
|
||||||
xmlns:atom="http://www.w3.org/2005/Atom"
|
xmlns:atom="http://www.w3.org/2005/Atom"
|
||||||
xmlns:media="http://search.yahoo.com/mrss/"
|
xmlns:media="http://search.yahoo.com/mrss/"
|
||||||
>
|
>
|
||||||
<channel>
|
<channel>
|
||||||
<title>${META_TITLE}</title>
|
<title>${META_TITLE}</title>
|
||||||
<atom:link href="${ABSOLUTE_PATH_FOR_RSS_XML}"
|
<atom:link
|
||||||
rel="self" type="application/rss+xml" />
|
href="${ABSOLUTE_PATH_FOR_RSS_XML}"
|
||||||
|
rel="self"
|
||||||
|
type="application/rss+xml"
|
||||||
|
/>
|
||||||
<link>${BASE_URL}</link>
|
<link>${BASE_URL}</link>
|
||||||
<description>${META_DESCRIPTION}</description>
|
<description>${META_DESCRIPTION}</description>
|
||||||
${items.join('\n')}
|
${items.join('\n')}
|
||||||
@ -40,6 +42,6 @@ export async function GET() {
|
|||||||
{ headers: { 'Content-Type': 'text/xml' } },
|
{ headers: { 'Content-Type': 'text/xml' } },
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return new Response('RSS feed access disabled', { status: 404 });
|
return new Response('Feed disabled', { status: 404 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import {
|
|||||||
NextImageSize,
|
NextImageSize,
|
||||||
} from '@/platforms/next-image';
|
} from '@/platforms/next-image';
|
||||||
import { formatDate, formatDateFromPostgresString } from '@/utility/date';
|
import { formatDate, formatDateFromPostgresString } from '@/utility/date';
|
||||||
|
import { formatStringForXml } from '@/utility/string';
|
||||||
|
|
||||||
export const FEED_PHOTO_REQUEST_LIMIT = 40;
|
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_MEDIUM = 640;
|
||||||
export const FEED_PHOTO_WIDTH_LARGE = 1200;
|
export const FEED_PHOTO_WIDTH_LARGE = 1200;
|
||||||
|
|
||||||
interface PublicFeedMedia {
|
interface FeedMedia {
|
||||||
url: string
|
url: string
|
||||||
width: number
|
width: number
|
||||||
height: number
|
height: number
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PublicFeedPhotoJson {
|
interface FeedPhotoJson {
|
||||||
id: string
|
id: string
|
||||||
title?: string
|
title: string
|
||||||
url: string
|
url: string
|
||||||
make?: string
|
make?: string
|
||||||
model?: string
|
model?: string
|
||||||
tags?: string[]
|
tags?: string[]
|
||||||
takenAtNaive: string
|
takenAtNaive: string
|
||||||
src: Record<'small' | 'medium' | 'large', PublicFeedMedia>
|
src: Record<'small' | 'medium' | 'large', FeedMedia>
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PublicFeedPhotoRss {
|
interface FeedPhotoRss {
|
||||||
id: string
|
id: string
|
||||||
title?: string
|
title: string
|
||||||
description?: string
|
description?: string
|
||||||
link: string
|
link: string
|
||||||
publicationDate: Date
|
pubDate: Date
|
||||||
media: Record<'content' | 'thumb', PublicFeedMedia>
|
media: Record<'content' | 'thumb', FeedMedia>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PublicFeedJson {
|
export interface FeedJson {
|
||||||
meta: {
|
meta: {
|
||||||
title: string
|
title: string
|
||||||
url: string
|
url: string
|
||||||
}
|
}
|
||||||
photos: PublicFeedPhotoJson[]
|
photos: FeedPhotoJson[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const generateFeedMedia = (
|
const generateFeedMedia = (
|
||||||
photo: Photo,
|
photo: Photo,
|
||||||
size: NextImageSize,
|
size: NextImageSize,
|
||||||
): PublicFeedMedia => ({
|
): FeedMedia => ({
|
||||||
url: getNextImageUrlForRequest({ imageUrl: photo.url, size }),
|
url: getNextImageUrlForRequest({ imageUrl: photo.url, size }),
|
||||||
width: size,
|
width: size,
|
||||||
height: Math.round(size / photo.aspectRatio),
|
height: Math.round(size / photo.aspectRatio),
|
||||||
@ -58,10 +59,10 @@ const generateFeedMedia = (
|
|||||||
const getCoreFeedFields = (photo: Photo) => ({
|
const getCoreFeedFields = (photo: Photo) => ({
|
||||||
id: photo.id,
|
id: photo.id,
|
||||||
title: titleForPhoto(photo),
|
title: titleForPhoto(photo),
|
||||||
description: descriptionForPhoto(photo),
|
description: descriptionForPhoto(photo, true),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const formatPhotoForFeedJson = (photo: Photo): PublicFeedPhotoJson => ({
|
export const formatPhotoForFeedJson = (photo: Photo): FeedPhotoJson => ({
|
||||||
...getCoreFeedFields(photo),
|
...getCoreFeedFields(photo),
|
||||||
url: absolutePathForPhoto({ photo }),
|
url: absolutePathForPhoto({ photo }),
|
||||||
...photo.make && { make: photo.make },
|
...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),
|
...getCoreFeedFields(photo),
|
||||||
link: absolutePathForPhoto({ photo }),
|
link: absolutePathForPhoto({ photo }),
|
||||||
publicationDate: photo.createdAt,
|
pubDate: photo.createdAt,
|
||||||
media: {
|
media: {
|
||||||
content: generateFeedMedia(photo, FEED_PHOTO_WIDTH_LARGE),
|
content: generateFeedMedia(photo, FEED_PHOTO_WIDTH_LARGE),
|
||||||
thumb: generateFeedMedia(photo, FEED_PHOTO_WIDTH_MEDIUM),
|
thumb: generateFeedMedia(photo, FEED_PHOTO_WIDTH_MEDIUM),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const feedPhotoToXml = (photo: PublicFeedPhotoRss): string => {
|
const feedPhotoToXml = (photo: FeedPhotoRss): string => {
|
||||||
const formattedDate = formatDate({
|
const formattedDate = formatDate({ date: photo.pubDate, length: 'rss' });
|
||||||
date: photo.publicationDate,
|
|
||||||
length: 'rss',
|
|
||||||
});
|
|
||||||
return `<item>
|
return `<item>
|
||||||
<title>${photo.title}</title>
|
<title>${photo.title}</title>
|
||||||
<link>${photo.link}</link>
|
<link>${photo.link}</link>
|
||||||
<pubDate>${formattedDate}</pubDate>
|
<pubDate>${formattedDate}</pubDate>
|
||||||
<guid isPermaLink="true">${photo.link}</guid>
|
<guid isPermaLink="true">${photo.link}</guid>
|
||||||
<description>
|
${photo.description
|
||||||
<![CDATA[${photo.description}]]>
|
? `<description><![CDATA[${photo.description}]]></description>`
|
||||||
</description>
|
: ''}
|
||||||
<media:content url="<![CDATA[${photo.media.content.url}]]>"
|
<media:content
|
||||||
|
url="${formatStringForXml(photo.media.content.url)}"
|
||||||
type="image/jpeg"
|
type="image/jpeg"
|
||||||
medium="image"
|
medium="image"
|
||||||
width="${photo.media.content.width}"
|
width="${photo.media.content.width}"
|
||||||
height="${photo.media.content.height}"
|
height="${photo.media.content.height}"
|
||||||
>
|
>
|
||||||
<media:thumbnail
|
<media:thumbnail
|
||||||
url="<![CDATA[${photo.media.thumb.url}]]>"
|
url="${formatStringForXml(photo.media.thumb.url)}"
|
||||||
width="${photo.media.thumb.width}"
|
width="${photo.media.thumb.width}"
|
||||||
height="${photo.media.thumb.height}"
|
height="${photo.media.thumb.height}"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -35,6 +35,14 @@ export const parameterize = (
|
|||||||
)
|
)
|
||||||
.toLocaleLowerCase();
|
.toLocaleLowerCase();
|
||||||
|
|
||||||
|
export const formatStringForXml = (string: string) =>
|
||||||
|
string
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.replace(/'/g, ''')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>');
|
||||||
|
|
||||||
export const deparameterize = (string: string) =>
|
export const deparameterize = (string: string) =>
|
||||||
capitalizeWords(string.replaceAll('-', ' '));
|
capitalizeWords(string.replaceAll('-', ' '));
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user