diff --git a/README.md b/README.md index 6d236c25..73b19079 100644 --- a/README.md +++ b/README.md @@ -62,3 +62,4 @@ Installation 1. Set `NEXT_PUBLIC_HIDE_REPO_LINK = 1` to remove footer link to repo 2. Set `NEXT_PUBLIC_PRO_MODE = 1` to enable higher quality image storage +3. Set `NEXT_PUBLIC_PUBLIC_API = 1` to enable a public API available at `/api` diff --git a/src/app/api/route.ts b/src/app/api/route.ts new file mode 100644 index 00000000..5573b5e5 --- /dev/null +++ b/src/app/api/route.ts @@ -0,0 +1,24 @@ +import { getPhotosCached } from '@/cache'; +import { parsePhotoForApi } from '@/photo'; +import { + BASE_URL, + PUBLIC_API_ENABLED, + SITE_TITLE, +} from '@/site/config'; + +const API_PHOTO_LIMIT = 20; + +export async function GET() { + if (PUBLIC_API_ENABLED) { + const photos = await getPhotosCached({ limit: API_PHOTO_LIMIT }); + return Response.json({ + meta: { + title: SITE_TITLE, + url: BASE_URL, + }, + photos: photos.map(parsePhotoForApi), + }); + } else { + return Response.json({ message: 'API is disabled' }); + } +} diff --git a/src/photo/actions.ts b/src/photo/actions.ts index ed6e071f..0034fe19 100644 --- a/src/photo/actions.ts +++ b/src/photo/actions.ts @@ -18,7 +18,7 @@ import { revalidateBlobKey, revalidatePhotosKey, } from '@/cache'; -import { IS_PRO_MODE } from '@/site/config'; +import { PRO_MODE_ENABLED } from '@/site/config'; import { getNextImageUrlForRequest } from '@/utility/image'; import { PATH_ADMIN_PHOTOS, PATH_ADMIN_TAGS } from '@/site/paths'; @@ -31,10 +31,10 @@ export async function createPhotoAction(formData: FormData) { const updatedUrl = await convertUploadToPhoto( photo.url, photo.id, - !IS_PRO_MODE + !PRO_MODE_ENABLED ? getNextImageUrlForRequest(photo.url, 3840, 90, requestOrigin) : undefined, - !IS_PRO_MODE ? 'webp' : undefined, + !PRO_MODE_ENABLED ? 'webp' : undefined, ); if (updatedUrl) { photo.url = updatedUrl; } diff --git a/src/photo/index.ts b/src/photo/index.ts index a6ee8910..d22cfbfd 100644 --- a/src/photo/index.ts +++ b/src/photo/index.ts @@ -1,4 +1,7 @@ -import { ABSOLUTE_PATH_FOR_HOME_IMAGE } from '@/site/paths'; +import { + ABSOLUTE_PATH_FOR_HOME_IMAGE, + absolutePathForPhoto, +} from '@/site/paths'; import { formatDateFromPostgresString } from '@/utility/date'; import { formatAperture, @@ -7,6 +10,7 @@ import { formatExposureTime, formatFocalLength, } from '@/utility/exif'; +import { getNextImageUrlForRequest } from '@/utility/image'; import camelcaseKeys from 'camelcase-keys'; import type { Metadata } from 'next'; @@ -86,6 +90,21 @@ export const parsePhotoFromDb = (photoDbRaw: PhotoDb): Photo => { }; }; +export const parsePhotoForApi = (photo: Photo) => ({ + 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(photo.url, 200), + medium: getNextImageUrlForRequest(photo.url, 640), + large: getNextImageUrlForRequest(photo.url, 1200), + }, +}); + export const parseCachedPhotoDates = (photo: Photo) => ({ ...photo, takenAt: new Date(photo.takenAt), diff --git a/src/site/SiteChecklistClient.tsx b/src/site/SiteChecklistClient.tsx index 1609a894..6487380d 100644 --- a/src/site/SiteChecklistClient.tsx +++ b/src/site/SiteChecklistClient.tsx @@ -26,7 +26,8 @@ export default function SiteChecklistClient({ hasTitle, hasDomain, showRepoLink, - isProMode, + isProModeEnabled, + isPublicApiEnabled, showRefreshButton, secret, }: { @@ -37,7 +38,8 @@ export default function SiteChecklistClient({ hasTitle: boolean hasDomain: boolean showRepoLink: boolean - isProMode: boolean + isProModeEnabled: boolean + isPublicApiEnabled: boolean showRefreshButton?: boolean secret: string }) { @@ -219,7 +221,7 @@ export default function SiteChecklistClient({ @@ -227,6 +229,16 @@ export default function SiteChecklistClient({ higher quality image storage: {renderEnvVars(['NEXT_PUBLIC_PRO_MODE'])} + + Set environment variable to {'"1"'} to enable + a public API available at /api: + {renderEnvVars(['NEXT_PUBLIC_PUBLIC_API'])} + {showRefreshButton &&
diff --git a/src/site/config.ts b/src/site/config.ts index 6652f7c1..5f66d86d 100644 --- a/src/site/config.ts +++ b/src/site/config.ts @@ -25,7 +25,8 @@ export const BASE_URL = process.env.NODE_ENV === 'production' : 'http://localhost:3000'; export const SHOW_REPO_LINK = process.env.NEXT_PUBLIC_HIDE_REPO_LINK !== '1'; -export const IS_PRO_MODE = process.env.NEXT_PUBLIC_PRO_MODE === '1'; +export const PRO_MODE_ENABLED = process.env.NEXT_PUBLIC_PRO_MODE === '1'; +export const PUBLIC_API_ENABLED = process.env.NEXT_PUBLIC_PUBLIC_API === '1'; export const CONFIG_CHECKLIST_STATUS = { hasPostgres: (process.env.POSTGRES_HOST ?? '').length > 0, @@ -38,7 +39,8 @@ export const CONFIG_CHECKLIST_STATUS = { hasTitle: (process.env.NEXT_PUBLIC_SITE_TITLE ?? '').length > 0, hasDomain: (process.env.NEXT_PUBLIC_SITE_DOMAIN ?? '').length > 0, showRepoLink: SHOW_REPO_LINK, - isProMode: IS_PRO_MODE, + isProModeEnabled: PRO_MODE_ENABLED, + isPublicApiEnabled: PUBLIC_API_ENABLED, }; export const IS_CHECKLIST_COMPLETE =