Vercel/src/app/paths.ts
2025-04-22 09:38:08 -05:00

371 lines
12 KiB
TypeScript

import { Photo } from '@/photo';
import { PhotoSetCategory } from '@/category';
import { getBaseUrl, GRID_HOMEPAGE_ENABLED } from './config';
import { Camera } from '@/camera';
import { parameterize } from '@/utility/string';
import { TAG_HIDDEN } from '@/tag';
import { Lens } from '@/lens';
// Core paths
export const PATH_ROOT = '/';
export const PATH_GRID = '/grid';
export const PATH_FEED = '/feed';
export const PATH_ADMIN = '/admin';
export const PATH_API = '/api';
export const PATH_SIGN_IN = '/sign-in';
export const PATH_OG = '/og';
export const PATH_GRID_INFERRED = GRID_HOMEPAGE_ENABLED
? PATH_ROOT
: PATH_GRID;
export const PATH_FEED_INFERRED = GRID_HOMEPAGE_ENABLED
? PATH_FEED
: PATH_ROOT;
// Path prefixes
export const PREFIX_PHOTO = '/p';
export const PREFIX_CAMERA = '/shot-on';
export const PREFIX_LENS = '/lens';
export const PREFIX_TAG = '/tag';
export const PREFIX_RECIPE = '/recipe';
export const PREFIX_FILM = '/film';
export const PREFIX_FOCAL_LENGTH = '/focal';
// Dynamic paths
const PATH_PHOTO_DYNAMIC = `${PREFIX_PHOTO}/[photoId]`;
const PATH_CAMERA_DYNAMIC = `${PREFIX_CAMERA}/[make]/[model]`;
const PATH_LENS_DYNAMIC = `${PREFIX_LENS}/[make]/[model]`;
const PATH_TAG_DYNAMIC = `${PREFIX_TAG}/[tag]`;
const PATH_FILM_DYNAMIC = `${PREFIX_FILM}/[film]`;
const PATH_FOCAL_LENGTH_DYNAMIC = `${PREFIX_FOCAL_LENGTH}/[focal]`;
const PATH_RECIPE_DYNAMIC = `${PREFIX_RECIPE}/[recipe]`;
// Admin paths
export const PATH_ADMIN_PHOTOS = `${PATH_ADMIN}/photos`;
export const PATH_ADMIN_PHOTOS_UPDATES = `${PATH_ADMIN_PHOTOS}/updates`;
export const PATH_ADMIN_UPLOADS = `${PATH_ADMIN}/uploads`;
export const PATH_ADMIN_TAGS = `${PATH_ADMIN}/tags`;
export const PATH_ADMIN_RECIPES = `${PATH_ADMIN}/recipes`;
export const PATH_ADMIN_CONFIGURATION = `${PATH_ADMIN}/configuration`;
export const PATH_ADMIN_INSIGHTS = `${PATH_ADMIN}/insights`;
export const PATH_ADMIN_BASELINE = `${PATH_ADMIN}/baseline`;
export const PATH_ADMIN_COMPONENTS = `${PATH_ADMIN}/components`;
// Debug paths
export const PATH_OG_ALL = `${PATH_OG}/all`;
export const PATH_OG_SAMPLE = `${PATH_OG}/sample`;
// API paths
export const PATH_API_STORAGE = `${PATH_API}/storage`;
export const PATH_API_VERCEL_BLOB_UPLOAD = `${PATH_API_STORAGE}/vercel-blob`;
export const PATH_API_PRESIGNED_URL = `${PATH_API_STORAGE}/presigned-url`;
// Modifiers
const EDIT = 'edit';
// Special characters
export const MISSING_FIELD = '-';
export const PATHS_ADMIN = [
PATH_ADMIN,
PATH_ADMIN_PHOTOS,
PATH_ADMIN_PHOTOS_UPDATES,
PATH_ADMIN_UPLOADS,
PATH_ADMIN_TAGS,
PATH_ADMIN_RECIPES,
PATH_ADMIN_INSIGHTS,
PATH_ADMIN_CONFIGURATION,
PATH_ADMIN_BASELINE,
PATH_ADMIN_COMPONENTS,
];
export const PATHS_TO_CACHE = [
PATH_ROOT,
PATH_GRID,
PATH_FEED,
PATH_OG,
PATH_PHOTO_DYNAMIC,
PATH_CAMERA_DYNAMIC,
PATH_LENS_DYNAMIC,
PATH_TAG_DYNAMIC,
PATH_FILM_DYNAMIC,
PATH_FOCAL_LENGTH_DYNAMIC,
PATH_RECIPE_DYNAMIC,
...PATHS_ADMIN,
];
type PhotoPathParams = { photo: PhotoOrPhotoId } & PhotoSetCategory & {
showRecipe?: boolean
};
export const pathForAdminUploadUrl = (url: string) =>
`${PATH_ADMIN_UPLOADS}/${encodeURIComponent(url)}`;
export const pathForAdminPhotoEdit = (photo: PhotoOrPhotoId) =>
`${PATH_ADMIN_PHOTOS}/${getPhotoId(photo)}/${EDIT}`;
export const pathForAdminTagEdit = (tag: string) =>
`${PATH_ADMIN_TAGS}/${tag}/${EDIT}`;
export const pathForAdminRecipeEdit = (recipe: string) =>
`${PATH_ADMIN_RECIPES}/${recipe}/${EDIT}`;
type PhotoOrPhotoId = Photo | string;
const getPhotoId = (photoOrPhotoId: PhotoOrPhotoId) =>
typeof photoOrPhotoId === 'string' ? photoOrPhotoId : photoOrPhotoId.id;
export const pathForPhoto = ({
photo,
camera,
lens,
tag,
film,
focal,
recipe,
}: PhotoPathParams) => {
let prefix = PREFIX_PHOTO;
if (typeof photo !== 'string' && photo.hidden) {
prefix = pathForTag(TAG_HIDDEN);
} else if (camera) {
prefix = pathForCamera(camera);
} else if (lens) {
prefix = pathForLens(lens);
} else if (tag) {
prefix = pathForTag(tag);
} else if (recipe) {
prefix = pathForRecipe(recipe);
} else if (film) {
prefix = pathForFilm(film);
} else if (focal) {
prefix = pathForFocalLength(focal);
}
return `${prefix}/${getPhotoId(photo)}`;
};
export const pathForTag = (tag: string) =>
`${PREFIX_TAG}/${tag}`;
export const pathForCamera = ({ make, model }: Camera) =>
`${PREFIX_CAMERA}/${parameterize(make)}/${parameterize(model)}`;
export const pathForFilm = (film: string) =>
`${PREFIX_FILM}/${film}`;
export const pathForLens = ({ make, model }: Lens) =>
make
? `${PREFIX_LENS}/${parameterize(make)}/${parameterize(model)}`
: `${PREFIX_LENS}/${MISSING_FIELD}/${parameterize(model)}`;
export const pathForFocalLength = (focal: number) =>
`${PREFIX_FOCAL_LENGTH}/${focal}mm`;
export const pathForRecipe = (recipe: string) =>
`${PREFIX_RECIPE}/${recipe}`;
// Absolute paths
export const ABSOLUTE_PATH_FOR_HOME_IMAGE =
`${getBaseUrl()}/home-image`;
export const absolutePathForPhoto = (
params: PhotoPathParams,
share?: boolean,
) =>
`${getBaseUrl(share)}${pathForPhoto(params)}`;
export const absolutePathForTag = (tag: string, share?: boolean) =>
`${getBaseUrl(share)}${pathForTag(tag)}`;
export const absolutePathForCamera= (camera: Camera, share?: boolean) =>
`${getBaseUrl(share)}${pathForCamera(camera)}`;
export const absolutePathForLens= (lens: Lens, share?: boolean) =>
`${getBaseUrl(share)}${pathForLens(lens)}`;
export const absolutePathForFilm = (film: string, share?: boolean) =>
`${getBaseUrl(share)}${pathForFilm(film)}`;
export const absolutePathForRecipe = (recipe: string, share?: boolean) =>
`${getBaseUrl(share)}${pathForRecipe(recipe)}`;
export const absolutePathForFocalLength = (focal: number, share?: boolean) =>
`${getBaseUrl(share)}${pathForFocalLength(focal)}`;
export const absolutePathForPhotoImage = (photo: PhotoOrPhotoId) =>
`${absolutePathForPhoto({ photo })}/image`;
export const absolutePathForTagImage = (tag: string) =>
`${absolutePathForTag(tag)}/image`;
export const absolutePathForCameraImage= (camera: Camera) =>
`${absolutePathForCamera(camera)}/image`;
export const absolutePathForLensImage= (lens: Lens) =>
`${absolutePathForLens(lens)}/image`;
export const absolutePathForFilmImage = (film: string) =>
`${absolutePathForFilm(film)}/image`;
export const absolutePathForRecipeImage = (recipe: string) =>
`${absolutePathForRecipe(recipe)}/image`;
export const absolutePathForFocalLengthImage =
(focal: number) =>
`${absolutePathForFocalLength(focal)}/image`;
// p/[photoId]
export const isPathPhoto = (pathname = '') =>
new RegExp(`^${PREFIX_PHOTO}/[^/]+/?$`).test(pathname);
// tag/[tag]
export const isPathTag = (pathname = '') =>
new RegExp(`^${PREFIX_TAG}/[^/]+/?$`).test(pathname);;
// tag/[tag]/[photoId]
export const isPathTagPhoto = (pathname = '') =>
new RegExp(`^${PREFIX_TAG}/[^/]+/[^/]+/?$`).test(pathname);
// shot-on/[make]/[model]
export const isPathCamera = (pathname = '') =>
new RegExp(`^${PREFIX_CAMERA}/[^/]+/[^/]+/?$`).test(pathname);
// shot-on/[make]/[model]/[photoId]
export const isPathCameraPhoto = (pathname = '') =>
new RegExp(`^${PREFIX_CAMERA}/[^/]+/[^/]+/[^/]+/?$`).test(pathname);
// film/[film]
export const isPathFilm = (pathname = '') =>
new RegExp(`^${PREFIX_FILM}/[^/]+/?$`).test(pathname);
// film/[film]/[photoId]
export const isPathFilmPhoto = (pathname = '') =>
new RegExp(`^${PREFIX_FILM}/[^/]+/[^/]+/?$`).test(pathname);
// focal/[focal]
export const isPathFocalLength = (pathname = '') =>
new RegExp(`^${PREFIX_FOCAL_LENGTH}/[^/]+/?$`).test(pathname);
// focal/[focal]/[photoId]
export const isPathFocalLengthPhoto = (pathname = '') =>
new RegExp(`^${PREFIX_FOCAL_LENGTH}/[^/]+/[^/]+/?$`).test(pathname);
export const checkPathPrefix = (pathname = '', prefix: string) =>
pathname.toLowerCase().startsWith(prefix);
export const isPathRoot = (pathname?: string) =>
pathname === PATH_ROOT;
export const isPathGrid = (pathname?: string) =>
checkPathPrefix(pathname, PATH_GRID);
export const isPathFeed = (pathname?: string) =>
checkPathPrefix(pathname, PATH_FEED);
export const isPathTopLevel = (pathname?: string) =>
isPathRoot(pathname)||
isPathGrid(pathname) ||
isPathFeed(pathname);
export const isPathSignIn = (pathname?: string) =>
checkPathPrefix(pathname, PATH_SIGN_IN);
export const isPathAdmin = (pathname?: string) =>
checkPathPrefix(pathname, PATH_ADMIN);
export const isPathTopLevelAdmin = (pathname?: string) =>
PATHS_ADMIN.some(path => path === pathname);
export const isPathAdminPhotos = (pathname?: string) =>
checkPathPrefix(pathname, PATH_ADMIN_PHOTOS);
export const isPathAdminInsights = (pathname?: string) =>
checkPathPrefix(pathname, PATH_ADMIN_INSIGHTS);
export const isPathAdminConfiguration = (pathname?: string) =>
checkPathPrefix(pathname, PATH_ADMIN_CONFIGURATION);
export const isPathAdminInfo = (pathname?: string) =>
isPathAdminInsights(pathname) ||
isPathAdminConfiguration(pathname);
export const isPathProtected = (pathname?: string) =>
checkPathPrefix(pathname, PATH_ADMIN) ||
checkPathPrefix(pathname, pathForTag(TAG_HIDDEN)) ||
checkPathPrefix(pathname, PATH_OG);
export const getPathComponents = (pathname = ''): {
photoId?: string
} & PhotoSetCategory => {
const photoIdFromPhoto = pathname.match(
new RegExp(`^${PREFIX_PHOTO}/([^/]+)`))?.[1];
const photoIdFromTag = pathname.match(
new RegExp(`^${PREFIX_TAG}/[^/]+/([^/]+)`))?.[1];
const photoIdFromCamera = pathname.match(
new RegExp(`^${PREFIX_CAMERA}/[^/]+/[^/]+/([^/]+)`))?.[1];
const photoIdFromFilm = pathname.match(
new RegExp(`^${PREFIX_FILM}/[^/]+/([^/]+)`))?.[1];
const photoIdFromFocalLength = pathname.match(
new RegExp(`^${PREFIX_FOCAL_LENGTH}/[0-9]+mm/([^/]+)`))?.[1];
const tag = pathname.match(
new RegExp(`^${PREFIX_TAG}/([^/]+)`))?.[1];
const cameraMake = pathname.match(
new RegExp(`^${PREFIX_CAMERA}/([^/]+)`))?.[1];
const cameraModel = pathname.match(
new RegExp(`^${PREFIX_CAMERA}/[^/]+/([^/]+)`))?.[1];
const film = pathname.match(
new RegExp(`^${PREFIX_FILM}/([^/]+)`))?.[1] as string;
const focalString = pathname.match(
new RegExp(`^${PREFIX_FOCAL_LENGTH}/([0-9]+)mm`))?.[1];
const camera = cameraMake && cameraModel
? { make: cameraMake, model: cameraModel }
: undefined;
const focal = focalString ? parseInt(focalString) : undefined;
return {
photoId: (
photoIdFromPhoto ||
photoIdFromTag ||
photoIdFromCamera ||
photoIdFromFilm ||
photoIdFromFocalLength
),
tag,
camera,
film,
focal,
};
};
export const getEscapePath = (pathname?: string) => {
const {
photoId,
tag,
camera,
film,
focal,
} = getPathComponents(pathname);
if (
(photoId && isPathPhoto(pathname)) ||
(tag && isPathTag(pathname)) ||
(camera && isPathCamera(pathname)) ||
(film && isPathFilm(pathname)) ||
(focal && isPathFocalLength(pathname))
) {
return PATH_ROOT;
} else if (tag && isPathTagPhoto(pathname)) {
return pathForTag(tag);
} else if (camera && isPathCameraPhoto(pathname)) {
return pathForCamera(camera);
} else if (film && isPathFilmPhoto(pathname)) {
return pathForFilm(film);
} else if (focal && isPathFocalLengthPhoto(pathname)) {
return pathForFocalLength(focal);
}
};