import { AI_AUTO_GENERATED_FIELDS_DEFAULT, parseAiAutoGeneratedFieldsString, } from '@/photo/ai'; import { parseOrderedCategoriesFromString } from '@/category'; import type { StorageType } from '@/platforms/storage'; import { makeUrlAbsolute, shortenUrl, } from '@/utility/url'; import { getNavSortControlFromString, getSortByFromString } from '@/photo/sort'; import { parseChromaCutoff, parseStartingHue } from '@/photo/color/sort'; import { parseSocialKeysFromString } from '@/social'; import { dependencies } from '../../package.json'; import { normalizeRedisUrl } from '@/platforms/redis'; // HARD-CODED GLOBAL CONFIGURATION export const SHOULD_PREFETCH_ALL_LINKS: boolean | undefined = undefined; // TEMPLATE META export const TEMPLATE_TITLE = 'Photo Blog'; export const TEMPLATE_DESCRIPTION = 'Store photos with original camera data'; // SOURCE CODE export const TEMPLATE_REPO_OWNER = 'sambecker'; export const TEMPLATE_REPO_NAME = 'exif-photo-blog'; export const TEMPLATE_REPO_BRANCH = 'main'; export const TEMPLATE_REPO_URL = `https://github.com/${TEMPLATE_REPO_OWNER}/${TEMPLATE_REPO_NAME}`; export const TEMPLATE_REPO_URL_FORK = `${TEMPLATE_REPO_URL}/fork`; export const TEMPLATE_REPO_URL_README = `${TEMPLATE_REPO_URL}?tab=readme-ov-file`; export const VERCEL_GIT_PROVIDER = process.env.NEXT_PUBLIC_VERCEL_GIT_PROVIDER; export const VERCEL_GIT_REPO_OWNER = process.env.NEXT_PUBLIC_VERCEL_GIT_REPO_OWNER; export const VERCEL_GIT_REPO_SLUG = process.env.NEXT_PUBLIC_VERCEL_GIT_REPO_SLUG; export const VERCEL_GIT_BRANCH = process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_REF; export const VERCEL_GIT_COMMIT_MESSAGE = process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_MESSAGE; export const VERCEL_GIT_COMMIT_SHA = process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA; export const VERCEL_GIT_COMMIT_SHA_SHORT = VERCEL_GIT_COMMIT_SHA ? VERCEL_GIT_COMMIT_SHA.slice(0, 7) : undefined; export const IS_VERCEL_GIT_PROVIDER_GITHUB = VERCEL_GIT_PROVIDER === 'github'; export const VERCEL_GIT_COMMIT_URL = IS_VERCEL_GIT_PROVIDER_GITHUB // eslint-disable-next-line max-len ? `https://github.com/${VERCEL_GIT_REPO_OWNER}/${VERCEL_GIT_REPO_SLUG}/commit/${VERCEL_GIT_COMMIT_SHA}` : undefined; export const VERCEL_ENV = process.env.NEXT_PUBLIC_VERCEL_ENV; export const VERCEL_PRODUCTION_URL = process.env.VERCEL_PROJECT_PRODUCTION_URL; export const VERCEL_DEPLOYMENT_URL = process.env.NEXT_PUBLIC_VERCEL_URL; export const VERCEL_BRANCH_URL = process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL; // Last resort: cannot be used reliably export const VERCEL_PROJECT_URL = VERCEL_BRANCH_URL && VERCEL_GIT_BRANCH ? `${VERCEL_BRANCH_URL.split(`-git-${VERCEL_GIT_BRANCH}-`)[0]}.vercel.app` : undefined; export const IS_PRODUCTION = process.env.NODE_ENV === 'production' && ( // Make environment checks resilient to non-Vercel deployments VERCEL_ENV === 'production' || !VERCEL_ENV ); export const IS_DEVELOPMENT = process.env.NODE_ENV === 'development'; export const IS_PREVIEW = VERCEL_ENV === 'preview'; export const IS_BUILDING = process.env.NEXT_PHASE === 'phase-production-build'; export const VERCEL_BYPASS_KEY = 'x-vercel-protection-bypass'; export const VERCEL_BYPASS_SECRET = process.env.VERCEL_AUTOMATION_BYPASS_SECRET; // DOMAIN // User-facing domain, potential site title const SITE_DOMAIN = process.env.NEXT_PUBLIC_DOMAIN || // Legacy environment variable process.env.NEXT_PUBLIC_SITE_DOMAIN || VERCEL_PRODUCTION_URL || VERCEL_PROJECT_URL || VERCEL_DEPLOYMENT_URL; const SITE_DOMAIN_SHARE = process.env.NEXT_PUBLIC_DOMAIN_SHARE; // Used primarily for absolute references such as OG images export const BASE_URL = makeUrlAbsolute(( process.env.NODE_ENV === 'production' && VERCEL_ENV !== 'preview' ) ? SITE_DOMAIN : VERCEL_ENV === 'preview' ? VERCEL_BRANCH_URL || VERCEL_DEPLOYMENT_URL : 'http://localhost:3000')?.toLocaleLowerCase(); export const BASE_URL_SHARE = makeUrlAbsolute(SITE_DOMAIN_SHARE)?.toLocaleLowerCase(); export const getBaseUrl = (share?: boolean) => (share && BASE_URL_SHARE) ? BASE_URL_SHARE : BASE_URL; const SITE_DOMAIN_SHORT = shortenUrl(SITE_DOMAIN); // SITE META export const APP_LOCALE = (process.env.NEXT_PUBLIC_LOCALE || 'en-us') // Accepts both `en-us` and `EN_US` .toLocaleLowerCase() .replace('_', '-'); export const HTML_LANG = (APP_LOCALE.split('-')[0] || 'en'); export const CUSTOM_NAV_TITLE = process.env.NEXT_PUBLIC_NAV_TITLE; export const NAV_CAPTION = process.env.NEXT_PUBLIC_NAV_CAPTION || // Legacy environment variable process.env.NEXT_PUBLIC_SITE_DESCRIPTION; export const META_TITLE = process.env.NEXT_PUBLIC_META_TITLE || // Legacy environment variable process.env.NEXT_PUBLIC_SITE_TITLE || CUSTOM_NAV_TITLE || TEMPLATE_TITLE; export const IS_META_TITLE_CONFIGURED = Boolean(process.env.NEXT_PUBLIC_META_TITLE) || // Legacy environment variable Boolean(process.env.NEXT_PUBLIC_SITE_TITLE) || Boolean(CUSTOM_NAV_TITLE); export const IS_META_DESCRIPTION_CONFIGURED = Boolean(process.env.NEXT_PUBLIC_META_DESCRIPTION) || Boolean(NAV_CAPTION); export const META_DESCRIPTION = process.env.NEXT_PUBLIC_META_DESCRIPTION || NAV_CAPTION || SITE_DOMAIN_SHORT; export const NAV_TITLE = CUSTOM_NAV_TITLE || SITE_DOMAIN_SHORT || META_TITLE; export const SIDEBAR_TEXT = process.env.NEXT_PUBLIC_SIDEBAR_TEXT || // Legacy environment variables process.env.NEXT_PUBLIC_PAGE_ABOUT || process.env.NEXT_PUBLIC_SITE_ABOUT; export const ABOUT_TITLE = process.env.NEXT_PUBLIC_ABOUT_TITLE || NAV_TITLE; export const ABOUT_SUBHEAD = process.env.NEXT_PUBLIC_ABOUT_SUBHEAD; export const ABOUT_DESCRIPTION_DEFAULT = process.env.NEXT_PUBLIC_ABOUT_DESCRIPTION || process.env.NEXT_PUBLIC_SIDEBAR_TEXT || process.env.NEXT_PUBLIC_META_DESCRIPTION; // STORAGE // STORAGE: DATABASE export const HAS_DATABASE = Boolean(process.env.POSTGRES_URL); export const POSTGRES_SSL_ENABLED = process.env.DISABLE_POSTGRES_SSL === '1' ? false : true; // STORAGE: REDIS export const REDIS_URL = normalizeRedisUrl( process.env.KV_URL || process.env.KV_REST_API_URL || process.env.EXIF_KV_REST_API_URL || process.env.UPSTASH_REDIS_REST_URL, ); export const REDIS_TOKEN = ( process.env.KV_TOKEN || process.env.KV_REST_API_TOKEN || process.env.EXIF_KV_REST_API_TOKEN || process.env.UPSTASH_REDIS_REST_TOKEN ); export const HAS_REDIS_STORAGE = Boolean(REDIS_URL && REDIS_TOKEN); // STORAGE: VERCEL BLOB export const HAS_VERCEL_BLOB_STORAGE = Boolean(process.env.BLOB_READ_WRITE_TOKEN); // STORAGE: Cloudflare R2 // Includes separate check for client-side usage, i.e., url construction export const HAS_CLOUDFLARE_R2_STORAGE_CLIENT = Boolean(process.env.NEXT_PUBLIC_CLOUDFLARE_R2_BUCKET) && Boolean(process.env.NEXT_PUBLIC_CLOUDFLARE_R2_ACCOUNT_ID) && Boolean(process.env.NEXT_PUBLIC_CLOUDFLARE_R2_PUBLIC_DOMAIN); export const HAS_CLOUDFLARE_R2_STORAGE = HAS_CLOUDFLARE_R2_STORAGE_CLIENT && Boolean(process.env.CLOUDFLARE_R2_ACCESS_KEY) && Boolean(process.env.CLOUDFLARE_R2_SECRET_ACCESS_KEY); // STORAGE: AWS S3 // Includes separate check for client-side usage, i.e., url construction export const HAS_AWS_S3_STORAGE_CLIENT = Boolean(process.env.NEXT_PUBLIC_AWS_S3_BUCKET) && Boolean(process.env.NEXT_PUBLIC_AWS_S3_REGION); export const HAS_AWS_S3_STORAGE = HAS_AWS_S3_STORAGE_CLIENT && Boolean(process.env.AWS_S3_ACCESS_KEY) && Boolean(process.env.AWS_S3_SECRET_ACCESS_KEY); // STORAGE: MINIO // Includes separate check for client-side usage, i.e., url construction export const HAS_MINIO_STORAGE_CLIENT = Boolean(process.env.NEXT_PUBLIC_MINIO_BUCKET) && Boolean(process.env.NEXT_PUBLIC_MINIO_DOMAIN); export const HAS_MINIO_STORAGE = HAS_MINIO_STORAGE_CLIENT && Boolean(process.env.MINIO_ACCESS_KEY) && Boolean(process.env.MINIO_SECRET_ACCESS_KEY); export const HAS_MULTIPLE_STORAGE_PROVIDERS = [ HAS_VERCEL_BLOB_STORAGE, HAS_CLOUDFLARE_R2_STORAGE, HAS_AWS_S3_STORAGE, HAS_MINIO_STORAGE, ].filter(Boolean).length > 1; // Storage preference requires client-available keys // so it can be reached in the browser when uploading export const CURRENT_STORAGE: StorageType = (process.env.NEXT_PUBLIC_STORAGE_PREFERENCE as StorageType | undefined) || ( HAS_MINIO_STORAGE_CLIENT ? 'minio' : HAS_CLOUDFLARE_R2_STORAGE_CLIENT ? 'cloudflare-r2' : HAS_AWS_S3_STORAGE_CLIENT ? 'aws-s3' : 'vercel-blob' ); // PERFORMANCE export const STATICALLY_OPTIMIZED_PHOTOS = process.env.NEXT_PUBLIC_STATICALLY_OPTIMIZE_PHOTOS === '1' || // Legacy environment variable process.env.NEXT_PUBLIC_STATICALLY_OPTIMIZE_PAGES === '1'; export const STATICALLY_OPTIMIZED_PHOTO_OG_IMAGES = process.env.NEXT_PUBLIC_STATICALLY_OPTIMIZE_PHOTO_OG_IMAGES === '1' || // Legacy environment variable process.env.NEXT_PUBLIC_STATICALLY_OPTIMIZE_OG_IMAGES === '1'; export const STATICALLY_OPTIMIZED_PHOTO_CATEGORIES = process.env.NEXT_PUBLIC_STATICALLY_OPTIMIZE_PHOTO_CATEGORIES === '1'; export const STATICALLY_OPTIMIZED_PHOTO_CATEGORY_OG_IMAGES = process.env.NEXT_PUBLIC_STATICALLY_OPTIMIZE_PHOTO_CATEGORY_OG_IMAGES === '1'; export const HAS_STATIC_OPTIMIZATION = STATICALLY_OPTIMIZED_PHOTOS || STATICALLY_OPTIMIZED_PHOTO_OG_IMAGES || STATICALLY_OPTIMIZED_PHOTO_CATEGORIES || STATICALLY_OPTIMIZED_PHOTO_CATEGORY_OG_IMAGES; export const PRESERVE_ORIGINAL_UPLOADS = process.env.NEXT_PUBLIC_PRESERVE_ORIGINAL_UPLOADS === '1' || // Legacy environment variable process.env.NEXT_PUBLIC_PRO_MODE === '1'; export const IMAGE_QUALITY = process.env.NEXT_PUBLIC_IMAGE_QUALITY ? parseInt(process.env.NEXT_PUBLIC_IMAGE_QUALITY) : 75; // Load photos from storage CDN in the browser (skip /_next/image server proxy). // Enabled by default when a public R2 domain is configured; set to 0 to disable. export const DIRECT_STORAGE_IMAGES = process.env.NEXT_PUBLIC_DIRECT_STORAGE_IMAGES === '1' || ( process.env.NEXT_PUBLIC_DIRECT_STORAGE_IMAGES !== '0' && Boolean(process.env.NEXT_PUBLIC_CLOUDFLARE_R2_PUBLIC_DOMAIN) ); export const BLUR_ENABLED = process.env.NEXT_PUBLIC_BLUR_DISABLED !== '1'; // AI export const OPENAI_SECRET_KEY = process.env.OPENAI_SECRET_KEY; export const OPENAI_MODEL = process.env.OPENAI_MODEL; export const OPENAI_BASE_URL = process.env.OPENAI_BASE_URL; export const AI_CONTENT_GENERATION_ENABLED = Boolean(OPENAI_SECRET_KEY); export const AI_TEXT_AUTO_GENERATED_FIELDS = parseAiAutoGeneratedFieldsString( process.env.AI_TEXT_AUTO_GENERATED_FIELDS); // LOCATION SERVICES export const GOOGLE_PLACES_API_KEY = process.env.GOOGLE_PLACES_API_KEY; export const HAS_LOCATION_SERVICES = Boolean(GOOGLE_PLACES_API_KEY); // CATEGORIES export const CATEGORY_VISIBILITY = parseOrderedCategoriesFromString( process.env.NEXT_PUBLIC_CATEGORY_VISIBILITY); export const SHOW_RECENTS = CATEGORY_VISIBILITY.includes('recents'); export const IS_RECENTS_FIRST = CATEGORY_VISIBILITY[0] === 'recents'; export const SHOW_YEARS = CATEGORY_VISIBILITY.includes('years'); export const SHOW_CAMERAS = CATEGORY_VISIBILITY.includes('cameras'); export const SHOW_LENSES = CATEGORY_VISIBILITY.includes('lenses'); export const SHOW_ALBUMS = CATEGORY_VISIBILITY.includes('albums'); export const SHOW_TAGS = CATEGORY_VISIBILITY.includes('tags'); export const SHOW_RECIPES = CATEGORY_VISIBILITY.includes('recipes'); export const SHOW_FILMS = CATEGORY_VISIBILITY.includes('films'); export const SHOW_FOCAL_LENGTHS = CATEGORY_VISIBILITY.includes('focal-lengths'); export const SHOW_CATEGORIES_ON_MOBILE = process.env.NEXT_PUBLIC_HIDE_CATEGORIES_ON_MOBILE !== '1'; export const SHOW_CATEGORY_IMAGE_HOVERS = process.env.NEXT_PUBLIC_HIDE_CATEGORY_IMAGE_HOVERS !== '1'; export const COLLAPSE_SIDEBAR_CATEGORIES = process.env.NEXT_PUBLIC_EXHAUSTIVE_SIDEBAR_CATEGORIES !== '1'; export const HIDE_TAGS_WITH_ONE_PHOTO = process.env.NEXT_PUBLIC_HIDE_TAGS_WITH_ONE_PHOTO === '1'; // SORT export const USER_DEFAULT_SORT_BY = getSortByFromString(process.env.NEXT_PUBLIC_DEFAULT_SORT); export const USER_DEFAULT_SORT_WITH_PRIORITY = process.env.NEXT_PUBLIC_PRIORITY_BASED_SORTING === '1'; export const USER_DEFAULT_SORT_OPTIONS = { sortBy: USER_DEFAULT_SORT_BY, sortWithPriority: USER_DEFAULT_SORT_WITH_PRIORITY, }; export const COLOR_SORT_ENABLED = process.env.NEXT_PUBLIC_COLOR_SORT === '1'; export const COLOR_SORT_STARTING_HUE = parseStartingHue(process.env.NEXT_PUBLIC_COLOR_SORT_STARTING_HUE); export const COLOR_SORT_CHROMA_CUTOFF = parseChromaCutoff(process.env.NEXT_PUBLIC_COLOR_SORT_CHROMA_CUTOFF); export const NAV_SORT_CONTROL = COLOR_SORT_ENABLED ? 'menu' : getNavSortControlFromString(process.env.NEXT_PUBLIC_NAV_SORT_CONTROL); // DISPLAY export const SHOW_ABOUT_PAGE = process.env.NEXT_PUBLIC_HIDE_ABOUT_PAGE !== '1'; export const SHOW_KEYBOARD_SHORTCUT_TOOLTIPS = process.env.NEXT_PUBLIC_HIDE_KEYBOARD_SHORTCUT_TOOLTIPS !== '1'; export const SHOW_EXIF_DATA = process.env.NEXT_PUBLIC_HIDE_EXIF_DATA !== '1'; export const ALWAYS_SHOW_EXPOSURE_COMP = process.env.NEXT_PUBLIC_ALWAYS_SHOW_EXPOSURE_COMP === '1'; export const SHOW_ZOOM_CONTROLS = process.env.NEXT_PUBLIC_HIDE_ZOOM_CONTROLS !== '1'; export const SHOW_TAKEN_AT_TIME = process.env.NEXT_PUBLIC_HIDE_TAKEN_AT_TIME !== '1'; export const SHOW_REPO_LINK = process.env.NEXT_PUBLIC_HIDE_REPO_LINK !== '1'; // GRID export const GRID_HOMEPAGE_ENABLED = process.env.NEXT_PUBLIC_GRID_HOMEPAGE === '1'; export const MASONRY_GRID_ENABLED = process.env.NEXT_PUBLIC_MASONRY_GRID === '1'; export const GRID_ASPECT_RATIO = process.env.NEXT_PUBLIC_GRID_ASPECT_RATIO ? parseFloat(process.env.NEXT_PUBLIC_GRID_ASPECT_RATIO) : 1; export const PREFERS_LOW_DENSITY_GRID = process.env.NEXT_PUBLIC_SHOW_LARGE_THUMBNAILS === '1'; export const HIGH_DENSITY_GRID = GRID_ASPECT_RATIO <= 1 && !PREFERS_LOW_DENSITY_GRID; // DESIGN export const DEFAULT_THEME = process.env.NEXT_PUBLIC_DEFAULT_THEME === 'dark' ? 'dark' : process.env.NEXT_PUBLIC_DEFAULT_THEME === 'light' ? 'light' : 'system'; export const MATTE_PHOTOS = process.env.NEXT_PUBLIC_MATTE_PHOTOS === '1'; export const MATTE_COLOR = process.env.NEXT_PUBLIC_MATTE_COLOR; export const MATTE_COLOR_DARK = process.env.NEXT_PUBLIC_MATTE_COLOR_DARK; // SETTINGS 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 SOCIAL_NETWORKS = parseSocialKeysFromString( // Legacy environment variable process.env.NEXT_PUBLIC_HIDE_SOCIAL === '1' ? 'none' : process.env.NEXT_PUBLIC_SOCIAL_NETWORKS, ); export const SITE_FEEDS_ENABLED = process.env.NEXT_PUBLIC_SITE_FEEDS === '1'; export const OG_TEXT_BOTTOM_ALIGNMENT = (process.env.NEXT_PUBLIC_OG_TEXT_ALIGNMENT ?? '').toUpperCase() === 'BOTTOM'; // SCRIPTS & ANALYTICS export const PAGE_SCRIPT_URLS = process.env.PAGE_SCRIPT_URLS ? process.env.PAGE_SCRIPT_URLS .split(',') .map(url => url.trim().toLocaleLowerCase()) .filter(url => url.startsWith('https://')) : []; // DEBUGGING export const DEBUG_OUTPUTS_ENABLED = process.env.DISABLE_DEBUG_OUTPUTS !== '1'; // INTERNAL export const ADMIN_DEBUG_TOOLS_ENABLED = process.env.ADMIN_DEBUG_TOOLS === '1'; export const ADMIN_SQL_DEBUG_ENABLED = process.env.ADMIN_SQL_DEBUG === '1' && !IS_BUILDING; export const ADMIN_STORAGE_DEBUG_ENABLED = process.env.ADMIN_STORAGE_DEBUG === '1'; export const APP_CONFIGURATION = { // Storage hasDatabase: HAS_DATABASE, isPostgresSslEnabled: POSTGRES_SSL_ENABLED, hasRedisStorage: HAS_REDIS_STORAGE, hasVercelBlobStorage: HAS_VERCEL_BLOB_STORAGE, hasCloudflareR2Storage: HAS_CLOUDFLARE_R2_STORAGE, hasAwsS3Storage: HAS_AWS_S3_STORAGE, hasMinioStorage: HAS_MINIO_STORAGE, hasStorageProvider: ( HAS_VERCEL_BLOB_STORAGE || HAS_CLOUDFLARE_R2_STORAGE || HAS_AWS_S3_STORAGE || HAS_MINIO_STORAGE ), hasMultipleStorageProviders: HAS_MULTIPLE_STORAGE_PROVIDERS, currentStorage: CURRENT_STORAGE, // Auth hasAuthSecret: Boolean(process.env.AUTH_SECRET), hasAdminUser: ( Boolean(process.env.ADMIN_EMAIL) && Boolean(process.env.ADMIN_PASSWORD) ), // Content locale: APP_LOCALE, hasLocale: Boolean(process.env.NEXT_PUBLIC_LOCALE), domain: SITE_DOMAIN_SHORT, hasDomain: Boolean( process.env.NEXT_PUBLIC_DOMAIN || // Legacy environment variable process.env.NEXT_PUBLIC_SITE_DOMAIN, ), metaTitle: META_TITLE, isMetaTitleConfigured: IS_META_TITLE_CONFIGURED, metaDescription: META_DESCRIPTION, isMetaDescriptionConfigured: IS_META_DESCRIPTION_CONFIGURED, navTitle: NAV_TITLE, hasNavTitle: Boolean(CUSTOM_NAV_TITLE), navCaption: NAV_CAPTION, hasNavCaption: Boolean(NAV_CAPTION), sidebarText: SIDEBAR_TEXT, hasSidebarText: Boolean(SIDEBAR_TEXT), // Performance isStaticallyOptimized: HAS_STATIC_OPTIMIZATION, arePhotosStaticallyOptimized: STATICALLY_OPTIMIZED_PHOTOS, arePhotoOGImagesStaticallyOptimized: STATICALLY_OPTIMIZED_PHOTO_OG_IMAGES, arePhotoCategoriesStaticallyOptimized: STATICALLY_OPTIMIZED_PHOTO_CATEGORIES, arePhotoCategoryOgImagesStaticallyOptimized: STATICALLY_OPTIMIZED_PHOTO_CATEGORY_OG_IMAGES, areOriginalUploadsPreserved: PRESERVE_ORIGINAL_UPLOADS, hasImageQuality: Boolean(process.env.NEXT_PUBLIC_IMAGE_QUALITY), imageQuality: IMAGE_QUALITY, isBlurEnabled: BLUR_ENABLED, // AI hasOpenaiModel: Boolean(OPENAI_MODEL), hasOpenaiBaseUrl: Boolean(OPENAI_BASE_URL), isAiTextGenerationEnabled: AI_CONTENT_GENERATION_ENABLED, aiTextAutoGeneratedFields: process.env.AI_TEXT_AUTO_GENERATED_FIELDS ? AI_TEXT_AUTO_GENERATED_FIELDS.length === 0 ? ['none'] : AI_TEXT_AUTO_GENERATED_FIELDS : AI_AUTO_GENERATED_FIELDS_DEFAULT, hasAiTextAutoGeneratedFields: Boolean(process.env.AI_TEXT_AUTO_GENERATED_FIELDS), // Location services hasLocationServices: HAS_LOCATION_SERVICES, // Categories hasCategoryVisibility: Boolean(process.env.NEXT_PUBLIC_CATEGORY_VISIBILITY), categoryVisibility: CATEGORY_VISIBILITY, showCategoriesOnMobile: SHOW_CATEGORIES_ON_MOBILE, showCategoryImageHover: SHOW_CATEGORY_IMAGE_HOVERS, collapseSidebarCategories: COLLAPSE_SIDEBAR_CATEGORIES, hideTagsWithOnePhoto: HIDE_TAGS_WITH_ONE_PHOTO, // Sort hasDefaultSortBy: Boolean(process.env.NEXT_PUBLIC_DEFAULT_SORT), defaultSortBy: USER_DEFAULT_SORT_BY, hasNavSortControl: Boolean(process.env.NEXT_PUBLIC_NAV_SORT_CONTROL), navSortControl: NAV_SORT_CONTROL, isColorSortEnabled: COLOR_SORT_ENABLED, hasColorSortConfiguration: Boolean(process.env.NEXT_PUBLIC_COLOR_SORT_STARTING_HUE) || Boolean(process.env.NEXT_PUBLIC_COLOR_SORT_CHROMA_CUTOFF), colorSortStartingHue: COLOR_SORT_STARTING_HUE, colorSortChromaCutoff: COLOR_SORT_CHROMA_CUTOFF, isSortWithPriority: USER_DEFAULT_SORT_WITH_PRIORITY, // Display showAboutPage: SHOW_ABOUT_PAGE, showKeyboardShortcutTooltips: SHOW_KEYBOARD_SHORTCUT_TOOLTIPS, showExifInfo: SHOW_EXIF_DATA, alwaysShowExposureComp: ALWAYS_SHOW_EXPOSURE_COMP, showZoomControls: SHOW_ZOOM_CONTROLS, showTakenAtTimeHidden: SHOW_TAKEN_AT_TIME, showRepoLink: SHOW_REPO_LINK, // Grid isGridHomepageEnabled: GRID_HOMEPAGE_ENABLED, isMasonryGridEnabled: MASONRY_GRID_ENABLED, gridAspectRatio: GRID_ASPECT_RATIO, hasGridAspectRatio: Boolean(process.env.NEXT_PUBLIC_GRID_ASPECT_RATIO), hasHighGridDensity: HIGH_DENSITY_GRID, hasGridDensityPreference: Boolean(process.env.NEXT_PUBLIC_SHOW_LARGE_THUMBNAILS), // Design hasDefaultTheme: Boolean(process.env.NEXT_PUBLIC_DEFAULT_THEME), defaultTheme: DEFAULT_THEME, arePhotosMatted: MATTE_PHOTOS, arePhotoMatteColorsConfigured: Boolean(MATTE_COLOR) || Boolean(MATTE_COLOR_DARK), matteColor: MATTE_COLOR, matteColorDark: MATTE_COLOR_DARK, // Settings isGeoPrivacyEnabled: GEO_PRIVACY_ENABLED, arePublicDownloadsEnabled: ALLOW_PUBLIC_DOWNLOADS, hasSocialKeys: Boolean(process.env.NEXT_PUBLIC_SOCIAL_NETWORKS), socialKeys: SOCIAL_NETWORKS, areSiteFeedsEnabled: SITE_FEEDS_ENABLED, isOgTextBottomAligned: OG_TEXT_BOTTOM_ALIGNMENT, // Scripts & Analytics hasPageScriptUrls: PAGE_SCRIPT_URLS.length > 0, pageScriptUrls: PAGE_SCRIPT_URLS, // Debugging isDebuggingEnabled: DEBUG_OUTPUTS_ENABLED, // Internal areInternalToolsEnabled: ( ADMIN_DEBUG_TOOLS_ENABLED || ADMIN_SQL_DEBUG_ENABLED ), areAdminDebugToolsEnabled: ADMIN_DEBUG_TOOLS_ENABLED, isAdminSqlDebugEnabled: ADMIN_SQL_DEBUG_ENABLED, isAdminStorageDebugEnabled: ADMIN_STORAGE_DEBUG_ENABLED, // Misc nextVersion: dependencies.next, reactVersion: dependencies.react, nodeVersion: (process.version || '').match(/[0-9.]+$/)?.[0], baseUrl: BASE_URL, baseUrlShare: BASE_URL_SHARE, commitSha: VERCEL_GIT_COMMIT_SHA_SHORT, commitMessage: VERCEL_GIT_COMMIT_MESSAGE, commitUrl: VERCEL_GIT_COMMIT_URL, }; const ALL_DEPRECATED_ENV_VARS = [{ old: 'NEXT_PUBLIC_SITE_DOMAIN', replacement: 'NEXT_PUBLIC_DOMAIN', }, { old: 'NEXT_PUBLIC_SITE_DESCRIPTION', replacement: 'NEXT_PUBLIC_NAV_CAPTION', }, { old: 'NEXT_PUBLIC_SITE_TITLE', replacement: 'NEXT_PUBLIC_META_TITLE', }, { old: 'NEXT_PUBLIC_SITE_ABOUT', replacement: 'NEXT_PUBLIC_SIDEBAR_TEXT', }, { old: 'NEXT_PUBLIC_PAGE_ABOUT', replacement: 'NEXT_PUBLIC_SIDEBAR_TEXT', }, { old: 'NEXT_PUBLIC_STATICALLY_OPTIMIZE_PAGES', replacement: 'NEXT_PUBLIC_STATICALLY_OPTIMIZE_PHOTOS', }, { old: 'NEXT_PUBLIC_STATICALLY_OPTIMIZE_OG_IMAGES', replacement: 'NEXT_PUBLIC_STATICALLY_OPTIMIZE_PHOTO_OG_IMAGES', }, { old: 'NEXT_PUBLIC_PRO_MODE', replacement: 'NEXT_PUBLIC_PRESERVE_ORIGINAL_UPLOADS', }, { old: 'NEXT_PUBLIC_HIDE_SOCIAL', replacement: 'NEXT_PUBLIC_SOCIAL_NETWORKS', }]; export const USED_DEPRECATED_ENV_VARS = ALL_DEPRECATED_ENV_VARS .filter(({ old }) => Boolean(process.env[old])); export const HAS_DEPRECATED_ENV_VARS = USED_DEPRECATED_ENV_VARS.length > 0; export const IS_APP_READY = APP_CONFIGURATION.hasDatabase && APP_CONFIGURATION.hasStorageProvider && APP_CONFIGURATION.hasAuthSecret && APP_CONFIGURATION.hasAdminUser; export type AppConfiguration = typeof APP_CONFIGURATION;