Remove public api endpoint

This commit is contained in:
Sam Becker 2025-06-12 18:54:50 -05:00
parent 534348b7a8
commit 4dc9149931
10 changed files with 44 additions and 106 deletions

View File

@ -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)

View File

@ -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 });
}
}

View File

@ -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({

View File

@ -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',

View File

@ -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() {
<channel>
<title>${META_TITLE}</title>
<atom:link href="${BASE_URL}/rss.xml"
<atom:link href="${ABSOLUTE_PATH_FOR_FEED_JSON}"
rel="self" type="application/rss+xml" />
<link>${BASE_URL}</link>
<description>${META_DESCRIPTION}</description>
${items.join('\n\n ')}
${items.join('\n')}
</channel>
</rss>`,

View File

@ -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}
</ErrorNote>;
const renderLink = (href: string, children?: ReactNode) =>
<Link
href={href}
className="underline underline-offset-3 hover:no-underline"
target="_blank"
>
{children || href}
</Link>;
return (
<ScoreCardContainer>
<ChecklistGroup
@ -735,23 +745,14 @@ export default function AdminAppConfigurationClient({
{renderEnvVars(['NEXT_PUBLIC_ALLOW_PUBLIC_DOWNLOADS'])}
</ChecklistRow>
<ChecklistRow
title="Public Feed"
status={isPublicFeedEnabled}
title="Site feeds (JSON/RSS)"
status={areSiteFeedsEnabled}
optional
>
Set environment variable to {'"1"'} to enable
a public feed available at <code>/feed.json</code>
and <code>/rss.xml</code>:
{renderEnvVars(['NEXT_PUBLIC_PUBLIC_FEED'])}
</ChecklistRow>
<ChecklistRow
title="Public API"
status={isPublicApiEnabled}
optional
>
Set environment variable to {'"1"'} to enable
a public API available at <code>/api</code>:
{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'])}
</ChecklistRow>
<ChecklistRow
title="Priority order"

View File

@ -1,43 +0,0 @@
import { Photo } from '@/photo';
import { absolutePathForPhoto } from './paths';
import { formatDateFromPostgresString } from '@/utility/date';
import { getNextImageUrlForRequest } from '@/platforms/next-image';
export const API_PHOTO_REQUEST_LIMIT = 40;
export interface PublicApi {
meta: {
title: string
url: string
}
photos: PublicApiPhoto[]
}
interface PublicApiPhoto {
id: string
title?: string
url: string
make?: string
model?: string
tags?: string[]
takenAtNaive: string
src: Record<
'small' | 'medium' | 'large',
string
>
}
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 }),
},
});

View File

@ -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

View File

@ -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;

View File

@ -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`;