04de621e37
https://expressjs.com/en/5x/api.html#req.query "As req.query’s shape is based on user-controlled input, all properties and values in this object are untrusted and should be validated before trusting" In essence, req.query.xx can be an array OR an array of strings.
124 lines
4.3 KiB
JavaScript
124 lines
4.3 KiB
JavaScript
'use strict';
|
|
|
|
exports = module.exports = {
|
|
load,
|
|
get,
|
|
list,
|
|
|
|
stopTask,
|
|
|
|
getLogs,
|
|
getLogStream
|
|
};
|
|
|
|
const assert = require('assert'),
|
|
BoxError = require('../boxerror.js'),
|
|
HttpError = require('@cloudron/connect-lastmile').HttpError,
|
|
HttpSuccess = require('@cloudron/connect-lastmile').HttpSuccess,
|
|
safe = require('safetydance'),
|
|
tasks = require('../tasks.js');
|
|
|
|
async function load(req, res, next) {
|
|
assert.strictEqual(typeof req.params.taskId, 'string');
|
|
|
|
const [error, result] = await safe(tasks.get(req.params.taskId));
|
|
if (error) return next(BoxError.toHttpError(error));
|
|
if (!result) return next(new HttpError(404, 'Task not found'));
|
|
|
|
req.resources.task = result;
|
|
|
|
next();
|
|
}
|
|
|
|
async function get(req, res, next) {
|
|
assert.strictEqual(typeof req.params.taskId, 'string');
|
|
|
|
next(new HttpSuccess(200, tasks.removePrivateFields(req.resources.task)));
|
|
}
|
|
|
|
async function list(req, res, next) {
|
|
const page = typeof req.query.page === 'string' ? parseInt(req.query.page) : 1;
|
|
if (!page || page < 0) return next(new HttpError(400, 'page query param has to be a postive number'));
|
|
|
|
const perPage = typeof req.query.per_page === 'string'? parseInt(req.query.per_page) : 25;
|
|
if (!perPage || perPage < 0) return next(new HttpError(400, 'per_page query param has to be a postive number'));
|
|
|
|
if (req.query.type && typeof req.query.type !== 'string') return next(new HttpError(400, 'type must be a string'));
|
|
|
|
const [error, result] = await safe(tasks.listByTypePaged(req.query.type || null, page, perPage));
|
|
if (error) return next(BoxError.toHttpError(error));
|
|
|
|
next(new HttpSuccess(200, { tasks: result.map(tasks.removePrivateFields) }));
|
|
}
|
|
|
|
async function stopTask(req, res, next) {
|
|
assert.strictEqual(typeof req.resources.task, 'object');
|
|
|
|
const [error] = await safe(tasks.stopTask(req.resources.task.id));
|
|
if (error) return next(BoxError.toHttpError(error));
|
|
|
|
next(new HttpSuccess(204, {}));
|
|
}
|
|
|
|
async function getLogs(req, res, next) {
|
|
assert.strictEqual(typeof req.resources.task, 'object');
|
|
|
|
const lines = typeof req.query.lines === 'string' ? parseInt(req.query.lines, 10) : 10; // we ignore last-event-id
|
|
if (isNaN(lines)) return next(new HttpError(400, 'lines must be a number'));
|
|
|
|
const options = {
|
|
lines: lines,
|
|
follow: false,
|
|
format: typeof req.query.format === 'string' ? req.query.format : 'json'
|
|
};
|
|
|
|
const [error, logStream] = await safe(tasks.getLogs(req.resources.task, options));
|
|
if (error) return next(BoxError.toHttpError(error));
|
|
|
|
res.writeHead(200, {
|
|
'Content-Type': 'application/x-logs',
|
|
'Content-Disposition': `attachment; filename="task-${req.resources.task.id}.log"`,
|
|
'Cache-Control': 'no-cache',
|
|
'X-Accel-Buffering': 'no' // disable nginx buffering
|
|
});
|
|
res.on('close', () => logStream.destroy());
|
|
logStream.pipe(res);
|
|
}
|
|
|
|
// this route is for streaming logs
|
|
async function getLogStream(req, res, next) {
|
|
assert.strictEqual(typeof req.resources.task, 'object');
|
|
|
|
const lines = typeof req.query.lines === 'string' ? parseInt(req.query.lines, 10) : 10; // we ignore last-event-id
|
|
if (isNaN(lines)) return next(new HttpError(400, 'lines must be a valid number'));
|
|
|
|
function sse(id, data) { return 'id: ' + id + '\ndata: ' + data + '\n\n'; }
|
|
|
|
if (req.headers.accept !== 'text/event-stream') return next(new HttpError(400, 'This API call requires EventStream'));
|
|
|
|
const options = {
|
|
lines: lines,
|
|
follow: true,
|
|
format: 'json'
|
|
};
|
|
|
|
const [error, logStream] = await safe(tasks.getLogs(req.resources.task, options));
|
|
if (error) return next(BoxError.toHttpError(error));
|
|
|
|
res.writeHead(200, {
|
|
'Content-Type': 'text/event-stream',
|
|
'Cache-Control': 'no-cache',
|
|
'Connection': 'keep-alive',
|
|
'X-Accel-Buffering': 'no', // disable nginx buffering
|
|
'Access-Control-Allow-Origin': '*'
|
|
});
|
|
res.write('retry: 3000\n');
|
|
res.on('close', () => logStream.destroy());
|
|
logStream.on('data', function (data) {
|
|
const obj = JSON.parse(data);
|
|
res.write(sse(obj.realtimeTimestamp, JSON.stringify(obj))); // send timestamp as id
|
|
});
|
|
logStream.on('end', res.end.bind(res));
|
|
logStream.on('error', res.end.bind(res, null));
|
|
}
|