Files
tehran_taj/apps/api/src/modules/admin-uploads/upload-path.ts
T

50 lines
1.7 KiB
TypeScript
Raw Normal View History

2026-09-19 15:44:00 +03:30
import { existsSync, statSync } from 'node:fs';
import { basename, isAbsolute, join, relative, resolve } from 'node:path';
import { envOr } from '../../common/env';
2026-09-19 15:44:00 +03:30
/**
* Directory files are written to and served from.
*
* Default matches docker-compose's `api_uploads:/app/storage/uploads` volume.
* A nested leftover (`storage/uploads/uploads/`) is also checked when serving,
* because copying a folder named `uploads` into the volume creates that extra
* directory and the previous static root then 404'd every file.
*/
export function getUploadDir(): string {
return resolve(envOr('UPLOAD_DIR', join(process.cwd(), 'storage', 'uploads')));
2026-09-19 15:44:00 +03:30
}
const UPLOAD_MIME: Record<string, string> = {
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.gif': 'image/gif',
'.webp': 'image/webp',
'.avif': 'image/avif',
'.svg': 'image/svg+xml',
'.pdf': 'application/pdf',
};
export function mimeForUpload(filePath: string): string | undefined {
const dot = filePath.lastIndexOf('.');
const ext = dot >= 0 ? filePath.slice(dot).toLowerCase() : '';
return UPLOAD_MIME[ext];
}
/** Resolve a public filename to an on-disk file, or null if it is missing/unsafe. */
export function resolveStoredUpload(rawName: string): string | null {
const safe = basename(rawName);
if (!safe || safe === '.' || safe === '..') return null;
const dir = getUploadDir();
const candidates = [join(dir, safe), join(dir, 'uploads', safe)];
for (const candidate of candidates) {
const resolved = resolve(candidate);
const rel = relative(dir, resolved);
if (!rel || rel.startsWith('..') || isAbsolute(rel)) continue;
if (existsSync(resolved) && statSync(resolved).isFile()) return resolved;
}
return null;
}