mirror of
https://github.com/zadam/trilium.git
synced 2025-02-24 23:13:43 +08:00
custom HTTP handler which triggers associated script notes WIP, #356
This commit is contained in:
parent
e211dd65ad
commit
54de4d236d
8 changed files with 67 additions and 18 deletions
|
@ -63,6 +63,8 @@ app.use(favicon(__dirname + '/public/images/app-icons/win/icon.ico'));
|
|||
|
||||
require('./routes/routes').register(app);
|
||||
|
||||
require('./routes/custom').register(app);
|
||||
|
||||
// catch 404 and forward to error handler
|
||||
app.use((req, res, next) => {
|
||||
const err = new Error('Router not found for request ' + req.url);
|
||||
|
|
|
@ -19,7 +19,7 @@ async function exec(req) {
|
|||
async function run(req) {
|
||||
const note = await repository.getNote(req.params.noteId);
|
||||
|
||||
const result = await scriptService.executeNote(note, note);
|
||||
const result = await scriptService.executeNote(note, { originEntity: note });
|
||||
|
||||
return { executionResult: result };
|
||||
}
|
||||
|
|
43
src/routes/custom.js
Normal file
43
src/routes/custom.js
Normal file
|
@ -0,0 +1,43 @@
|
|||
const repository = require('../services/repository');
|
||||
const log = require('../services/log');
|
||||
const scriptService = require('../services/script');
|
||||
|
||||
function register(router) {
|
||||
router.all('/custom/:path*', async (req, res, next) => {
|
||||
const attrs = await repository.getEntities("SELECT * FROM attributes WHERE isDeleted = 0 AND type = 'label' AND ('customRequestHandler', 'customResourceProvider')");
|
||||
|
||||
for (const attr of attrs) {
|
||||
const regex = new RegExp(attr.value);
|
||||
|
||||
try {
|
||||
const m = regex.match(router.path);
|
||||
|
||||
if (m) {
|
||||
if (attr.name === 'customRequestHandler') {
|
||||
const note = await attr.getNote();
|
||||
|
||||
await scriptService.executeNote(note, {
|
||||
pathParams: m.slice(1),
|
||||
req,
|
||||
res
|
||||
});
|
||||
}
|
||||
else if (attr.name === 'customResourceProvider') {
|
||||
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
log.error(`Testing path for label ${attr.attributeId}, regex=${attr.value} failed with error ` + e.stack);
|
||||
}
|
||||
}
|
||||
|
||||
res.send('Hello ' + req.params.path);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
register
|
||||
};
|
|
@ -19,13 +19,17 @@ const appInfo = require('./app_info');
|
|||
* @constructor
|
||||
* @hideconstructor
|
||||
*/
|
||||
function BackendScriptApi(startNote, currentNote, originEntity) {
|
||||
function BackendScriptApi(currentNote, apiParams) {
|
||||
/** @property {Note} note where script started executing */
|
||||
this.startNote = startNote;
|
||||
this.startNote = apiParams.startNote;
|
||||
/** @property {Note} note where script is currently executing */
|
||||
this.currentNote = currentNote;
|
||||
/** @property {Entity} entity whose event triggered this executions */
|
||||
this.originEntity = originEntity;
|
||||
this.originEntity = apiParams.originEntity;
|
||||
|
||||
for (const key in apiParams) {
|
||||
this[key] = apiParams[key];
|
||||
}
|
||||
|
||||
this.axios = axios;
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ async function runAttachedRelations(note, relationName, originEntity) {
|
|||
const scriptNote = await relation.getTargetNote();
|
||||
|
||||
if (scriptNote) {
|
||||
await scriptService.executeNote(scriptNote, originEntity);
|
||||
await scriptService.executeNote(scriptNote, { originEntity });
|
||||
}
|
||||
else {
|
||||
log.error(`Target note ${relation.value} of atttribute ${relation.attributeId} has not been found.`);
|
||||
|
|
|
@ -17,7 +17,7 @@ async function runNotesWithLabel(runAttrValue) {
|
|||
AND notes.isDeleted = 0`, [runAttrValue]);
|
||||
|
||||
for (const note of notes) {
|
||||
scriptService.executeNote(note, note);
|
||||
scriptService.executeNote(note, { originEntity: note });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,33 +5,33 @@ const cls = require('./cls');
|
|||
const sourceIdService = require('./source_id');
|
||||
const log = require('./log');
|
||||
|
||||
async function executeNote(note, originEntity) {
|
||||
async function executeNote(note, apiParams) {
|
||||
if (!note.isJavaScript() || note.getScriptEnv() !== 'backend' || !note.isContentAvailable) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bundle = await getScriptBundle(note);
|
||||
|
||||
await executeBundle(bundle, note, originEntity);
|
||||
await executeBundle(bundle, apiParams);
|
||||
}
|
||||
|
||||
async function executeBundle(bundle, startNote, originEntity = null) {
|
||||
if (!startNote) {
|
||||
async function executeBundle(bundle, apiParams = {}) {
|
||||
if (!apiParams.startNote) {
|
||||
// this is the default case, the only exception is when we want to preserve frontend startNote
|
||||
startNote = bundle.note;
|
||||
apiParams.startNote = bundle.note;
|
||||
}
|
||||
|
||||
// last \r\n is necessary if script contains line comment on its last line
|
||||
const script = "async function() {\r\n" + bundle.script + "\r\n}";
|
||||
|
||||
const ctx = new ScriptContext(startNote, bundle.allNotes, originEntity);
|
||||
const ctx = new ScriptContext(bundle.allNotes, apiParams);
|
||||
|
||||
try {
|
||||
if (await bundle.note.hasLabel('manualTransactionHandling')) {
|
||||
return await execute(ctx, script, '');
|
||||
return await execute(ctx, script);
|
||||
}
|
||||
else {
|
||||
return await sql.transactional(async () => await execute(ctx, script, ''));
|
||||
return await sql.transactional(async () => await execute(ctx, script));
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
|
@ -57,11 +57,11 @@ async function executeScript(script, params, startNoteId, currentNoteId, originE
|
|||
return await executeBundle(bundle, startNote, originEntity);
|
||||
}
|
||||
|
||||
async function execute(ctx, script, paramsStr) {
|
||||
async function execute(ctx, script, params = []) {
|
||||
// scripts run as "server" sourceId so clients recognize the changes as "foreign" and update themselves
|
||||
cls.namespace.set('sourceId', sourceIdService.getCurrentSourceId());
|
||||
|
||||
return await (function() { return eval(`const apiContext = this;\r\n(${script}\r\n)(${paramsStr})`); }.call(ctx));
|
||||
return await (function() { return eval(`const apiContext = this;\r\n(${script}\r\n)()`); }.call(ctx));
|
||||
}
|
||||
|
||||
function getParams(params) {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
const utils = require('./utils');
|
||||
const BackendScriptApi = require('./backend_script_api');
|
||||
|
||||
function ScriptContext(startNote, allNotes, originEntity = null) {
|
||||
function ScriptContext(allNotes, apiParams = {}) {
|
||||
this.modules = {};
|
||||
this.notes = utils.toObject(allNotes, note => [note.noteId, note]);
|
||||
this.apis = utils.toObject(allNotes, note => [note.noteId, new BackendScriptApi(startNote, note, originEntity)]);
|
||||
this.apis = utils.toObject(allNotes, note => [note.noteId, new BackendScriptApi(note, apiParams)]);
|
||||
this.require = moduleNoteIds => {
|
||||
return moduleName => {
|
||||
const candidates = allNotes.filter(note => moduleNoteIds.includes(note.noteId));
|
||||
|
|
Loading…
Reference in a new issue