diff --git a/README.md b/README.md index 12645122..fff0f43d 100644 --- a/README.md +++ b/README.md @@ -158,8 +158,7 @@ Application behavior can be changed by configuring the following environment var #### Settings - `NEXT_PUBLIC_GEO_PRIVACY = 1` disables collection/display of location-based data (⚠️ re-compresses uploaded images in order to remove GPS information) - `NEXT_PUBLIC_ALLOW_PUBLIC_DOWNLOADS = 1` enables public photo downloads for all visitors (⚠️ may result in increased bandwidth usage) -- `NEXT_PUBLIC_PUBLIC_FEED = 1` enables public feed available at `/feed.json` and `/rss.xml` -- `NEXT_PUBLIC_PUBLIC_API = 1` enables public API available at `/api` +- `NEXT_PUBLIC_SITE_FEEDS = 1` enables feeds at `/feed.json` and `/rss.xml` - `NEXT_PUBLIC_IGNORE_PRIORITY_ORDER = 1` prevents `priority_order` field affecting photo order - `NEXT_PUBLIC_OG_TEXT_ALIGNMENT = BOTTOM` keeps OG image text bottom aligned (default is top) diff --git a/app/api/route.ts b/app/api/route.ts deleted file mode 100644 index fe566c70..00000000 --- a/app/api/route.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { getPhotosCached } from '@/photo/cache'; -import { API_PHOTO_REQUEST_LIMIT, formatPhotoForApi } from '@/app/api'; -import { - BASE_URL, - PUBLIC_API_ENABLED, - META_TITLE, -} from '@/app/config'; - -export const dynamic = 'force-dynamic'; - -export async function GET() { - if (PUBLIC_API_ENABLED) { - const photos = await getPhotosCached({ limit: API_PHOTO_REQUEST_LIMIT }); - return Response.json({ - meta: { - title: META_TITLE, - url: BASE_URL, - }, - photos: photos.map(formatPhotoForApi), - }); - } else { - return new Response('API access disabled', { status: 404 }); - } -} diff --git a/app/feed.json/route.ts b/app/feed.json/route.ts index 3422017e..3078cdfd 100644 --- a/app/feed.json/route.ts +++ b/app/feed.json/route.ts @@ -1,18 +1,17 @@ import { getPhotosCached } from '@/photo/cache'; -import { INFINITE_SCROLL_FEED_INITIAL } from '@/photo'; import { BASE_URL, - PUBLIC_FEED_ENABLED, + SITE_FEEDS_ENABLED, META_TITLE, } from '@/app/config'; -import { formatPhotoForFeedJson } from '@/app/feed'; +import { FEED_PHOTO_REQUEST_LIMIT, formatPhotoForFeedJson } from '@/app/feed'; export const dynamic = 'force-static'; export async function GET() { - if (PUBLIC_FEED_ENABLED) { + if (SITE_FEEDS_ENABLED) { const photos = await getPhotosCached({ - limit: INFINITE_SCROLL_FEED_INITIAL, + limit: FEED_PHOTO_REQUEST_LIMIT, sortBy: 'createdAt', }); return Response.json({ diff --git a/app/layout.tsx b/app/layout.tsx index dc8a19ae..3eeaa1e2 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -10,7 +10,7 @@ import { META_TITLE, HTML_LANG, NAV_CAPTION, - PUBLIC_FEED_ENABLED, + SITE_FEEDS_ENABLED, } from '@/app/config'; import AppStateProvider from '@/state/AppStateProvider'; import ToasterWithThemes from '@/toast/ToasterWithThemes'; @@ -66,7 +66,7 @@ export const metadata: Metadata = { type: 'image/png', sizes: '180x180', }], - ...PUBLIC_FEED_ENABLED && { + ...SITE_FEEDS_ENABLED && { alternates: { types: { 'application/rss+xml': '/rss.xml', diff --git a/app/rss.xml/route.ts b/app/rss.xml/route.ts index 3499818a..79573fca 100644 --- a/app/rss.xml/route.ts +++ b/app/rss.xml/route.ts @@ -4,14 +4,15 @@ import { BASE_URL, META_DESCRIPTION, META_TITLE, - PUBLIC_FEED_ENABLED, + SITE_FEEDS_ENABLED, } from '@/app/config'; import { feedPhotoToXml, formatPhotoForFeedRss } from '@/app/feed'; +import { ABSOLUTE_PATH_FOR_FEED_JSON } from '@/app/paths'; export const dynamic = 'force-static'; export async function GET() { - if (PUBLIC_FEED_ENABLED) { + if (SITE_FEEDS_ENABLED) { const photos = await getPhotosCached({ limit: INFINITE_SCROLL_FEED_INITIAL, sortBy: 'createdAt', @@ -26,13 +27,11 @@ export async function GET() { ${META_TITLE} - ${BASE_URL} ${META_DESCRIPTION} - - ${items.join('\n\n ')} - + ${items.join('\n')} `, diff --git a/src/admin/AdminAppConfigurationClient.tsx b/src/admin/AdminAppConfigurationClient.tsx index 10b9fffe..9e5fd412 100644 --- a/src/admin/AdminAppConfigurationClient.tsx +++ b/src/admin/AdminAppConfigurationClient.tsx @@ -31,6 +31,8 @@ import ScoreCardContainer from '@/components/ScoreCardContainer'; import { DEFAULT_CATEGORY_KEYS, getHiddenCategories } from '@/category'; import { AI_AUTO_GENERATED_FIELDS_ALL } from '@/photo/ai'; import clsx from 'clsx/lite'; +import Link from 'next/link'; +import { PATH_FEED_JSON, PATH_RSS_XML } from '@/app/paths'; export default function AdminAppConfigurationClient({ // Storage @@ -103,8 +105,7 @@ export default function AdminAppConfigurationClient({ // Settings isGeoPrivacyEnabled, arePublicDownloadsEnabled, - isPublicApiEnabled, - isPublicFeedEnabled, + areSiteFeedsEnabled, isPriorityOrderEnabled, isOgTextBottomAligned, // Internal @@ -178,6 +179,15 @@ export default function AdminAppConfigurationClient({ {message} ; + const renderLink = (href: string, children?: ReactNode) => + + {children || href} + ; + return ( - Set environment variable to {'"1"'} to enable - a public feed available at /feed.json - and /rss.xml: - {renderEnvVars(['NEXT_PUBLIC_PUBLIC_FEED'])} - - - Set environment variable to {'"1"'} to enable - a public API available at /api: - {renderEnvVars(['NEXT_PUBLIC_PUBLIC_API'])} + Set environment variable to {'"1"'} to enable feeds at + {' '} + {renderLink(PATH_FEED_JSON)} and {renderLink(PATH_RSS_XML)}: + {renderEnvVars(['NEXT_PUBLIC_SITE_FEEDS'])} -} - -export const formatPhotoForApi = (photo: Photo): PublicApiPhoto => ({ - id: photo.id, - title: photo.title, - 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: getNextImageUrlForRequest({ imageUrl: photo.url, size: 200 }), - medium: getNextImageUrlForRequest({ imageUrl: photo.url, size: 640 }), - large: getNextImageUrlForRequest({ imageUrl: photo.url, size: 1200 }), - }, -}); diff --git a/src/app/config.ts b/src/app/config.ts index 40f7b97e..fb7c6b2a 100644 --- a/src/app/config.ts +++ b/src/app/config.ts @@ -307,10 +307,8 @@ export const GEO_PRIVACY_ENABLED = process.env.NEXT_PUBLIC_GEO_PRIVACY === '1'; export const ALLOW_PUBLIC_DOWNLOADS = process.env.NEXT_PUBLIC_ALLOW_PUBLIC_DOWNLOADS === '1'; -export const PUBLIC_FEED_ENABLED = - process.env.NEXT_PUBLIC_PUBLIC_FEED === '1'; -export const PUBLIC_API_ENABLED = - process.env.NEXT_PUBLIC_PUBLIC_API === '1'; +export const SITE_FEEDS_ENABLED = + process.env.NEXT_PUBLIC_SITE_FEEDS === '1'; export const PRIORITY_ORDER_ENABLED = process.env.NEXT_PUBLIC_IGNORE_PRIORITY_ORDER !== '1'; export const OG_TEXT_BOTTOM_ALIGNMENT = @@ -419,8 +417,7 @@ export const APP_CONFIGURATION = { // Settings isGeoPrivacyEnabled: GEO_PRIVACY_ENABLED, arePublicDownloadsEnabled: ALLOW_PUBLIC_DOWNLOADS, - isPublicApiEnabled: PUBLIC_API_ENABLED, - isPublicFeedEnabled: PUBLIC_FEED_ENABLED, + areSiteFeedsEnabled: SITE_FEEDS_ENABLED, isPriorityOrderEnabled: PRIORITY_ORDER_ENABLED, isOgTextBottomAligned: OG_TEXT_BOTTOM_ALIGNMENT, // Internal diff --git a/src/app/feed.ts b/src/app/feed.ts index 7d4dc159..12d4eab5 100644 --- a/src/app/feed.ts +++ b/src/app/feed.ts @@ -6,7 +6,7 @@ import { } from '@/platforms/next-image'; import { formatDate, formatDateFromPostgresString } from '@/utility/date'; -export const API_PHOTO_REQUEST_LIMIT = 40; +export const FEED_PHOTO_REQUEST_LIMIT = 40; export const FEED_PHOTO_WIDTH_SMALL = 200; export const FEED_PHOTO_WIDTH_MEDIUM = 640; diff --git a/src/app/paths.ts b/src/app/paths.ts index 5a06f14d..3829471c 100644 --- a/src/app/paths.ts +++ b/src/app/paths.ts @@ -15,6 +15,10 @@ export const PATH_API = '/api'; export const PATH_SIGN_IN = '/sign-in'; export const PATH_OG = '/og'; +// Feeds +export const PATH_FEED_JSON = '/feed.json'; +export const PATH_RSS_XML = '/rss.xml'; + export const PATH_GRID_INFERRED = GRID_HOMEPAGE_ENABLED ? PATH_ROOT : PATH_GRID; @@ -167,6 +171,12 @@ export const pathForRecipe = (recipe: string) => `${PREFIX_RECIPE}/${recipe}`; // Absolute paths +export const ABSOLUTE_PATH_FOR_FEED_JSON = + `${getBaseUrl()}${PATH_FEED_JSON}`; + +export const ABSOLUTE_PATH_FOR_RSS_XML = + `${getBaseUrl()}${PATH_RSS_XML}`; + export const ABSOLUTE_PATH_FOR_HOME_IMAGE = `${getBaseUrl()}/home-image`;