131 lines
5.4 KiB
JavaScript
131 lines
5.4 KiB
JavaScript
"use strict";
|
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
};
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.getStorageRoot = getStorageRoot;
|
|
exports.getPublicStorageRoot = getPublicStorageRoot;
|
|
exports.getPrivateStorageRoot = getPrivateStorageRoot;
|
|
exports.getLegacyStorageRoots = getLegacyStorageRoots;
|
|
exports.assertStorageConfiguration = assertStorageConfiguration;
|
|
exports.getProtectedCustomerLicenseImageUrl = getProtectedCustomerLicenseImageUrl;
|
|
exports.uploadImage = uploadImage;
|
|
exports.resolveStoredFilePath = resolveStoredFilePath;
|
|
exports.deleteImage = deleteImage;
|
|
const fs_1 = __importDefault(require("fs"));
|
|
const path_1 = __importDefault(require("path"));
|
|
const crypto_1 = __importDefault(require("crypto"));
|
|
const APP_PACKAGE_ROOT = path_1.default.resolve(__dirname, '..', '..', '..');
|
|
const APP_SOURCE_ROOT = path_1.default.join(APP_PACKAGE_ROOT, 'src');
|
|
const APP_DIST_ROOT = path_1.default.join(APP_PACKAGE_ROOT, 'dist');
|
|
const DEFAULT_STORAGE_ROOT = path_1.default.join(APP_PACKAGE_ROOT, 'storage');
|
|
const LEGACY_STORAGE_ROOTS = [
|
|
path_1.default.join(APP_SOURCE_ROOT, 'lib', 'storage'),
|
|
];
|
|
function getStorageRoot() {
|
|
return process.env.FILE_STORAGE_ROOT
|
|
? path_1.default.resolve(process.env.FILE_STORAGE_ROOT)
|
|
: DEFAULT_STORAGE_ROOT;
|
|
}
|
|
function getPublicStorageRoot() {
|
|
return path_1.default.join(getStorageRoot(), 'public');
|
|
}
|
|
function getPrivateStorageRoot() {
|
|
return path_1.default.join(getStorageRoot(), 'private');
|
|
}
|
|
function getLegacyStorageRoots() {
|
|
return LEGACY_STORAGE_ROOTS;
|
|
}
|
|
function isWithinPath(targetPath, parentPath) {
|
|
const relative = path_1.default.relative(parentPath, targetPath);
|
|
return relative === '' || (!relative.startsWith('..') && !path_1.default.isAbsolute(relative));
|
|
}
|
|
function assertStorageConfiguration() {
|
|
const storageRoot = getStorageRoot();
|
|
if (process.env.NODE_ENV === 'production' && !process.env.FILE_STORAGE_ROOT) {
|
|
throw new Error('FILE_STORAGE_ROOT must be set in production so uploads are stored on the mounted volume.');
|
|
}
|
|
if (process.env.NODE_ENV === 'production') {
|
|
const forbiddenRoots = [APP_PACKAGE_ROOT, APP_SOURCE_ROOT, APP_DIST_ROOT];
|
|
const invalidRoot = forbiddenRoots.find((root) => isWithinPath(storageRoot, root));
|
|
if (invalidRoot) {
|
|
throw new Error(`FILE_STORAGE_ROOT must point outside the API app tree in production. Received ${storageRoot}, which is inside ${invalidRoot}.`);
|
|
}
|
|
}
|
|
return storageRoot;
|
|
}
|
|
function ensureStorageRoot(visibility) {
|
|
assertStorageConfiguration();
|
|
const root = visibility === 'public' ? getPublicStorageRoot() : getPrivateStorageRoot();
|
|
fs_1.default.mkdirSync(root, { recursive: true });
|
|
return root;
|
|
}
|
|
function inferVisibility(folder) {
|
|
const normalized = folder.replace(/\\/g, '/');
|
|
if (/\/customers\//.test(`/${normalized}/`))
|
|
return 'private';
|
|
if (/\/licenses?\//.test(`/${normalized}/`))
|
|
return 'private';
|
|
if (/\/contracts?\//.test(`/${normalized}/`))
|
|
return 'private';
|
|
if (/\/documents?\//.test(`/${normalized}/`))
|
|
return 'private';
|
|
if (/\/inspections?\//.test(`/${normalized}/`))
|
|
return 'private';
|
|
if (/\/internal\//.test(`/${normalized}/`))
|
|
return 'private';
|
|
return 'public';
|
|
}
|
|
function getApiBase() {
|
|
return (process.env.API_URL ?? 'http://localhost:4000').replace(/\/$/, '');
|
|
}
|
|
function getDashboardBase() {
|
|
return (process.env.DASHBOARD_URL ??
|
|
process.env.NEXT_PUBLIC_DASHBOARD_URL ??
|
|
'http://localhost:3000/dashboard').replace(/\/$/, '');
|
|
}
|
|
function getStorageRelativePath(imageUrl) {
|
|
const marker = '/storage/';
|
|
const idx = imageUrl.indexOf(marker);
|
|
if (idx === -1)
|
|
return null;
|
|
return imageUrl.slice(idx + marker.length);
|
|
}
|
|
function getProtectedCustomerLicenseImageUrl(customerId) {
|
|
return `${getDashboardBase()}/api/v1/customers/${customerId}/license-image`;
|
|
}
|
|
async function uploadImage(buffer, folder, publicId, visibility = inferVisibility(folder)) {
|
|
const storageRoot = ensureStorageRoot(visibility);
|
|
const folderPath = path_1.default.join(storageRoot, folder);
|
|
if (!isWithinPath(folderPath, storageRoot)) {
|
|
throw new Error('Upload path escapes storage root');
|
|
}
|
|
fs_1.default.mkdirSync(folderPath, { recursive: true });
|
|
const filename = publicId
|
|
? `${publicId}.jpg`
|
|
: `${crypto_1.default.randomBytes(16).toString('hex')}.jpg`;
|
|
fs_1.default.writeFileSync(path_1.default.join(folderPath, filename), buffer);
|
|
return `${getApiBase()}/storage/${folder}/${filename}`;
|
|
}
|
|
function resolveStoredFilePath(imageUrl) {
|
|
const relative = getStorageRelativePath(imageUrl);
|
|
if (!relative)
|
|
return null;
|
|
const roots = [getPublicStorageRoot(), getPrivateStorageRoot(), ...getLegacyStorageRoots(), assertStorageConfiguration()];
|
|
for (const root of roots) {
|
|
const filePath = path_1.default.join(root, relative);
|
|
if (!isWithinPath(filePath, root))
|
|
continue;
|
|
if (fs_1.default.existsSync(filePath)) {
|
|
return filePath;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
async function deleteImage(imageUrl) {
|
|
const filePath = resolveStoredFilePath(imageUrl);
|
|
if (filePath && fs_1.default.existsSync(filePath)) {
|
|
fs_1.default.unlinkSync(filePath);
|
|
}
|
|
}
|
|
//# sourceMappingURL=storage.js.map
|