diff --git a/src/entities/note.js b/src/entities/note.js index 86bdb1679..cd20e222a 100644 --- a/src/entities/note.js +++ b/src/entities/note.js @@ -32,6 +32,17 @@ class Note extends Entity { return this.repository.getEntities("SELECT * FROM attributes WHERE noteId = ? AND isDeleted = 0", [this.noteId]); } + // WARNING: this doesn't take into account the possibility to have multi-valued attributes! + async getAttributeMap() { + const map = {}; + + for (const attr of await this.getAttributes()) { + map[attr.name] = attr.value; + } + + return map; + } + async getAttribute(name) { return this.repository.getEntity("SELECT * FROM attributes WHERE noteId = ? AND name = ?", [this.noteId, name]); } diff --git a/src/routes/api/script.js b/src/routes/api/script.js index cb3848b34..3d88d2c70 100644 --- a/src/routes/api/script.js +++ b/src/routes/api/script.js @@ -4,7 +4,6 @@ const express = require('express'); const router = express.Router(); const auth = require('../../services/auth'); const wrap = require('express-promise-wrap').wrap; -const notes = require('../../services/notes'); const attributes = require('../../services/attributes'); const script = require('../../services/script'); const Repository = require('../../services/repository'); @@ -24,13 +23,15 @@ router.post('/job', auth.checkApiAuth, wrap(async (req, res, next) => { })); router.get('/startup', auth.checkApiAuth, wrap(async (req, res, next) => { - const noteIds = await attributes.getNoteIdsWithAttribute("run_on_startup"); + const noteIds = await attributes.getNoteIdsWithAttribute("run", "frontend_startup"); const repository = new Repository(req); const scripts = []; for (const noteId of noteIds) { - scripts.push(await getNoteWithSubtreeScript(noteId, repository)); + const note = await repository.getNote(noteId); + + scripts.push(await script.getNoteScript(note)); } res.send(scripts); @@ -38,55 +39,9 @@ router.get('/startup', auth.checkApiAuth, wrap(async (req, res, next) => { router.get('/subtree/:noteId', auth.checkApiAuth, wrap(async (req, res, next) => { const repository = new Repository(req); - const noteId = req.params.noteId; + const note = await repository.getNote(req.params.noteId); - res.send(await getNoteWithSubtreeScript(noteId, repository)); + res.send(await script.getNoteScript(note, repository)); })); -async function getNoteWithSubtreeScript(noteId, repository) { - const note = await repository.getNote(noteId); - - let noteScript = note.content; - - if (note.isJavaScript()) { - // last \r\n is necessary if script contains line comment on its last line - noteScript = "(async function() {" + noteScript + "\r\n})()"; - } - - const subTreeScripts = await getSubTreeScripts(noteId, [noteId], repository, note.isJavaScript()); - - return subTreeScripts + noteScript; -} - -async function getSubTreeScripts(parentId, includedNoteIds, repository, isJavaScript) { - const children = await repository.getEntities(` - SELECT notes.* - FROM notes JOIN note_tree USING(noteId) - WHERE note_tree.isDeleted = 0 AND notes.isDeleted = 0 - AND note_tree.parentNoteId = ? AND (notes.type = 'code' OR notes.type = 'file') - AND (notes.mime = 'application/javascript' - OR notes.mime = 'application/x-javascript' - OR notes.mime = 'text/html')`, [parentId]); - - let script = "\r\n"; - - for (const child of children) { - if (includedNoteIds.includes(child.noteId)) { - return; - } - - includedNoteIds.push(child.noteId); - - script += await getSubTreeScripts(child.noteId, includedNoteIds, repository); - - if (!isJavaScript && child.isJavaScript()) { - child.content = ''; - } - - script += child.content + "\r\n"; - } - - return script; -} - module.exports = router; \ No newline at end of file diff --git a/src/services/attributes.js b/src/services/attributes.js index 8ba17cd81..1958bd26c 100644 --- a/src/services/attributes.js +++ b/src/services/attributes.js @@ -11,7 +11,8 @@ const BUILTIN_ATTRIBUTES = [ 'calendar_root', 'hide_in_autocomplete', 'exclude_from_export', - 'run' + 'run', + 'manual_transaction_handling' ]; async function getNoteAttributeMap(noteId) { diff --git a/src/services/scheduler.js b/src/services/scheduler.js index 8d296e833..048501307 100644 --- a/src/services/scheduler.js +++ b/src/services/scheduler.js @@ -19,7 +19,7 @@ async function runNotesWithAttribute(runAttrValue) { } } -setTimeout(() => runNotesWithAttribute('on_startup'), 10 * 1000); +setTimeout(() => runNotesWithAttribute('backend_startup'), 10 * 1000); setInterval(() => runNotesWithAttribute('hourly'), 3600 * 1000); diff --git a/src/services/script.js b/src/services/script.js index 0de7c47c0..6bbb44263 100644 --- a/src/services/script.js +++ b/src/services/script.js @@ -6,11 +6,10 @@ async function executeNote(note) { return; } - const ctx = new ScriptContext(); + const manualTransactionHandling = (await note.getAttributeMap()).manual_transaction_handling !== undefined; + const noteScript = await getNoteScript(note); - return await sql.doInTransaction(async () => { - return await (function() { return eval(`const api = this; (async function() {${note.content}\n\r})()`); }.call(ctx)); - }); + return await executeJob(noteScript, [], manualTransactionHandling); } async function executeScript(dataKey, script, params) { @@ -84,8 +83,38 @@ function getParams(params) { }).join(","); } +async function getNoteScript(note) { + const subTreeScripts = await getSubTreeScripts(note, [note.noteId]); + + // last \r\n is necessary if script contains line comment on its last line + return "async function() {" + subTreeScripts + note.content + "\r\n}"; +} + +/** + * @param includedNoteIds - if multiple child note scripts reference same dependency (child note), + * it will be included just once + */ +async function getSubTreeScripts(parent, includedNoteIds) { + let script = "\r\n"; + + for (const child of await parent.getChildren()) { + if (!child.isJavaScript() || includedNoteIds.includes(child.noteId)) { + continue; + } + + includedNoteIds.push(child.noteId); + + script += await getSubTreeScripts(child.noteId, includedNoteIds); + + script += child.content + "\r\n"; + } + + return script; +} + module.exports = { executeNote, executeScript, - setJob + setJob, + getNoteScript }; \ No newline at end of file