import type { H3Event } from 'h3' import { getSetting, setSetting, SETTINGS_ALLOWED_REFERER_HOSTS, SETTINGS_API_UPLOAD_TOKEN, SETTINGS_AUTO_DELETE_DAYS, SETTINGS_AUTO_DELETE_ENABLED_AT, SETTINGS_IMAGE_BASE_URL, SETTINGS_WEBP_QUALITY, isAllowRegistration } from './db' export function getRuntimeEnv( event: H3Event, envKey: string, configKey?: string ): string { try { const fromProcess = process.env[envKey] if (fromProcess) { return fromProcess } } catch { // process.env may be unavailable in some runtimes } if (configKey) { const config = useRuntimeConfig(event) as Record const fromConfig = config[configKey] if (typeof fromConfig === 'string' && fromConfig.length > 0) { return fromConfig } } return '' } export function isAdminSecretConfigured(event: H3Event): boolean { return getAdminSecret(event).length > 0 } export function getAdminSecret(event: H3Event): string { return getRuntimeEnv(event, 'ADMIN_SECRET', 'adminSecret') || getRuntimeEnv(event, 'NUXT_ADMIN_SECRET', 'adminSecret') } export type SettingSource = 'env' | 'db' | 'none' export function normalizeImageBaseUrl(raw: string): string { return raw.trim().replace(/\/$/, '') } export function getImageBaseUrlFromEnv(event: H3Event): string { return normalizeImageBaseUrl( getRuntimeEnv(event, 'IMAGE_BASE_URL', 'imageBaseUrl') ) } export function isImageBaseUrlEnvConfigured(event: H3Event): boolean { return getImageBaseUrlFromEnv(event).length > 0 } export function getImageBaseUrlSource(event: H3Event): SettingSource { if (getSetting(SETTINGS_IMAGE_BASE_URL) !== null) return 'db' if (isImageBaseUrlEnvConfigured(event)) return 'env' return 'none' } export function getImageBaseUrlConfigured(event: H3Event): string { const dbRaw = getSetting(SETTINGS_IMAGE_BASE_URL) if (dbRaw !== null) return normalizeImageBaseUrl(dbRaw) return getImageBaseUrlFromEnv(event) } export function getImageBaseUrl(event: H3Event): string { const dbRaw = getSetting(SETTINGS_IMAGE_BASE_URL) if (dbRaw !== null) { const configured = normalizeImageBaseUrl(dbRaw) if (configured) return configured } else { const fromEnv = getImageBaseUrlFromEnv(event) if (fromEnv) return fromEnv } // 单域名部署:未配置时用当前请求的域名(支持反代转发头) const requestUrl = getRequestURL(event, { xForwardedHost: true, xForwardedProto: true }) return requestUrl.origin } export function getApiUploadTokenFromEnv(event: H3Event): string { return ( getRuntimeEnv(event, 'API_UPLOAD_TOKEN', 'apiUploadToken') || getRuntimeEnv(event, 'NUXT_API_UPLOAD_TOKEN', 'apiUploadToken') ) } export function isApiUploadTokenEnvConfigured(event: H3Event): boolean { return getApiUploadTokenFromEnv(event).length > 0 } export type ApiUploadTokenSource = SettingSource export function getApiUploadTokenSource(event: H3Event): ApiUploadTokenSource { if (getApiUploadTokenFromEnv(event)) return 'env' if (getSetting(SETTINGS_API_UPLOAD_TOKEN)) return 'db' return 'none' } export function isApiUploadTokenConfigured(event: H3Event): boolean { return getApiUploadToken(event).length > 0 } /** 优先级:环境变量 → SQLite settings → runtimeConfig */ export function getApiUploadToken(event: H3Event): string { const fromEnv = getApiUploadTokenFromEnv(event) if (fromEnv) return fromEnv const fromDb = getSetting(SETTINGS_API_UPLOAD_TOKEN) if (fromDb) return fromDb return '' } export const DEFAULT_WEBP_QUALITY = 80 export type WebpQualitySource = 'env' | 'db' | 'default' export function parseWebpQuality(raw: string): number | null { const value = Number(raw) if (Number.isFinite(value) && value >= 1 && value <= 100) { return Math.round(value) } return null } export function getWebpQualityFromEnv(): string { try { return process.env.WEBP_QUALITY?.trim() ?? '' } catch { return '' } } export function isWebpQualityEnvConfigured(): boolean { return parseWebpQuality(getWebpQualityFromEnv()) !== null } export function getWebpQualitySource(): WebpQualitySource { if (getSetting(SETTINGS_WEBP_QUALITY) !== null) return 'db' if (isWebpQualityEnvConfigured()) return 'env' return 'default' } /** 优先级:SQLite settings → 环境变量 → 默认 80 */ export function getWebpQuality(_event?: H3Event): number { const dbRaw = getSetting(SETTINGS_WEBP_QUALITY) if (dbRaw !== null) { const fromDb = parseWebpQuality(dbRaw) if (fromDb !== null) return fromDb } const fromEnv = parseWebpQuality(getWebpQualityFromEnv()) if (fromEnv !== null) return fromEnv return DEFAULT_WEBP_QUALITY } export function normalizeRefererHosts(raw: string): string { const seen = new Set() const hosts: string[] = [] for (const part of raw.split(',')) { const host = part.trim().toLowerCase() if (!host || seen.has(host)) continue seen.add(host) hosts.push(host) } return hosts.join(',') } export function getRefererHostsFromEnv(): string { try { return normalizeRefererHosts(process.env.ALLOWED_REFERER_HOSTS ?? '') } catch { return '' } } export function isRefererEnvConfigured(): boolean { return getRefererHostsFromEnv().length > 0 } export function getRefererSource(): SettingSource { if (getSetting(SETTINGS_ALLOWED_REFERER_HOSTS) !== null) return 'db' if (isRefererEnvConfigured()) return 'env' return 'none' } /** 优先级:SQLite settings → 环境变量;均可不配置 */ export function getAllowedRefererHostsRaw(_event?: H3Event): string { const dbRaw = getSetting(SETTINGS_ALLOWED_REFERER_HOSTS) if (dbRaw !== null) return normalizeRefererHosts(dbRaw) return getRefererHostsFromEnv() } export function isRefererConfigured(event?: H3Event): boolean { return getAllowedRefererHostsRaw(event).length > 0 } export const DEFAULT_AUTO_DELETE_DAYS = 0 export const MAX_AUTO_DELETE_DAYS = 3650 export type AutoDeleteDaysSource = 'env' | 'db' | 'default' export function parseAutoDeleteDays(raw: string): number | null { const trimmed = raw.trim() if (!trimmed) return null const value = Number(trimmed) if (!Number.isFinite(value) || value < 0 || !Number.isInteger(value)) { return null } if (value > MAX_AUTO_DELETE_DAYS) { return null } return value } export function getAutoDeleteDaysFromEnv(): string { try { return process.env.AUTO_DELETE_DAYS?.trim() ?? '' } catch { return '' } } export function isAutoDeleteDaysEnvConfigured(): boolean { return parseAutoDeleteDays(getAutoDeleteDaysFromEnv()) !== null } export function getAutoDeleteDaysSource(): AutoDeleteDaysSource { if (getSetting(SETTINGS_AUTO_DELETE_DAYS) !== null) return 'db' if (isAutoDeleteDaysEnvConfigured()) return 'env' return 'default' } /** 优先级:SQLite settings → 环境变量 → 默认 0(关闭) */ export function getAutoDeleteDays(_event?: H3Event): number { const dbRaw = getSetting(SETTINGS_AUTO_DELETE_DAYS) if (dbRaw !== null) { const fromDb = parseAutoDeleteDays(dbRaw) if (fromDb !== null) return fromDb } const fromEnv = parseAutoDeleteDays(getAutoDeleteDaysFromEnv()) if (fromEnv !== null) return fromEnv return DEFAULT_AUTO_DELETE_DAYS } export function getGlobalAutoDeleteEnabledAt(): string | null { const raw = getSetting(SETTINGS_AUTO_DELETE_ENABLED_AT) return raw?.trim() ? raw : null } export function getGlobalAutoDeletePolicy(): { days: number, enabledAt: string | null } { return { days: getAutoDeleteDays(), enabledAt: getGlobalAutoDeleteEnabledAt() } } export function setGlobalAutoDeletePolicy(days: number): void { const dbRaw = getSetting(SETTINGS_AUTO_DELETE_DAYS) const currentDays = dbRaw !== null ? (parseAutoDeleteDays(dbRaw) ?? 0) : 0 const currentEnabledAt = getGlobalAutoDeleteEnabledAt() const now = new Date().toISOString() let enabledAt = currentEnabledAt if (days > 0 && currentDays === 0) { enabledAt = now } else if (days === 0) { enabledAt = null } setSetting(SETTINGS_AUTO_DELETE_DAYS, String(days)) setSetting(SETTINGS_AUTO_DELETE_ENABLED_AT, enabledAt ?? '') } export function isValidRefererHost(host: string): boolean { return /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i.test(host) } export function isValidImageBaseUrl(value: string): boolean { if (!value) return true try { const url = new URL(value) return url.protocol === 'http:' || url.protocol === 'https:' } catch { return false } } export function getSettingsPayload(event: H3Event) { const config = useRuntimeConfig(event) const webpQuality = getWebpQuality(event) const allowedRefererHosts = getAllowedRefererHostsRaw(event) const imageBaseUrl = getImageBaseUrl(event) const autoDeleteDays = getAutoDeleteDays(event) const appVersion = config.appVersion as string return { apiUploadToken: getApiUploadToken(event), tokenSource: getApiUploadTokenSource(event), envTokenOverride: isApiUploadTokenEnvConfigured(event), webpQuality, webpQualitySource: getWebpQualitySource(), allowedRefererHosts, refererSource: getRefererSource(), refererEnvFallback: getRefererHostsFromEnv(), imageBaseUrl, imageBaseUrlConfigured: getImageBaseUrlConfigured(event), imageBaseUrlSource: getImageBaseUrlSource(event), autoDeleteDays, autoDeleteDaysSource: getAutoDeleteDaysSource(), autoDeleteEnvFallback: parseAutoDeleteDays(getAutoDeleteDaysFromEnv()) ?? 0, allowRegistration: isAllowRegistration(), appVersion, env: { webpQuality, refererConfigured: allowedRefererHosts.length > 0, imageBaseUrl, appVersion } } }