import path from 'node:path'; import { createImgproxySigner } from './imgproxy-signer.mjs'; export const PUBLIC_IMAGES_DIR = 'images'; let cachedSigner = null; let signerInitAttempted = false; export function resolveImgproxyBaseUrl(env = process.env) { return (env.IMGPROXY_BASE_URL ?? 'http://localhost:20081').replace(/\/$/, ''); } export function getImgproxySigner(env = process.env) { if (signerInitAttempted) return cachedSigner; signerInitAttempted = true; const key = env.IMGPROXY_SIGNING_KEY?.trim(); const salt = env.IMGPROXY_SIGNING_SALT?.trim(); if (!key || !salt) return null; try { cachedSigner = createImgproxySigner(key, salt); } catch { cachedSigner = null; } return cachedSigner; } export function formatImageDateFolder(date = new Date()) { const value = date instanceof Date ? date : new Date(date); const year = value.getFullYear(); const month = String(value.getMonth() + 1).padStart(2, '0'); const day = String(value.getDate()).padStart(2, '0'); return `${year}-${month}-${day}`; } function extensionForMime(mimeType, fallbackFilename = '') { const ext = path.extname(String(fallbackFilename)).toLowerCase(); if (ext && /^\.[a-z0-9]{1,8}$/.test(ext)) return ext === '.jpeg' ? '.jpg' : ext; if (mimeType === 'image/jpeg') return '.jpg'; if (mimeType === 'image/png') return '.png'; if (mimeType === 'image/webp') return '.webp'; if (mimeType === 'image/gif') return '.gif'; return '.img'; } function sanitizeImageBasename(filename, assetId) { const raw = path.basename(String(filename ?? ''), path.extname(String(filename ?? ''))).trim(); const normalized = raw .normalize('NFKC') .replace(/[^\w\u4e00-\u9fff.-]+/g, '-') .replace(/^-+|-+$/g, '') .slice(0, 80); return normalized || assetId.slice(0, 8); } export function buildPublicImagePaths({ userId, assetId, mimeType, originalFilename, date = new Date(), uniqueSuffix = null, }) { const dateFolder = formatImageDateFolder(date); const extension = extensionForMime(mimeType, originalFilename); const basename = sanitizeImageBasename(originalFilename, assetId); const suffix = uniqueSuffix ? `-${uniqueSuffix}` : ''; const filename = `${basename}${suffix}${extension}`; const storageKey = path.posix.join('users', userId, PUBLIC_IMAGES_DIR, dateFolder, filename); const workspaceRelativePath = path.posix.join('public', PUBLIC_IMAGES_DIR, dateFolder, filename); return { storageKey, workspaceRelativePath, filename, dateFolder }; } export function buildImgproxyDisplayUrl(storageKey, options = {}) { const baseUrl = (options.baseUrl ?? resolveImgproxyBaseUrl()).replace(/\/$/, ''); const signer = options.signer ?? getImgproxySigner(); if (signer) { return signer.buildUrl(baseUrl, storageKey, options.preset ?? 'display'); } const preset = options.preset ?? 'display'; if (preset === 'vision') { return `${baseUrl}/unsafe/rs:fit:768:768:0/q:60/plain/local:///${storageKey}@jpg`; } if (preset === 'thumb') { return `${baseUrl}/unsafe/rs:fit:256:256:0/q:70/plain/local:///${storageKey}@jpg`; } return `${baseUrl}/unsafe/rs:fit:1280:1280:0/q:85/plain/local:///${storageKey}@jpg`; } export function buildUserImagePublicUrl({ userId, storageKey, mimeType, originalFilename }) { void userId; void mimeType; void originalFilename; return buildImgproxyDisplayUrl(storageKey); } export function isPublicImageStorageKey(storageKey) { const normalized = String(storageKey ?? '').replace(/\\/g, '/'); return /\/images\/\d{4}-\d{2}-\d{2}\//.test(normalized); }