agentlang
Version:
The easiest way to build the most reliable AI agents - enterprise-grade teams of AI agents that collaborate with each other and humans
849 lines • 34.9 kB
JavaScript
import chalk from 'chalk';
import express from 'express';
import * as path from 'path';
import { getAllChildRelationships, getAllEntityNames, getAllEventNames, Instance, isBetweenRelationship, makeInstance, objectAsInstanceAttributes, fetchModule, getModuleNames, Record, fetchRefTarget, getAttributeNames, } from '../runtime/module.js';
import { isNodeEnv } from '../utils/runtime.js';
import { parseAndEvaluateStatement } from '../runtime/interpreter.js';
import { logger } from '../runtime/logger.js';
import { requireAuth, verifySession } from '../runtime/modules/auth.js';
import { BypassSession, isNoSession, NoSession } from '../runtime/auth/defs.js';
import { escapeFqName, forceAsEscapedName, forceAsFqName, isString, isStringNumeric, makeFqName, restoreFqName, nameToPath, walkDownInstancePath, DefaultFileHandlingDirectory, splitRefs, splitFqName, escapeSepInPath, } from '../runtime/util.js';
import { BadRequestError, isPathAttribute, PathAttributeNameQuery, UnauthorisedError, } from '../runtime/defs.js';
import { evaluate } from '../runtime/interpreter.js';
import { findFileByFilename, createFileRecord, deleteFileRecord, } from '../runtime/modules/files.js';
export async function startServer(appSpec, port, host, config) {
const app = express();
app.use(express.json());
// Add CORS middleware
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
// Handle preflight requests
if (req.method === 'OPTIONS') {
res.sendStatus(200);
return;
}
next();
});
let uploadDir = null;
let upload = null;
if (isNodeEnv) {
const multer = (await import('multer')).default;
const fs = await import('fs');
uploadDir = path.join(process.cwd(), DefaultFileHandlingDirectory);
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir, { recursive: true });
}
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, uploadDir);
},
filename: (req, file, cb) => {
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9);
const ext = path.extname(file.originalname);
const basename = path.basename(file.originalname, ext);
cb(null, `${basename}-${uniqueSuffix}${ext}`);
},
});
upload = multer({
storage: storage,
limits: {
fileSize: 1024 * 1024 * 1024,
},
});
}
const appName = appSpec.name;
const appVersion = appSpec.version;
app.get('/', (req, res) => {
res.send({ agentlang: { application: `${appName}@${appVersion}` } });
});
app.get('/meta', (req, res) => {
handleMetaGet(req, res);
});
if (isNodeEnv && upload && uploadDir) {
app.post('/uploadFile', upload.single('file'), (req, res) => {
handleFileUpload(req, res, config);
});
app.get('/downloadFile/:filename', (req, res) => {
handleFileDownload(req, res, uploadDir, config);
});
app.post('/deleteFile/:filename', (req, res) => {
handleFileDelete(req, res, uploadDir, config);
});
}
else {
app.post('/uploadFile', (req, res) => {
res.status(501).send({ error: 'File upload is only supported in Node.js environment' });
});
app.get('/downloadFile/:filename', (req, res) => {
res.status(501).send({ error: 'File download is only supported in Node.js environment' });
});
app.post('/deleteFile/:filename', (req, res) => {
res.status(501).send({ error: 'File delete is only supported in Node.js environment' });
});
}
getAllEventNames().forEach((eventNames, moduleName) => {
const m = fetchModule(moduleName);
eventNames.forEach((n) => {
if (m.eventIsPublic(n))
app.post(`/${moduleName}/${n}`, (req, res) => {
handleEventPost(moduleName, n, req, res);
});
});
});
getAllEntityNames().forEach((entityNames, moduleName) => {
entityNames.forEach((n) => {
app.get(`/${moduleName}/${n}`, (req, res) => {
handleEntityGet(moduleName, n, req, res);
});
app.get(`/${moduleName}/${n}/*path`, (req, res) => {
handleEntityGet(moduleName, n, req, res);
});
app.post(`/${moduleName}/${n}`, (req, res) => {
handleEntityPost(moduleName, n, req, res);
});
app.post(`/${moduleName}/${n}/*path`, (req, res) => {
handleEntityPost(moduleName, n, req, res);
});
app.put(`/${moduleName}/${n}/*path`, (req, res) => {
handleEntityPut(moduleName, n, req, res);
});
app.patch(`/${moduleName}/${n}/*path`, (req, res) => {
handleEntityPut(moduleName, n, req, res);
});
app.delete(`/${moduleName}/${n}/*path`, (req, res) => {
handleEntityDelete(moduleName, n, req, res);
});
});
});
const cb = () => {
console.log(chalk.green(`Application ${chalk.bold(appName + ' version ' + appVersion)} started on port ${chalk.bold(port)}`));
};
if (host) {
app.listen(port, host, cb);
}
else {
app.listen(port, cb);
}
}
function ok(res) {
return (value) => {
const result = normalizedResult(value);
res.contentType('application/json');
res.send(JSON.stringify(result));
};
}
function statusFromErrorType(err) {
if (err instanceof UnauthorisedError) {
return 401;
}
else if (err instanceof BadRequestError) {
return 400;
}
else {
return 500;
}
}
function internalError(res) {
return (reason) => {
logger.error(reason);
res.status(statusFromErrorType(reason)).send(reason.message);
};
}
function patternFromAttributes(moduleName, recName, attrs) {
const attrsStrs = new Array();
attrs.forEach((v, n) => {
let av = isString(v) ? `"${v}"` : v;
if (av instanceof Object) {
av = JSON.stringify(av);
}
if (isPathAttribute(n)) {
av = escapeSepInPath(av);
}
attrsStrs.push(`${n} ${av}`);
});
return `{${moduleName}/${recName} { ${attrsStrs.join(',\n')} }}`;
}
function normalizeRequestPath(path, moduleName) {
if (path.length <= 1) {
return path;
}
const result = new Array();
result.push(path[0]);
for (let i = 1; i < path.length; ++i) {
const rn = forceAsEscapedName(path[i], moduleName);
const en = forceAsEscapedName(path[++i], moduleName);
result.push(rn);
result.push(en);
if (i < path.length) {
result.push(path[++i]);
}
}
return result;
}
function pathFromRequest(moduleName, entryName, req) {
const path = req.params.path;
if (!path) {
const url = req.url;
const i = url.indexOf('?');
if (i > 0) {
return url.substring(0, i);
}
return url;
}
let p = '';
if (path instanceof Array) {
p = normalizeRequestPath(path, moduleName).join('/');
}
else {
p = path.toString();
}
p = p.trim();
if (p.endsWith('/')) {
p = p.substring(0, p.length - 1);
}
return `${escapeFqName(makeFqName(moduleName, entryName))}/${p}`;
}
async function handleEventPost(moduleName, eventName, req, res) {
try {
const sessionInfo = await verifyAuth(moduleName, eventName, req.headers.authorization);
if (isNoSession(sessionInfo)) {
res.status(401).send('Authorization required');
return;
}
const inst = makeInstance(moduleName, eventName, objectAsInstanceAttributes(req.body)).setAuthContext(sessionInfo);
evaluate(inst, ok(res)).catch(internalError(res));
}
catch (err) {
logger.error(err);
res.status(500).send(err.toString());
}
}
async function handleEntityPost(moduleName, entityName, req, res) {
try {
const sessionInfo = await verifyAuth(moduleName, entityName, req.headers.authorization);
if (isNoSession(sessionInfo)) {
res.status(401).send('Authorization required');
return;
}
const pattern = req.params.path
? createChildPattern(moduleName, entityName, req)
: patternFromAttributes(moduleName, entityName, objectAsInstanceAttributes(req.body));
parseAndEvaluateStatement(pattern, sessionInfo.userId).then(ok(res)).catch(internalError(res));
}
catch (err) {
logger.error(err);
res.status(500).send(err.toString());
}
}
async function handleEntityGet(moduleName, entityName, req, res) {
try {
const path = pathFromRequest(moduleName, entityName, req);
const sessionInfo = await verifyAuth(moduleName, entityName, req.headers.authorization);
if (isNoSession(sessionInfo)) {
res.status(401).send('Authorization required');
return;
}
let pattern = '';
if (req.query.tree) {
pattern = fetchTreePattern(makeFqName(moduleName, entityName), path);
}
else {
pattern = queryPatternFromPath(path, req);
}
parseAndEvaluateStatement(pattern, sessionInfo.userId).then(ok(res)).catch(internalError(res));
}
catch (err) {
logger.error(err);
res.status(500).send(err.toString());
}
}
const joinTags = new Map()
.set('@joinOn', '@join')
.set('@leftJoinOn', '@left_join')
.set('@rightJoinOn', '@right_join');
function objectAsAttributesPattern(entityFqName, obj) {
const attrs = new Array();
let joinType;
let joinOnAttr;
Object.keys(obj).forEach(key => {
const s = obj[key];
if (joinTags.has(key)) {
joinType = joinTags.get(key);
joinOnAttr = s;
}
else {
let v = s;
if (!s.startsWith('"')) {
if (!isStringNumeric(s) && s != 'true' && s != 'false') {
v = `"${s}"`;
}
}
attrs.push(`${key}? ${v}`);
}
});
const hasQueryAttrs = attrs.length > 0;
const pat = `{
${attrs.join(',')}
}`;
if (joinType && joinOnAttr) {
const [targetEntity, targetAttr, reverseJoin] = fetchRefTarget(entityFqName, joinOnAttr);
const intoSpec = new Array();
const en1 = splitFqName(entityFqName)[1];
getAttributeNames(entityFqName).forEach((n) => {
intoSpec.push(`${en1}_${n} ${entityFqName}.${n}`);
});
const en2 = splitFqName(targetEntity)[1];
getAttributeNames(targetEntity).forEach((n) => {
intoSpec.push(`${en2}_${n} ${targetEntity}.${n}`);
});
const intoPat = `@into {${intoSpec.join(', ')}}`;
joinOnAttr = reverseJoin ? splitRefs(joinOnAttr)[1] : joinOnAttr;
return [
`${pat},\n${joinType} ${targetEntity} {${targetAttr}? ${entityFqName}.${joinOnAttr}}, \n${intoPat}`,
hasQueryAttrs,
];
}
else {
return [pat, hasQueryAttrs];
}
}
function queryPatternFromPath(path, req) {
const r = walkDownInstancePath(path);
let moduleName = r[0];
let entityName = r[1];
const id = r[2];
const parts = r[3];
const fqName = `${moduleName}/${entityName}`;
if (parts.length == 2 && id === undefined) {
if (req.query && Object.keys(req.query).length > 0) {
const [pat, hasQueryAttrs] = objectAsAttributesPattern(fqName, req.query);
const n = hasQueryAttrs ? fqName : `${fqName}?`;
return `{${n} ${pat}}`;
}
else {
return `{${fqName}? {}}`;
}
}
else {
moduleName = restoreFqName(moduleName);
const relName = restoreFqName(parts[parts.length - 2]);
if (relName && isBetweenRelationship(relName, moduleName)) {
const n = restoreFqName(parts[0]);
const ns = nameToPath(n);
const pe = ns.getEntryName();
const pm = ns.hasModule() ? ns.getModuleName() : moduleName;
const p = escapeSepInPath(parts.slice(0, parts.length - 2).join('/'));
return `{${pm}/${pe} {${PathAttributeNameQuery} "${p}"}, ${relName} {${moduleName}/${entityName}? {}}}`;
}
entityName = restoreFqName(entityName);
path = escapeSepInPath(path);
if (id === undefined) {
return `{${moduleName}/${entityName} {${PathAttributeNameQuery}like "${path}%"}}`;
}
else {
return `{${moduleName}/${entityName} {${PathAttributeNameQuery} "${path}"}}`;
}
}
}
async function handleEntityPut(moduleName, entityName, req, res) {
try {
const path = pathFromRequest(moduleName, entityName, req);
const sessionInfo = await verifyAuth(moduleName, entityName, req.headers.authorization);
if (isNoSession(sessionInfo)) {
res.status(401).send('Authorization required');
return;
}
const attrs = objectAsInstanceAttributes(req.body);
attrs.set(PathAttributeNameQuery, path);
const r = walkDownInstancePath(path);
moduleName = r[0];
entityName = r[1];
const pattern = patternFromAttributes(moduleName, entityName, attrs);
parseAndEvaluateStatement(pattern, sessionInfo.userId).then(ok(res)).catch(internalError(res));
}
catch (err) {
logger.error(err);
res.status(500).send(err.toString());
}
}
async function handleEntityDelete(moduleName, entityName, req, res) {
try {
const path = pathFromRequest(moduleName, entityName, req);
const sessionInfo = await verifyAuth(moduleName, entityName, req.headers.authorization);
if (isNoSession(sessionInfo)) {
res.status(401).send('Authorization required');
return;
}
const cmd = req.query.purge == 'true' ? 'purge' : 'delete';
const pattern = `${cmd} ${queryPatternFromPath(path, req)}`;
parseAndEvaluateStatement(pattern, sessionInfo.userId).then(ok(res)).catch(internalError(res));
}
catch (err) {
logger.error(err);
res.status(500).send(err.toString());
}
}
function fetchTreePattern(fqName, path) {
let pattern = path ? `{${fqName} {${PathAttributeNameQuery} "${path}"}` : `{${fqName}? {}`;
const rels = getAllChildRelationships(fqName);
if (rels.length > 0) {
const treePats = new Array();
rels.forEach((rel) => {
treePats.push(`${rel.getFqName()} ${fetchTreePattern(rel.getChildFqName())}`);
});
pattern = pattern.concat(',', treePats.join(','));
}
return `${pattern}}`;
}
function createChildPattern(moduleName, entityName, req) {
const path = pathFromRequest(moduleName, entityName, req);
try {
const parts = path.split('/');
const pinfo = parts.slice(-4);
const parentFqname = forceAsFqName(pinfo[0], moduleName);
const relName = forceAsFqName(pinfo[2], moduleName);
const parentPath = escapeSepInPath(parts.slice(0, parts.length - 2).join('/'));
const childFqName = forceAsFqName(pinfo[3], moduleName);
const cparts = nameToPath(childFqName);
const childModuleName = cparts.getModuleName();
const childName = cparts.getEntryName();
const cp = patternFromAttributes(childModuleName, childName, objectAsInstanceAttributes(req.body));
return `{${parentFqname} {${PathAttributeNameQuery} "${parentPath}"}, ${relName} ${cp}}`;
}
catch (err) {
throw new BadRequestError(err.message);
}
}
async function verifyAuth(moduleName, eventName, authValue) {
if (requireAuth(moduleName, eventName)) {
if (authValue) {
const token = authValue.substring(authValue.indexOf(' ')).trim();
return await verifySession(token);
}
else {
return NoSession;
}
}
return BypassSession;
}
function normalizedResult(r) {
if (r instanceof Array) {
return r.map((x) => {
return normalizedResult(x);
});
}
else if (Instance.IsInstance(r)) {
r.mergeRelatedInstances();
Array.from(r.attributes.keys()).forEach(k => {
const v = r.attributes.get(k);
if (v instanceof Array || Instance.IsInstance(v)) {
r.attributes.set(k, normalizedResult(v));
}
});
return r.asObject();
}
else {
if (r instanceof Map) {
return Object.fromEntries(r.entries());
}
return r;
}
}
async function handleMetaGet(req, res) {
try {
const sessionInfo = await verifyAuth('', '', req.headers.authorization);
if (isNoSession(sessionInfo)) {
res.status(401).send('Authorization required');
return;
}
const moduleFilter = req.query.module;
const entityFilter = req.query.entity;
const eventFilter = req.query.event;
const entities = [];
const events = [];
const entityNames = getAllEntityNames();
const eventNames = getAllEventNames();
// entities
// skip entities if eventFilter is provided
if (!eventFilter || eventFilter === '') {
entityNames.forEach((entityNames, moduleName) => {
if (moduleFilter && moduleName !== moduleFilter) {
return;
}
entityNames.forEach((entityName) => {
if (entityFilter && !entityName.toLowerCase().includes(entityFilter.toLowerCase())) {
return;
}
try {
const module = fetchModule(moduleName);
const entity = module.getEntry(entityName);
const attributes = [];
if (entity instanceof Record && entity.schema) {
entity.schema.forEach((attrSpec, attrName) => {
let properties = {};
if (attrSpec.properties) {
const propsObj = {};
attrSpec.properties.forEach((value, key) => {
if (value instanceof Set) {
propsObj[key] = Array.from(value);
}
else {
propsObj[key] = value;
}
});
properties = propsObj;
}
const attrInfo = {
name: attrName,
type: attrSpec.type,
properties: JSON.stringify(properties),
};
attributes.push(attrInfo);
});
}
const relationships = [];
const allModules = getModuleNames();
allModules.forEach((modName) => {
const mod = fetchModule(modName);
const rels = mod.getRelationshipEntries();
rels.forEach((rel) => {
const parentNode = rel.parentNode();
const childNode = rel.childNode();
if (parentNode.path.getModuleName() === moduleName &&
parentNode.path.getEntryName() === entityName) {
relationships.push({
name: rel.name,
type: rel.isContains() ? 'contains' : 'between',
direction: 'parent',
target: childNode.path.asFqName(),
cardinality: rel.isOneToOne()
? 'one-to-one'
: rel.isOneToMany()
? 'one-to-many'
: 'many-to-many',
});
}
else if (childNode.path.getModuleName() === moduleName &&
childNode.path.getEntryName() === entityName) {
relationships.push({
name: rel.name,
type: rel.isContains() ? 'contains' : 'between',
direction: 'child',
target: parentNode.path.asFqName(),
cardinality: rel.isOneToOne()
? 'one-to-one'
: rel.isOneToMany()
? 'one-to-many'
: 'many-to-many',
});
}
});
});
const entityInfo = {
name: entityName,
module: moduleName,
fqName: makeFqName(moduleName, entityName),
type: 'entity',
attributes: attributes,
relationships: relationships,
meta: entity instanceof Record && entity.meta ? Object.fromEntries(entity.meta) : {},
};
entities.push(entityInfo);
}
catch (err) {
logger.warn(`Could not get detailed info for entity ${moduleName}/${entityName}: ${err.message}`);
const entityInfo = {
name: entityName,
module: moduleName,
fqName: makeFqName(moduleName, entityName),
type: 'entity',
error: 'Could not load detailed information',
};
entities.push(entityInfo);
}
});
});
}
// events
if (!entityFilter || entityFilter === '') {
eventNames.forEach((eventNames, moduleName) => {
if (moduleFilter && moduleName !== moduleFilter) {
return;
}
eventNames.forEach((eventName) => {
if (eventFilter && !eventName.toLowerCase().includes(eventFilter.toLowerCase())) {
return;
}
try {
const module = fetchModule(moduleName);
const event = module.getEntry(eventName);
const attributes = [];
if (event instanceof Record && event.schema) {
event.schema.forEach((attrSpec, attrName) => {
let properties = {};
if (attrSpec.properties) {
const propsObj = {};
attrSpec.properties.forEach((value, key) => {
if (value instanceof Set) {
propsObj[key] = Array.from(value);
}
else {
propsObj[key] = value;
}
});
properties = propsObj;
}
const attrInfo = {
name: attrName,
type: attrSpec.type,
properties: JSON.stringify(properties),
};
attributes.push(attrInfo);
});
}
const eventInfo = {
name: eventName,
module: moduleName,
fqName: makeFqName(moduleName, eventName),
type: 'event',
attributes: attributes,
meta: event instanceof Record && event.meta ? Object.fromEntries(event.meta) : {},
};
events.push(eventInfo);
}
catch (err) {
logger.warn(`Could not get detailed info for event ${moduleName}/${eventName}: ${err.message}`);
const eventInfo = {
name: eventName,
module: moduleName,
fqName: makeFqName(moduleName, eventName),
type: 'event',
error: 'Could not load detailed information',
};
events.push(eventInfo);
}
});
});
}
const entitiesByModule = {};
const eventsByModule = {};
entities.forEach(entity => {
if (!entitiesByModule[entity.module]) {
entitiesByModule[entity.module] = [];
}
entitiesByModule[entity.module].push(entity);
});
events.forEach(event => {
if (!eventsByModule[event.module]) {
eventsByModule[event.module] = [];
}
eventsByModule[event.module].push(event);
});
const result = {
entities: entitiesByModule,
events: eventsByModule,
modules: Array.from(new Set([...entities.map(e => e.module), ...events.map(e => e.module)])),
};
res.contentType('application/json');
res.send(result);
}
catch (err) {
logger.error(err);
res.status(500).send(err.toString());
}
}
async function handleFileUpload(req, res, config) {
var _a;
try {
if (!isNodeEnv) {
res.status(501).send({ error: 'File upload is only supported in Node.js environment' });
return;
}
if (!((_a = config === null || config === void 0 ? void 0 : config.service) === null || _a === void 0 ? void 0 : _a.httpFileHandling)) {
res
.status(403)
.send({ error: 'File handling is not enabled. Set httpFileHandling: true in config.' });
return;
}
const sessionInfo = await verifyAuth('', '', req.headers.authorization);
if (isNoSession(sessionInfo)) {
res.status(401).send('Authorization required');
return;
}
if (!req.file) {
res.status(400).send({ error: 'No file uploaded' });
return;
}
const file = req.file;
try {
await createFileRecord({
filename: file.filename,
originalName: file.originalname,
mimetype: file.mimetype,
size: file.size,
path: file.path,
uploadedBy: sessionInfo.userId,
}, sessionInfo);
}
catch (dbErr) {
logger.error(`Failed to create file record in database: ${dbErr.message}`);
}
const fileInfo = {
success: true,
filename: file.filename,
originalName: file.originalname,
mimetype: file.mimetype,
size: file.size,
path: file.path,
uploadedAt: new Date().toISOString(),
uploadedBy: sessionInfo.userId,
};
logger.info(`File uploaded successfully: ${file.originalname} -> ${file.filename}`);
res.contentType('application/json');
res.send(fileInfo);
}
catch (err) {
logger.error(`File upload error: ${err}`);
res.status(500).send({ error: err.message || 'File upload failed' });
}
}
async function handleFileDownload(req, res, uploadDir, config) {
var _a;
try {
if (!isNodeEnv) {
res.status(501).send({ error: 'File download is only supported in Node.js environment' });
return;
}
if (!((_a = config === null || config === void 0 ? void 0 : config.service) === null || _a === void 0 ? void 0 : _a.httpFileHandling)) {
res
.status(403)
.send({ error: 'File handling is not enabled. Set httpFileHandling: true in config.' });
return;
}
const sessionInfo = await verifyAuth('', '', req.headers.authorization);
if (isNoSession(sessionInfo)) {
res.status(401).send('Authorization required');
return;
}
const filename = req.params.filename;
if (!filename) {
res.status(400).send({ error: 'Filename is required' });
return;
}
const file = await findFileByFilename(filename, sessionInfo);
if (!file) {
res.status(404).send({ error: 'File not found' });
return;
}
const sanitizedFilename = path.basename(filename);
const fs = await import('fs');
const filePath = path.join(uploadDir, sanitizedFilename);
if (!fs.existsSync(filePath)) {
res.status(404).send({ error: 'File not found' });
return;
}
const realPath = fs.realpathSync(filePath);
const realUploadDir = fs.realpathSync(uploadDir);
if (!realPath.startsWith(realUploadDir)) {
res.status(403).send({ error: 'Access denied' });
return;
}
const stats = fs.statSync(filePath);
const ext = path.extname(sanitizedFilename).toLowerCase();
const mimeTypes = {
'.pdf': 'application/pdf',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.png': 'image/png',
'.gif': 'image/gif',
'.txt': 'text/plain',
'.json': 'application/json',
'.xml': 'application/xml',
'.zip': 'application/zip',
'.csv': 'text/csv',
'.doc': 'application/msword',
'.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'.xls': 'application/vnd.ms-excel',
'.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
};
const mimeType = mimeTypes[ext] || 'application/octet-stream';
res.setHeader('Content-Type', mimeType);
res.setHeader('Content-Length', stats.size);
res.setHeader('Content-Disposition', `attachment; filename="${sanitizedFilename}"`);
res.setHeader('Cache-Control', 'no-cache');
logger.info(`File download: ${sanitizedFilename}`);
const fileStream = fs.createReadStream(filePath);
fileStream.pipe(res);
fileStream.on('error', (err) => {
logger.error(`File stream error: ${err}`);
if (!res.headersSent) {
res.status(500).send({ error: 'Error streaming file' });
}
});
}
catch (err) {
logger.error(`File download error: ${err}`);
if (!res.headersSent) {
res.status(500).send({ error: err.message || 'File download failed' });
}
}
}
async function handleFileDelete(req, res, uploadDir, config) {
var _a;
try {
if (!isNodeEnv) {
res.status(501).send({ error: 'File delete is only supported in Node.js environment' });
return;
}
if (!((_a = config === null || config === void 0 ? void 0 : config.service) === null || _a === void 0 ? void 0 : _a.httpFileHandling)) {
res
.status(403)
.send({ error: 'File handling is not enabled. Set httpFileHandling: true in config.' });
return;
}
const sessionInfo = await verifyAuth('', '', req.headers.authorization);
if (isNoSession(sessionInfo)) {
res.status(401).send('Authorization required');
return;
}
const filename = req.params.filename;
if (!filename) {
res.status(400).send({ error: 'Filename is required' });
return;
}
const file = await findFileByFilename(filename, sessionInfo);
if (!file) {
res.status(404).send({ error: 'File not found' });
return;
}
const sanitizedFilename = path.basename(filename);
const fs = await import('fs');
const filePath = path.join(uploadDir, sanitizedFilename);
if (!fs.existsSync(filePath)) {
res.status(404).send({ error: 'File not found' });
return;
}
const realPath = fs.realpathSync(filePath);
const realUploadDir = fs.realpathSync(uploadDir);
if (!realPath.startsWith(realUploadDir)) {
res.status(403).send({ error: 'Access denied' });
return;
}
await deleteFileRecord(filename, sessionInfo);
fs.unlinkSync(filePath);
logger.info(`File deleted: ${sanitizedFilename}`);
res.status(200).send({
message: 'File deleted successfully',
filename: sanitizedFilename,
});
}
catch (err) {
logger.error(`File delete error: ${err}`);
if (!res.headersSent) {
res.status(500).send({ error: err.message || 'File delete failed' });
}
}
}
//# sourceMappingURL=http.js.map