diff --git a/src/entities/entity_constructor.js b/src/entities/entity_constructor.js index 00fa8d084..c51bc931c 100644 --- a/src/entities/entity_constructor.js +++ b/src/entities/entity_constructor.js @@ -3,7 +3,7 @@ const Note = require('../entities/note'); const NoteRevision = require('../services/becca/entities/note_revision.js'); const Branch = require('../entities/branch'); const Attribute = require('../entities/attribute'); -const RecentNote = require('../entities/recent_note'); +const RecentNote = require('../services/becca/entities/recent_note.js'); const ApiToken = require('../entities/api_token'); const cls = require('../services/cls'); diff --git a/src/entities/recent_note.js b/src/entities/recent_note.js deleted file mode 100644 index f4ef899e6..000000000 --- a/src/entities/recent_note.js +++ /dev/null @@ -1,28 +0,0 @@ -"use strict"; - -const Entity = require('./entity'); -const dateUtils = require('../services/date_utils'); - -/** - * RecentNote represents recently visited note. - * - * @property {string} noteId - * @property {string} notePath - * @property {string} utcDateCreated - * - * @extends Entity - */ -class RecentNote extends Entity { - static get entityName() { return "recent_notes"; } - static get primaryKeyName() { return "noteId"; } - - beforeSaving() { - if (!this.utcDateCreated) { - this.utcDateCreated = dateUtils.utcNowDateTime(); - } - - super.beforeSaving(); - } -} - -module.exports = RecentNote; diff --git a/src/routes/api/attributes.js b/src/routes/api/attributes.js index 242754fa9..0744af675 100644 --- a/src/routes/api/attributes.js +++ b/src/routes/api/attributes.js @@ -5,7 +5,7 @@ const log = require('../../services/log'); const attributeService = require('../../services/attributes'); const repository = require('../../services/repository'); const Attribute = require('../../entities/attribute'); -const becca = require("../../services/becca/becca.js"); +const becca = require("../../services/becca/becca"); function getEffectiveNoteAttributes(req) { const note = becca.getNote(req.params.noteId); diff --git a/src/routes/api/autocomplete.js b/src/routes/api/autocomplete.js index ae76b6945..0b2fca7d3 100644 --- a/src/routes/api/autocomplete.js +++ b/src/routes/api/autocomplete.js @@ -6,6 +6,7 @@ const repository = require('../../services/repository'); const log = require('../../services/log'); const utils = require('../../services/utils'); const cls = require('../../services/cls'); +const becca = require("../../services/becca/becca"); function getAutocomplete(req) { const query = req.query.query.trim(); @@ -41,7 +42,7 @@ function getRecentNotes(activeNoteId) { params.push('%' + hoistedNoteId + '%'); } - const recentNotes = repository.getEntities(` + const recentNotes = becca.getRecentNotesFromQuery(` SELECT recent_notes.* FROM diff --git a/src/routes/api/files.js b/src/routes/api/files.js index b85bcee7f..4cb671ae1 100644 --- a/src/routes/api/files.js +++ b/src/routes/api/files.js @@ -9,7 +9,7 @@ const fs = require('fs'); const { Readable } = require('stream'); const chokidar = require('chokidar'); const ws = require('../../services/ws'); -const becca = require("../../services/becca/becca.js"); +const becca = require("../../services/becca/becca"); function updateFile(req) { const {noteId} = req.params; diff --git a/src/routes/api/note_revisions.js b/src/routes/api/note_revisions.js index 00ed71709..a0cf7fbab 100644 --- a/src/routes/api/note_revisions.js +++ b/src/routes/api/note_revisions.js @@ -7,10 +7,10 @@ const noteRevisionService = require('../../services/note_revisions'); const utils = require('../../services/utils'); const sql = require('../../services/sql'); const path = require('path'); -const becca = require("../../services/becca/becca.js"); +const becca = require("../../services/becca/becca"); function getNoteRevisions(req) { - return repository.getEntities(` + return becca.getNoteRevisionsFromQuery(` SELECT note_revisions.*, LENGTH(note_revision_contents.content) AS contentLength FROM note_revisions @@ -107,7 +107,7 @@ function restoreNoteRevision(req) { } function getEditedNotesOnDate(req) { - const notes = repository.getEntities(` + const noteIds = sql.getColumn(` SELECT notes.* FROM notes WHERE noteId IN ( @@ -121,6 +121,8 @@ function getEditedNotesOnDate(req) { ORDER BY isDeleted LIMIT 50`, {date: req.params.date + '%'}); + const notes = becca.getNotes(noteIds); + for (const note of notes) { const notePath = note.isDeleted ? null : beccaService.getNotePath(note.noteId); diff --git a/src/routes/api/notes.js b/src/routes/api/notes.js index 8457cfd68..63a26838d 100644 --- a/src/routes/api/notes.js +++ b/src/routes/api/notes.js @@ -9,7 +9,7 @@ const log = require('../../services/log'); const TaskContext = require('../../services/task_context'); const fs = require('fs'); const noteRevisionService = require("../../services/note_revisions.js"); -const becca = require("../../services/becca/becca.js"); +const becca = require("../../services/becca/becca"); function getNote(req) { const noteId = req.params.noteId; @@ -159,7 +159,8 @@ function getRelationMap(req) { console.log("displayRelations", displayRelations); - const notes = repository.getEntities(`SELECT * FROM notes WHERE isDeleted = 0 AND noteId IN (${questionMarks})`, noteIds); + const foundNoteIds = sql.getColumn(`SELECT noteId FROM notes WHERE isDeleted = 0 AND noteId IN (${questionMarks})`, noteIds); + const notes = becca.getNotes(foundNoteIds); for (const note of notes) { resp.noteTitles[note.noteId] = note.title; diff --git a/src/routes/api/recent_notes.js b/src/routes/api/recent_notes.js index 2b6594f4d..3c8108a30 100644 --- a/src/routes/api/recent_notes.js +++ b/src/routes/api/recent_notes.js @@ -1,6 +1,6 @@ "use strict"; -const RecentNote = require('../../entities/recent_note'); +const RecentNote = require('../../services/becca/entities/recent_note.js'); const sql = require('../../services/sql'); const dateUtils = require('../../services/date_utils'); diff --git a/src/routes/api/search.js b/src/routes/api/search.js index 6d13b218d..315985fee 100644 --- a/src/routes/api/search.js +++ b/src/routes/api/search.js @@ -6,6 +6,7 @@ const log = require('../../services/log'); const scriptService = require('../../services/script'); const searchService = require('../../services/search/services/search'); const noteRevisionService = require("../../services/note_revisions.js"); +const {formatAttrForSearch} = require("../../services/attribute_formatter.js"); async function searchFromNoteInt(note) { let searchResultNoteIds; @@ -267,51 +268,6 @@ function getRelatedNotes(req) { }; } -function formatAttrForSearch(attr, searchWithValue) { - let searchStr = ''; - - if (attr.type === 'label') { - searchStr += '#'; - } - else if (attr.type === 'relation') { - searchStr += '~'; - } - else { - throw new Error(`Unrecognized attribute type ${JSON.stringify(attr)}`); - } - - searchStr += attr.name; - - if (searchWithValue && attr.value) { - if (attr.type === 'relation') { - searchStr += ".noteId"; - } - - searchStr += '='; - searchStr += formatValue(attr.value); - } - - return searchStr; -} - -function formatValue(val) { - if (!/[^\w_-]/.test(val)) { - return val; - } - else if (!val.includes('"')) { - return '"' + val + '"'; - } - else if (!val.includes("'")) { - return "'" + val + "'"; - } - else if (!val.includes("`")) { - return "`" + val + "`"; - } - else { - return '"' + val.replace(/"/g, '\\"') + '"'; - } -} - module.exports = { searchFromNote, searchAndExecute, diff --git a/src/routes/api/similar_notes.js b/src/routes/api/similar_notes.js index 35f81328d..1d20a2664 100644 --- a/src/routes/api/similar_notes.js +++ b/src/routes/api/similar_notes.js @@ -1,7 +1,7 @@ "use strict"; const similarityService = require('../../services/becca/similarity.js'); -const becca = require("../../services/becca/becca.js"); +const becca = require("../../services/becca/becca"); async function getSimilarNotes(req) { const noteId = req.params.noteId; diff --git a/src/routes/custom.js b/src/routes/custom.js index 5c811b425..929252492 100644 --- a/src/routes/custom.js +++ b/src/routes/custom.js @@ -3,13 +3,17 @@ const log = require('../services/log'); const fileUploadService = require('./api/files.js'); const scriptService = require('../services/script'); const cls = require('../services/cls'); +const sql = require("../services/sql"); +const becca = require("../services/becca/becca"); async function handleRequest(req, res) { // express puts content after first slash into 0 index element const path = req.params.path + req.params[0]; - const attrs = repository.getEntities("SELECT * FROM attributes WHERE isDeleted = 0 AND type = 'label' AND name IN ('customRequestHandler', 'customResourceProvider')"); + const attributeIds = sql.getColumn("SELECT attributeId FROM attributes WHERE isDeleted = 0 AND type = 'label' AND name IN ('customRequestHandler', 'customResourceProvider')"); + + const attrs = attributeIds.map(attrId => becca.getAttribute(attrId)); for (const attr of attrs) { if (!attr.value.trim()) { diff --git a/src/services/attribute_formatter.js b/src/services/attribute_formatter.js new file mode 100644 index 000000000..b7498bece --- /dev/null +++ b/src/services/attribute_formatter.js @@ -0,0 +1,50 @@ +"use strict" + +function formatAttrForSearch(attr, searchWithValue) { + let searchStr = ''; + + if (attr.type === 'label') { + searchStr += '#'; + } + else if (attr.type === 'relation') { + searchStr += '~'; + } + else { + throw new Error(`Unrecognized attribute type ${JSON.stringify(attr)}`); + } + + searchStr += attr.name; + + if (searchWithValue && attr.value) { + if (attr.type === 'relation') { + searchStr += ".noteId"; + } + + searchStr += '='; + searchStr += formatValue(attr.value); + } + + return searchStr; +} + +function formatValue(val) { + if (!/[^\w_-]/.test(val)) { + return val; + } + else if (!val.includes('"')) { + return '"' + val + '"'; + } + else if (!val.includes("'")) { + return "'" + val + "'"; + } + else if (!val.includes("`")) { + return "`" + val + "`"; + } + else { + return '"' + val.replace(/"/g, '\\"') + '"'; + } +} + +module.exports = { + formatAttrForSearch +} diff --git a/src/services/attributes.js b/src/services/attributes.js index bfd207c3c..bcf256e26 100644 --- a/src/services/attributes.js +++ b/src/services/attributes.js @@ -1,9 +1,10 @@ "use strict"; -const repository = require('./repository'); +const searchService = require('./search/services/search'); const sql = require('./sql'); const becca = require('./becca/becca.js'); const Attribute = require('../entities/attribute'); +const {formatAttrForSearch} = require("./attribute_formatter.js"); const ATTRIBUTE_TYPES = [ 'label', 'relation' ]; @@ -59,16 +60,7 @@ const BUILTIN_ATTRIBUTES = [ ]; function getNotesWithLabel(name, value) { - let valueCondition = ""; - let params = [name]; - - if (value !== undefined) { - valueCondition = " AND attributes.value = ?"; - params.push(value); - } - - return repository.getEntities(`SELECT notes.* FROM notes JOIN attributes USING(noteId) - WHERE notes.isDeleted = 0 AND attributes.isDeleted = 0 AND attributes.name = ? ${valueCondition} ORDER BY position`, params); + return searchService.findNotes(formatAttrForSearch({type: 'label', name, value}, true)); } function getNoteIdsWithLabels(names) { diff --git a/src/services/backend_script_api.js b/src/services/backend_script_api.js index a824940d2..660bf7e16 100644 --- a/src/services/backend_script_api.js +++ b/src/services/backend_script_api.js @@ -14,7 +14,7 @@ const cloningService = require('./cloning'); const appInfo = require('./app_info'); const searchService = require('./search/services/search'); const SearchContext = require("./search/search_context.js"); -const becca = require("./becca/becca.js"); +const becca = require("./becca/becca"); /** * This is the main backend API interface for scripts. It's published in the local "api" object. diff --git a/src/services/becca/becca.js b/src/services/becca/becca.js index 74f16b526..d4e46db5b 100644 --- a/src/services/becca/becca.js +++ b/src/services/becca/becca.js @@ -2,6 +2,7 @@ const sql = require("../sql.js"); const NoteRevision = require("./entities/note_revision.js"); +const RecentNote = require("./entities/recent_note.js"); class Becca { constructor() { @@ -94,6 +95,18 @@ class Becca { return this[camelCaseEntityName][entityId]; } + + getRecentNotesFromQuery(query, params = []) { + const rows = sql.getRows(query, params); + + return rows.map(row => new RecentNote(row)); + } + + getNoteRevisionsFromQuery(query, params = []) { + const rows = sql.getRows(query, params); + + return rows.map(row => new NoteRevision(row)); + } } const becca = new Becca(); diff --git a/src/services/becca/entities/recent_note.js b/src/services/becca/entities/recent_note.js new file mode 100644 index 000000000..9dc2f9aba --- /dev/null +++ b/src/services/becca/entities/recent_note.js @@ -0,0 +1,22 @@ +"use strict"; + +const dateUtils = require('../../date_utils.js'); +const AbstractEntity = require("./abstract_entity.js"); + +/** + * RecentNote represents recently visited note. + */ +class RecentNote extends AbstractEntity { + static get entityName() { return "recent_notes"; } + static get primaryKeyName() { return "noteId"; } + + constructor(row) { + super(); + + this.noteId = row.noteId; + this.notePath = row.notePath; + this.utcDateCreated = row.utcDateCreated || dateUtils.utcNowDateTime(); + } +} + +module.exports = RecentNote; diff --git a/src/services/cloning.js b/src/services/cloning.js index 8fd26dcf8..017f418c2 100644 --- a/src/services/cloning.js +++ b/src/services/cloning.js @@ -8,7 +8,7 @@ const repository = require('./repository'); const Branch = require('../entities/branch'); const TaskContext = require("./task_context.js"); const utils = require('./utils'); -const becca = require("./becca/becca.js"); +const becca = require("./becca/becca"); function cloneNoteToParent(noteId, parentBranchId, prefix) { const parentBranch = becca.getBranch(parentBranchId); diff --git a/src/services/consistency_checks.js b/src/services/consistency_checks.js index 6ba24f967..c55c14c57 100644 --- a/src/services/consistency_checks.js +++ b/src/services/consistency_checks.js @@ -13,7 +13,7 @@ const Branch = require('../entities/branch'); const dateUtils = require('./date_utils'); const attributeService = require('./attributes'); const noteRevisionService = require('./note_revisions'); -const becca = require("./becca/becca.js"); +const becca = require("./becca/becca"); class ConsistencyChecks { constructor(autoFix) { @@ -244,13 +244,15 @@ class ConsistencyChecks { HAVING COUNT(1) > 1`, ({noteId, parentNoteId}) => { if (this.autoFix) { - const branches = repository.getEntities( - `SELECT * + const branchIds = sql.getColumn( + `SELECT branchId FROM branches WHERE noteId = ? and parentNoteId = ? and isDeleted = 0`, [noteId, parentNoteId]); + const branches = branchIds.map(branchId => becca.getBranch(branchId)); + // it's not necessarily "original" branch, it's just the only one which will survive const origBranch = branches[0]; @@ -359,11 +361,13 @@ class ConsistencyChecks { AND branches.isDeleted = 0`, ({parentNoteId}) => { if (this.autoFix) { - const branches = repository.getEntities(`SELECT * + const branchIds = sql.getColumn(`SELECT branchId FROM branches WHERE isDeleted = 0 AND parentNoteId = ?`, [parentNoteId]); + const branches = branchIds.map(branchId => becca.getBranch(branchId)); + for (const branch of branches) { branch.parentNoteId = 'root'; branch.save(); diff --git a/src/services/export/opml.js b/src/services/export/opml.js index f5060d9ef..8b0d8f7dd 100644 --- a/src/services/export/opml.js +++ b/src/services/export/opml.js @@ -2,7 +2,7 @@ const repository = require("../repository"); const utils = require('../utils'); -const becca = require("../becca/becca.js"); +const becca = require("../becca/becca"); function exportToOpml(taskContext, branch, version, res) { if (!['1.0', '2.0'].includes(version)) { diff --git a/src/services/notes.js b/src/services/notes.js index 657165526..a6ee511a9 100644 --- a/src/services/notes.js +++ b/src/services/notes.js @@ -6,11 +6,8 @@ const entityChangesService = require('./entity_changes.js'); const eventService = require('./events'); const repository = require('./repository'); const cls = require('../services/cls'); -const Note = require('../entities/note'); const BeccaNote = require('../services/becca/entities/note.js'); -const Branch = require('../entities/branch'); const BeccaBranch = require('../services/becca/entities/branch.js'); -const Attribute = require('../entities/attribute'); const BeccaAttribute = require('../services/becca/entities/attribute.js'); const protectedSessionService = require('../services/protected_session'); const log = require('../services/log'); @@ -617,14 +614,14 @@ function undeleteBranch(branch, deleteId, taskContext) { note.isDeleted = false; note.save(); - const attrs = repository.getEntities(` - SELECT * FROM attributes + const attributeIds = sql.getColumn(` + SELECT attributeId FROM attributes WHERE isDeleted = 1 AND deleteId = ? AND (noteId = ? OR (type = 'relation' AND value = ?))`, [deleteId, note.noteId, note.noteId]); - for (const attr of attrs) { + for (const attr of attributeIds) { attr.isDeleted = false; attr.save(); } @@ -646,14 +643,16 @@ function undeleteBranch(branch, deleteId, taskContext) { * @return return deleted branches of an undeleted parent note */ function getUndeletedParentBranches(noteId, deleteId) { - return repository.getEntities(` - SELECT branches.* + const branchIds = sql.getColumn(` + SELECT branches.branchId FROM branches JOIN notes AS parentNote ON parentNote.noteId = branches.parentNoteId WHERE branches.noteId = ? AND branches.isDeleted = 1 AND branches.deleteId = ? AND parentNote.isDeleted = 0`, [noteId, deleteId]); + + return branchIds.map(branchId => becca.getBranch(branchId)); } function scanForLinks(note) { diff --git a/src/services/scheduler.js b/src/services/scheduler.js index 805b8cf06..49280e5a0 100644 --- a/src/services/scheduler.js +++ b/src/services/scheduler.js @@ -1,9 +1,10 @@ const scriptService = require('./script'); -const repository = require('./repository'); const cls = require('./cls'); const sqlInit = require('./sql_init'); const config = require('./config'); const log = require('./log'); +const sql = require("./sql"); +const becca = require("./becca/becca"); function getRunAtHours(note) { try { @@ -17,8 +18,9 @@ function getRunAtHours(note) { } function runNotesWithLabel(runAttrValue) { - const notes = repository.getEntities(` - SELECT notes.* + // TODO: should be refactored into becca search + const noteIds = sql.getColumn(` + SELECT notes.noteId FROM notes JOIN attributes ON attributes.noteId = notes.noteId AND attributes.isDeleted = 0 @@ -29,6 +31,8 @@ function runNotesWithLabel(runAttrValue) { notes.type = 'code' AND notes.isDeleted = 0`, [runAttrValue]); + const notes = becca.getNotes(noteIds); + const instanceName = config.General ? config.General.instanceName : null; const currentHours = new Date().getHours(); diff --git a/src/services/script.js b/src/services/script.js index ee27c536c..260179a53 100644 --- a/src/services/script.js +++ b/src/services/script.js @@ -2,7 +2,7 @@ const ScriptContext = require('./script_context'); const repository = require('./repository'); const cls = require('./cls'); const log = require('./log'); -const becca = require("./becca/becca.js"); +const becca = require("./becca/becca"); async function executeNote(note, apiParams) { if (!note.isJavaScript() || note.getScriptEnv() !== 'backend' || !note.isContentAvailable) { diff --git a/src/services/setup.js b/src/services/setup.js index becd97257..867510c6e 100644 --- a/src/services/setup.js +++ b/src/services/setup.js @@ -7,7 +7,7 @@ const syncOptions = require('./sync_options'); const request = require('./request'); const appInfo = require('./app_info'); const utils = require('./utils'); -const becca = require("./becca/becca.js"); +const becca = require("./becca/becca"); async function hasSyncServerSchemaAndSeed() { const response = await requestToSyncServer('GET', '/api/setup/status');