diff --git a/app/feed.json/route.ts b/app/feed.json/route.ts
index 19f83e2f..156ddb42 100644
--- a/app/feed.json/route.ts
+++ b/app/feed.json/route.ts
@@ -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;
diff --git a/app/rss.xml/route.ts b/app/rss.xml/route.ts
index 4f67951e..25a0a952 100644
--- a/app/rss.xml/route.ts
+++ b/app/rss.xml/route.ts
@@ -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
diff --git a/src/app/feed.ts b/src/app/feed.ts
deleted file mode 100644
index 3395c7b0..00000000
--- a/src/app/feed.ts
+++ /dev/null
@@ -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 `-
- ${photo.title}
- ${photo.link}
- ${formattedDate}
- ${photo.link}
- ${photo.description
- ? ``
- : ''}
-
-
-
-
`;
-};
-
-export const createRssItems = (photos: Photo[]) =>
- photos.map(formatPhotoForFeedRss).map(feedPhotoToXml);
diff --git a/src/feed/index.ts b/src/feed/index.ts
new file mode 100644
index 00000000..97c4ae1e
--- /dev/null
+++ b/src/feed/index.ts
@@ -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),
+});
diff --git a/src/feed/json.ts b/src/feed/json.ts
new file mode 100644
index 00000000..9a1fe608
--- /dev/null
+++ b/src/feed/json.ts
@@ -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),
+ },
+});
diff --git a/src/feed/rss.ts b/src/feed/rss.ts
new file mode 100644
index 00000000..e34e9d1e
--- /dev/null
+++ b/src/feed/rss.ts
@@ -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 `-
+ ${photo.title}
+ ${photo.link}
+ ${formattedDate}
+ ${photo.link}
+ ${photo.description
+ ? ``
+ : ''}
+
+
+
+
`;
+};
+
+export const createRssItems = (photos: Photo[]) =>
+ photos.map(formatPhotoForFeedRss).map(feedPhotoToXml);