mirror of
				https://github.com/zadam/trilium.git
				synced 2025-11-04 05:28:59 +01:00 
			
		
		
		
	custom HTTP handler which triggers associated script notes WIP, #356
This commit is contained in:
		
							parent
							
								
									e211dd65ad
								
							
						
					
					
						commit
						54de4d236d
					
				@ -63,6 +63,8 @@ app.use(favicon(__dirname + '/public/images/app-icons/win/icon.ico'));
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
require('./routes/routes').register(app);
 | 
					require('./routes/routes').register(app);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require('./routes/custom').register(app);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// catch 404 and forward to error handler
 | 
					// catch 404 and forward to error handler
 | 
				
			||||||
app.use((req, res, next) => {
 | 
					app.use((req, res, next) => {
 | 
				
			||||||
    const err = new Error('Router not found for request ' + req.url);
 | 
					    const err = new Error('Router not found for request ' + req.url);
 | 
				
			||||||
 | 
				
			|||||||
@ -19,7 +19,7 @@ async function exec(req) {
 | 
				
			|||||||
async function run(req) {
 | 
					async function run(req) {
 | 
				
			||||||
    const note = await repository.getNote(req.params.noteId);
 | 
					    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 };
 | 
					    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
 | 
					 * @constructor
 | 
				
			||||||
 * @hideconstructor
 | 
					 * @hideconstructor
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
function BackendScriptApi(startNote, currentNote, originEntity) {
 | 
					function BackendScriptApi(currentNote, apiParams) {
 | 
				
			||||||
    /** @property {Note} note where script started executing */
 | 
					    /** @property {Note} note where script started executing */
 | 
				
			||||||
    this.startNote = startNote;
 | 
					    this.startNote = apiParams.startNote;
 | 
				
			||||||
    /** @property {Note} note where script is currently executing */
 | 
					    /** @property {Note} note where script is currently executing */
 | 
				
			||||||
    this.currentNote = currentNote;
 | 
					    this.currentNote = currentNote;
 | 
				
			||||||
    /** @property {Entity} entity whose event triggered this executions */
 | 
					    /** @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;
 | 
					    this.axios = axios;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -12,7 +12,7 @@ async function runAttachedRelations(note, relationName, originEntity) {
 | 
				
			|||||||
        const scriptNote = await relation.getTargetNote();
 | 
					        const scriptNote = await relation.getTargetNote();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (scriptNote) {
 | 
					        if (scriptNote) {
 | 
				
			||||||
            await scriptService.executeNote(scriptNote, originEntity);
 | 
					            await scriptService.executeNote(scriptNote, { originEntity });
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        else {
 | 
					        else {
 | 
				
			||||||
            log.error(`Target note ${relation.value} of atttribute ${relation.attributeId} has not been found.`);
 | 
					            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]);
 | 
					          AND notes.isDeleted = 0`, [runAttrValue]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (const note of notes) {
 | 
					    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 sourceIdService = require('./source_id');
 | 
				
			||||||
const log = require('./log');
 | 
					const log = require('./log');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function executeNote(note, originEntity) {
 | 
					async function executeNote(note, apiParams) {
 | 
				
			||||||
    if (!note.isJavaScript() || note.getScriptEnv() !== 'backend' || !note.isContentAvailable) {
 | 
					    if (!note.isJavaScript() || note.getScriptEnv() !== 'backend' || !note.isContentAvailable) {
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const bundle = await getScriptBundle(note);
 | 
					    const bundle = await getScriptBundle(note);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await executeBundle(bundle, note, originEntity);
 | 
					    await executeBundle(bundle, apiParams);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function executeBundle(bundle, startNote, originEntity = null) {
 | 
					async function executeBundle(bundle, apiParams = {}) {
 | 
				
			||||||
    if (!startNote) {
 | 
					    if (!apiParams.startNote) {
 | 
				
			||||||
        // this is the default case, the only exception is when we want to preserve frontend 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
 | 
					    // 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 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 {
 | 
					    try {
 | 
				
			||||||
        if (await bundle.note.hasLabel('manualTransactionHandling')) {
 | 
					        if (await bundle.note.hasLabel('manualTransactionHandling')) {
 | 
				
			||||||
            return await execute(ctx, script, '');
 | 
					            return await execute(ctx, script);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        else {
 | 
					        else {
 | 
				
			||||||
            return await sql.transactional(async () => await execute(ctx, script, ''));
 | 
					            return await sql.transactional(async () => await execute(ctx, script));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    catch (e) {
 | 
					    catch (e) {
 | 
				
			||||||
@ -57,11 +57,11 @@ async function executeScript(script, params, startNoteId, currentNoteId, originE
 | 
				
			|||||||
    return await executeBundle(bundle, startNote, originEntity);
 | 
					    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
 | 
					    // scripts run as "server" sourceId so clients recognize the changes as "foreign" and update themselves
 | 
				
			||||||
    cls.namespace.set('sourceId', sourceIdService.getCurrentSourceId());
 | 
					    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) {
 | 
					function getParams(params) {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,10 +1,10 @@
 | 
				
			|||||||
const utils = require('./utils');
 | 
					const utils = require('./utils');
 | 
				
			||||||
const BackendScriptApi = require('./backend_script_api');
 | 
					const BackendScriptApi = require('./backend_script_api');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function ScriptContext(startNote, allNotes, originEntity = null) {
 | 
					function ScriptContext(allNotes, apiParams = {}) {
 | 
				
			||||||
    this.modules = {};
 | 
					    this.modules = {};
 | 
				
			||||||
    this.notes = utils.toObject(allNotes, note => [note.noteId, note]);
 | 
					    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 => {
 | 
					    this.require = moduleNoteIds => {
 | 
				
			||||||
        return moduleName => {
 | 
					        return moduleName => {
 | 
				
			||||||
            const candidates = allNotes.filter(note => moduleNoteIds.includes(note.noteId));
 | 
					            const candidates = allNotes.filter(note => moduleNoteIds.includes(note.noteId));
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user