Create feed module
This commit is contained in:
parent
9eb04f6015
commit
4d904517a5
@ -4,7 +4,8 @@ import {
|
||||
SITE_FEEDS_ENABLED,
|
||||
META_TITLE,
|
||||
} from '@/app/config';
|
||||
import { FEED_PHOTO_REQUEST_LIMIT, formatPhotoForFeedJson } from '@/app/feed';
|
||||
import { FEED_PHOTO_REQUEST_LIMIT } from '@/feed';
|
||||
import { formatPhotoForFeedJson } from '@/feed/json';
|
||||
|
||||
// Cache for 24 hours
|
||||
export const revalidate = 86_400;
|
||||
|
||||
@ -5,7 +5,8 @@ import {
|
||||
META_TITLE,
|
||||
SITE_FEEDS_ENABLED,
|
||||
} from '@/app/config';
|
||||
import { createRssItems, FEED_PHOTO_REQUEST_LIMIT } from '@/app/feed';
|
||||
import { FEED_PHOTO_REQUEST_LIMIT } from '@/feed';
|
||||
import { createRssItems } from '@/feed/rss';
|
||||
import { ABSOLUTE_PATH_FOR_RSS_XML } from '@/app/paths';
|
||||
|
||||
// Cache for 24 hours
|
||||
|
||||
116
src/app/feed.ts
116
src/app/feed.ts
@ -1,116 +0,0 @@
|
||||
import { descriptionForPhoto, Photo, titleForPhoto } from '@/photo';
|
||||
import { absolutePathForPhoto } from './paths';
|
||||
import {
|
||||
getNextImageUrlForRequest,
|
||||
NextImageSize,
|
||||
} from '@/platforms/next-image';
|
||||
import { formatDate, formatDateFromPostgresString } from '@/utility/date';
|
||||
import { formatStringForXml } from '@/utility/string';
|
||||
|
||||
export const FEED_PHOTO_REQUEST_LIMIT = 40;
|
||||
|
||||
export const FEED_PHOTO_WIDTH_SMALL = 200;
|
||||
export const FEED_PHOTO_WIDTH_MEDIUM = 640;
|
||||
export const FEED_PHOTO_WIDTH_LARGE = 1200;
|
||||
|
||||
interface FeedMedia {
|
||||
url: string
|
||||
width: number
|
||||
height: number
|
||||
}
|
||||
|
||||
interface FeedPhotoJson {
|
||||
id: string
|
||||
title: string
|
||||
url: string
|
||||
make?: string
|
||||
model?: string
|
||||
tags?: string[]
|
||||
takenAtNaive: string
|
||||
src: Record<'small' | 'medium' | 'large', FeedMedia>
|
||||
}
|
||||
|
||||
interface FeedPhotoRss {
|
||||
id: string
|
||||
title: string
|
||||
description?: string
|
||||
link: string
|
||||
pubDate: Date
|
||||
media: Record<'content' | 'thumb', FeedMedia>
|
||||
}
|
||||
|
||||
export interface FeedJson {
|
||||
meta: {
|
||||
title: string
|
||||
url: string
|
||||
}
|
||||
photos: FeedPhotoJson[]
|
||||
}
|
||||
|
||||
const generateFeedMedia = (
|
||||
photo: Photo,
|
||||
size: NextImageSize,
|
||||
): FeedMedia => ({
|
||||
url: getNextImageUrlForRequest({ imageUrl: photo.url, size }),
|
||||
width: size,
|
||||
height: Math.round(size / photo.aspectRatio),
|
||||
});
|
||||
|
||||
const getCoreFeedFields = (photo: Photo) => ({
|
||||
id: photo.id,
|
||||
title: titleForPhoto(photo),
|
||||
description: descriptionForPhoto(photo, true),
|
||||
});
|
||||
|
||||
export const formatPhotoForFeedJson = (photo: Photo): FeedPhotoJson => ({
|
||||
...getCoreFeedFields(photo),
|
||||
url: absolutePathForPhoto({ photo }),
|
||||
...photo.make && { make: photo.make },
|
||||
...photo.model && { model: photo.model },
|
||||
...photo.tags.length > 0 && { tags: photo.tags },
|
||||
takenAtNaive: formatDateFromPostgresString(photo.takenAtNaive),
|
||||
src: {
|
||||
small: generateFeedMedia(photo, FEED_PHOTO_WIDTH_SMALL),
|
||||
medium: generateFeedMedia(photo, FEED_PHOTO_WIDTH_MEDIUM),
|
||||
large: generateFeedMedia(photo, FEED_PHOTO_WIDTH_LARGE),
|
||||
},
|
||||
});
|
||||
|
||||
const formatPhotoForFeedRss = (photo: Photo): FeedPhotoRss => ({
|
||||
...getCoreFeedFields(photo),
|
||||
link: absolutePathForPhoto({ photo }),
|
||||
pubDate: photo.createdAt,
|
||||
media: {
|
||||
content: generateFeedMedia(photo, FEED_PHOTO_WIDTH_LARGE),
|
||||
thumb: generateFeedMedia(photo, FEED_PHOTO_WIDTH_MEDIUM),
|
||||
},
|
||||
});
|
||||
|
||||
const feedPhotoToXml = (photo: FeedPhotoRss): string => {
|
||||
const formattedDate = formatDate({ date: photo.pubDate, length: 'rss' });
|
||||
return `<item>
|
||||
<title>${photo.title}</title>
|
||||
<link>${photo.link}</link>
|
||||
<pubDate>${formattedDate}</pubDate>
|
||||
<guid isPermaLink="true">${photo.link}</guid>
|
||||
${photo.description
|
||||
? `<description><![CDATA[${photo.description}]]></description>`
|
||||
: ''}
|
||||
<media:content
|
||||
url="${formatStringForXml(photo.media.content.url)}"
|
||||
type="image/jpeg"
|
||||
medium="image"
|
||||
width="${photo.media.content.width}"
|
||||
height="${photo.media.content.height}"
|
||||
>
|
||||
<media:thumbnail
|
||||
url="${formatStringForXml(photo.media.thumb.url)}"
|
||||
width="${photo.media.thumb.width}"
|
||||
height="${photo.media.thumb.height}"
|
||||
/>
|
||||
</media:content>
|
||||
</item>`;
|
||||
};
|
||||
|
||||
export const createRssItems = (photos: Photo[]) =>
|
||||
photos.map(formatPhotoForFeedRss).map(feedPhotoToXml);
|
||||
32
src/feed/index.ts
Normal file
32
src/feed/index.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { descriptionForPhoto, Photo, titleForPhoto } from '@/photo';
|
||||
import {
|
||||
getNextImageUrlForRequest,
|
||||
NextImageSize,
|
||||
} from '@/platforms/next-image';
|
||||
|
||||
export const FEED_PHOTO_REQUEST_LIMIT = 40;
|
||||
|
||||
export const FEED_PHOTO_WIDTH_SMALL = 200;
|
||||
export const FEED_PHOTO_WIDTH_MEDIUM = 640;
|
||||
export const FEED_PHOTO_WIDTH_LARGE = 1200;
|
||||
|
||||
export interface FeedMedia {
|
||||
url: string
|
||||
width: number
|
||||
height: number
|
||||
}
|
||||
|
||||
export const generateFeedMedia = (
|
||||
photo: Photo,
|
||||
size: NextImageSize,
|
||||
): FeedMedia => ({
|
||||
url: getNextImageUrlForRequest({ imageUrl: photo.url, size }),
|
||||
width: size,
|
||||
height: Math.round(size / photo.aspectRatio),
|
||||
});
|
||||
|
||||
export const getCoreFeedFields = (photo: Photo) => ({
|
||||
id: photo.id,
|
||||
title: titleForPhoto(photo),
|
||||
description: descriptionForPhoto(photo, true),
|
||||
});
|
||||
36
src/feed/json.ts
Normal file
36
src/feed/json.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { absolutePathForPhoto } from '@/app/paths';
|
||||
import {
|
||||
FEED_PHOTO_WIDTH_LARGE,
|
||||
FEED_PHOTO_WIDTH_MEDIUM,
|
||||
FEED_PHOTO_WIDTH_SMALL,
|
||||
FeedMedia,
|
||||
generateFeedMedia,
|
||||
getCoreFeedFields,
|
||||
} from '.';
|
||||
import { formatDateFromPostgresString } from '@/utility/date';
|
||||
import { Photo } from '@/photo';
|
||||
|
||||
interface FeedPhotoJson {
|
||||
id: string
|
||||
title: string
|
||||
url: string
|
||||
make?: string
|
||||
model?: string
|
||||
tags?: string[]
|
||||
takenAtNaive: string
|
||||
src: Record<'small' | 'medium' | 'large', FeedMedia>
|
||||
}
|
||||
|
||||
export const formatPhotoForFeedJson = (photo: Photo): FeedPhotoJson => ({
|
||||
...getCoreFeedFields(photo),
|
||||
url: absolutePathForPhoto({ photo }),
|
||||
...photo.make && { make: photo.make },
|
||||
...photo.model && { model: photo.model },
|
||||
...photo.tags.length > 0 && { tags: photo.tags },
|
||||
takenAtNaive: formatDateFromPostgresString(photo.takenAtNaive),
|
||||
src: {
|
||||
small: generateFeedMedia(photo, FEED_PHOTO_WIDTH_SMALL),
|
||||
medium: generateFeedMedia(photo, FEED_PHOTO_WIDTH_MEDIUM),
|
||||
large: generateFeedMedia(photo, FEED_PHOTO_WIDTH_LARGE),
|
||||
},
|
||||
});
|
||||
59
src/feed/rss.ts
Normal file
59
src/feed/rss.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import { Photo } from '@/photo';
|
||||
import {
|
||||
FEED_PHOTO_WIDTH_LARGE,
|
||||
FEED_PHOTO_WIDTH_MEDIUM,
|
||||
FeedMedia,
|
||||
generateFeedMedia,
|
||||
getCoreFeedFields,
|
||||
} from '.';
|
||||
import { absolutePathForPhoto } from '@/app/paths';
|
||||
import { formatDate } from '@/utility/date';
|
||||
import { formatStringForXml } from '@/utility/string';
|
||||
|
||||
interface FeedPhotoRss {
|
||||
id: string
|
||||
title: string
|
||||
description?: string
|
||||
link: string
|
||||
pubDate: Date
|
||||
media: Record<'content' | 'thumb', FeedMedia>
|
||||
}
|
||||
|
||||
const formatPhotoForFeedRss = (photo: Photo): FeedPhotoRss => ({
|
||||
...getCoreFeedFields(photo),
|
||||
link: absolutePathForPhoto({ photo }),
|
||||
pubDate: photo.createdAt,
|
||||
media: {
|
||||
content: generateFeedMedia(photo, FEED_PHOTO_WIDTH_LARGE),
|
||||
thumb: generateFeedMedia(photo, FEED_PHOTO_WIDTH_MEDIUM),
|
||||
},
|
||||
});
|
||||
|
||||
const feedPhotoToXml = (photo: FeedPhotoRss): string => {
|
||||
const formattedDate = formatDate({ date: photo.pubDate, length: 'rss' });
|
||||
return `<item>
|
||||
<title>${photo.title}</title>
|
||||
<link>${photo.link}</link>
|
||||
<pubDate>${formattedDate}</pubDate>
|
||||
<guid isPermaLink="true">${photo.link}</guid>
|
||||
${photo.description
|
||||
? `<description><![CDATA[${photo.description}]]></description>`
|
||||
: ''}
|
||||
<media:content
|
||||
url="${formatStringForXml(photo.media.content.url)}"
|
||||
type="image/jpeg"
|
||||
medium="image"
|
||||
width="${photo.media.content.width}"
|
||||
height="${photo.media.content.height}"
|
||||
>
|
||||
<media:thumbnail
|
||||
url="${formatStringForXml(photo.media.thumb.url)}"
|
||||
width="${photo.media.thumb.width}"
|
||||
height="${photo.media.thumb.height}"
|
||||
/>
|
||||
</media:content>
|
||||
</item>`;
|
||||
};
|
||||
|
||||
export const createRssItems = (photos: Photo[]) =>
|
||||
photos.map(formatPhotoForFeedRss).map(feedPhotoToXml);
|
||||
Loading…
Reference in New Issue
Block a user