syncification

This commit is contained in:
zadam 2020-06-20 12:31:38 +02:00
parent 30062d687f
commit 88348c560c
97 changed files with 1673 additions and 1700 deletions

View File

@ -7,11 +7,8 @@ const bodyParser = require('body-parser');
const helmet = require('helmet');
const session = require('express-session');
const FileStore = require('session-file-store')(session);
const os = require('os');
const sessionSecret = require('./services/session_secret');
const cls = require('./services/cls');
const dataDir = require('./services/data_dir');
require('./entities/entity_constructor');
require('./services/handlers');
require('./services/hoisted_note_loader');
require('./services/note_cache/note_cache_loader');

View File

@ -1,7 +1,6 @@
"use strict";
const Entity = require('./entity');
const repository = require('../services/repository');
const dateUtils = require('../services/date_utils');
const sql = require('../services/sql');
@ -44,14 +43,14 @@ class Attribute extends Entity {
/**
* @returns {Promise<Note|null>}
*/
async getNote() {
return await repository.getNote(this.noteId);
getNote() {
return this.repository.getNote(this.noteId);
}
/**
* @returns {Promise<Note|null>}
*/
async getTargetNote() {
getTargetNote() {
if (this.type !== 'relation') {
throw new Error(`Attribute ${this.attributeId} is not relation`);
}
@ -60,7 +59,7 @@ class Attribute extends Entity {
return null;
}
return await repository.getNote(this.value);
return this.repository.getNote(this.value);
}
/**
@ -70,7 +69,7 @@ class Attribute extends Entity {
return this.type === 'label-definition' || this.type === 'relation-definition';
}
async beforeSaving() {
beforeSaving() {
if (!this.value) {
if (this.type === 'relation') {
throw new Error(`Cannot save relation ${this.name} since it does not target any note.`);
@ -81,7 +80,7 @@ class Attribute extends Entity {
}
if (this.position === undefined) {
this.position = 1 + await sql.getValue(`SELECT COALESCE(MAX(position), 0) FROM attributes WHERE noteId = ?`, [this.noteId]);
this.position = 1 + sql.getValue(`SELECT COALESCE(MAX(position), 0) FROM attributes WHERE noteId = ?`, [this.noteId]);
}
if (!this.isInheritable) {

View File

@ -2,7 +2,6 @@
const Entity = require('./entity');
const dateUtils = require('../services/date_utils');
const repository = require('../services/repository');
const sql = require('../services/sql');
/**
@ -29,18 +28,18 @@ class Branch extends Entity {
static get hashedProperties() { return ["branchId", "noteId", "parentNoteId", "isDeleted", "deleteId", "prefix"]; }
/** @returns {Promise<Note|null>} */
async getNote() {
return await repository.getNote(this.noteId);
getNote() {
return this.repository.getNote(this.noteId);
}
/** @returns {Promise<Note|null>} */
async getParentNote() {
return await repository.getNote(this.parentNoteId);
getParentNote() {
return this.repository.getNote(this.parentNoteId);
}
async beforeSaving() {
beforeSaving() {
if (this.notePosition === undefined) {
const maxNotePos = await sql.getValue('SELECT MAX(notePosition) FROM branches WHERE parentNoteId = ? AND isDeleted = 0', [this.parentNoteId]);
const maxNotePos = sql.getValue('SELECT MAX(notePosition) FROM branches WHERE parentNoteId = ? AND isDeleted = 0', [this.parentNoteId]);
this.notePosition = maxNotePos === null ? 0 : maxNotePos + 10;
}

View File

@ -1,6 +1,7 @@
"use strict";
const utils = require('../services/utils');
let repo = null;
class Entity {
/**
@ -51,8 +52,16 @@ class Entity {
return utils.hash(contentToHash).substr(0, 10);
}
async save() {
await require('../services/repository').updateEntity(this);
get repository() {
if (!repo) {
repo = require('../services/repository');
}
return repo;
}
save() {
this.repository.updateEntity(this);
return this;
}

View File

@ -1,3 +1,4 @@
const repository = require('../services/repository');
const Note = require('../entities/note');
const NoteRevision = require('../entities/note_revision');
const Branch = require('../entities/branch');
@ -5,7 +6,6 @@ const Attribute = require('../entities/attribute');
const RecentNote = require('../entities/recent_note');
const ApiToken = require('../entities/api_token');
const Option = require('../entities/option');
const repository = require('../services/repository');
const cls = require('../services/cls');
const ENTITY_NAME_TO_ENTITY = {
@ -71,5 +71,3 @@ module.exports = {
createEntityFromRow,
getEntityFromEntityName
};
repository.setEntityConstructor(module.exports);

View File

@ -3,7 +3,6 @@
const Entity = require('./entity');
const Attribute = require('./attribute');
const protectedSessionService = require('../services/protected_session');
const repository = require('../services/repository');
const sql = require('../services/sql');
const utils = require('../services/utils');
const dateUtils = require('../services/date_utils');
@ -72,9 +71,9 @@ class Note extends Entity {
*/
/** @returns {Promise<*>} */
async getContent(silentNotFoundError = false) {
getContent(silentNotFoundError = false) {
if (this.content === undefined) {
const res = await sql.getRow(`SELECT content, hash FROM note_contents WHERE noteId = ?`, [this.noteId]);
const res = sql.getRow(`SELECT content, hash FROM note_contents WHERE noteId = ?`, [this.noteId]);
if (!res) {
if (silentNotFoundError) {
@ -108,8 +107,8 @@ class Note extends Entity {
}
/** @returns {Promise<*>} */
async getJsonContent() {
const content = await this.getContent();
getJsonContent() {
const content = this.getContent();
if (!content || !content.trim()) {
return null;
@ -119,7 +118,7 @@ class Note extends Entity {
}
/** @returns {Promise} */
async setContent(content) {
setContent(content) {
if (content === null || content === undefined) {
throw new Error(`Cannot set null content to note ${this.noteId}`);
}
@ -129,7 +128,7 @@ class Note extends Entity {
// force updating note itself so that dateModified is represented correctly even for the content
this.forcedChange = true;
this.contentLength = content.byteLength;
await this.save();
this.save();
this.content = content;
@ -149,14 +148,14 @@ class Note extends Entity {
}
}
await sql.upsert("note_contents", "noteId", pojo);
sql.upsert("note_contents", "noteId", pojo);
await syncTableService.addNoteContentSync(this.noteId);
syncTableService.addNoteContentSync(this.noteId);
}
/** @returns {Promise} */
async setJsonContent(content) {
await this.setContent(JSON.stringify(content, null, '\t'));
setJsonContent(content) {
this.setContent(JSON.stringify(content, null, '\t'));
}
/** @returns {boolean} true if this note is the root of the note tree. Root note has "root" noteId */
@ -204,8 +203,8 @@ class Note extends Entity {
return null;
}
async loadOwnedAttributesToCache() {
this.__ownedAttributeCache = await repository.getEntities(`SELECT * FROM attributes WHERE isDeleted = 0 AND noteId = ?`, [this.noteId]);
loadOwnedAttributesToCache() {
this.__ownedAttributeCache = this.repository.getEntities(`SELECT * FROM attributes WHERE isDeleted = 0 AND noteId = ?`, [this.noteId]);
return this.__ownedAttributeCache;
}
@ -217,9 +216,9 @@ class Note extends Entity {
* @param {string} [name] - (optional) attribute name to filter
* @returns {Promise<Attribute[]>} note's "owned" attributes - excluding inherited ones
*/
async getOwnedAttributes(type, name) {
getOwnedAttributes(type, name) {
if (!this.__ownedAttributeCache) {
await this.loadOwnedAttributesToCache();
this.loadOwnedAttributesToCache();
}
if (type && name) {
@ -241,8 +240,8 @@ class Note extends Entity {
*
* This method can be significantly faster than the getAttribute()
*/
async getOwnedAttribute(type, name) {
const attrs = await this.getOwnedAttributes(type, name);
getOwnedAttribute(type, name) {
const attrs = this.getOwnedAttributes(type, name);
return attrs.length > 0 ? attrs[0] : null;
}
@ -250,8 +249,8 @@ class Note extends Entity {
/**
* @returns {Promise<Attribute[]>} relations targetting this specific note
*/
async getTargetRelations() {
return await repository.getEntities("SELECT * FROM attributes WHERE type = 'relation' AND isDeleted = 0 AND value = ?", [this.noteId]);
getTargetRelations() {
return this.repository.getEntities("SELECT * FROM attributes WHERE type = 'relation' AND isDeleted = 0 AND value = ?", [this.noteId]);
}
/**
@ -259,9 +258,9 @@ class Note extends Entity {
* @param {string} [name] - (optional) attribute name to filter
* @returns {Promise<Attribute[]>} all note's attributes, including inherited ones
*/
async getAttributes(type, name) {
getAttributes(type, name) {
if (!this.__attributeCache) {
await this.loadAttributesToCache();
this.loadAttributesToCache();
}
if (type && name) {
@ -282,52 +281,52 @@ class Note extends Entity {
* @param {string} [name] - label name to filter
* @returns {Promise<Attribute[]>} all note's labels (attributes with type label), including inherited ones
*/
async getLabels(name) {
return await this.getAttributes(LABEL, name);
getLabels(name) {
return this.getAttributes(LABEL, name);
}
/**
* @param {string} [name] - label name to filter
* @returns {Promise<Attribute[]>} all note's labels (attributes with type label), excluding inherited ones
*/
async getOwnedLabels(name) {
return await this.getOwnedAttributes(LABEL, name);
getOwnedLabels(name) {
return this.getOwnedAttributes(LABEL, name);
}
/**
* @param {string} [name] - label name to filter
* @returns {Promise<Attribute[]>} all note's label definitions, including inherited ones
*/
async getLabelDefinitions(name) {
return await this.getAttributes(LABEL_DEFINITION, name);
getLabelDefinitions(name) {
return this.getAttributes(LABEL_DEFINITION, name);
}
/**
* @param {string} [name] - relation name to filter
* @returns {Promise<Attribute[]>} all note's relations (attributes with type relation), including inherited ones
*/
async getRelations(name) {
return await this.getAttributes(RELATION, name);
getRelations(name) {
return this.getAttributes(RELATION, name);
}
/**
* @param {string} [name] - relation name to filter
* @returns {Promise<Attribute[]>} all note's relations (attributes with type relation), excluding inherited ones
*/
async getOwnedRelations(name) {
return await this.getOwnedAttributes(RELATION, name);
getOwnedRelations(name) {
return this.getOwnedAttributes(RELATION, name);
}
/**
* @param {string} [name] - relation name to filter
* @returns {Promise<Note[]>}
*/
async getRelationTargets(name) {
const relations = await this.getRelations(name);
getRelationTargets(name) {
const relations = this.getRelations(name);
const targets = [];
for (const relation of relations) {
targets.push(await relation.getTargetNote());
targets.push(relation.getTargetNote());
}
return targets;
@ -337,8 +336,8 @@ class Note extends Entity {
* @param {string} [name] - relation name to filter
* @returns {Promise<Attribute[]>} all note's relation definitions including inherited ones
*/
async getRelationDefinitions(name) {
return await this.getAttributes(RELATION_DEFINITION, name);
getRelationDefinitions(name) {
return this.getAttributes(RELATION_DEFINITION, name);
}
/**
@ -351,8 +350,8 @@ class Note extends Entity {
}
/** @returns {Promise<void>} */
async loadAttributesToCache() {
const attributes = await repository.getEntities(`
loadAttributesToCache() {
const attributes = this.repository.getEntities(`
WITH RECURSIVE
tree(noteId, level) AS (
SELECT ?, 0
@ -419,8 +418,8 @@ class Note extends Entity {
* @param {string} name - attribute name
* @returns {Promise<boolean>} true if note has an attribute with given type and name (including inherited)
*/
async hasAttribute(type, name) {
return !!await this.getAttribute(type, name);
hasAttribute(type, name) {
return !!this.getAttribute(type, name);
}
/**
@ -428,8 +427,8 @@ class Note extends Entity {
* @param {string} name - attribute name
* @returns {Promise<boolean>} true if note has an attribute with given type and name (excluding inherited)
*/
async hasOwnedAttribute(type, name) {
return !!await this.getOwnedAttribute(type, name);
hasOwnedAttribute(type, name) {
return !!this.getOwnedAttribute(type, name);
}
/**
@ -437,8 +436,8 @@ class Note extends Entity {
* @param {string} name - attribute name
* @returns {Promise<Attribute>} attribute of given type and name. If there's more such attributes, first is returned. Returns null if there's no such attribute belonging to this note.
*/
async getAttribute(type, name) {
const attributes = await this.getAttributes();
getAttribute(type, name) {
const attributes = this.getAttributes();
return attributes.find(attr => attr.type === type && attr.name === name);
}
@ -448,8 +447,8 @@ class Note extends Entity {
* @param {string} name - attribute name
* @returns {Promise<string|null>} attribute value of given type and name or null if no such attribute exists.
*/
async getAttributeValue(type, name) {
const attr = await this.getAttribute(type, name);
getAttributeValue(type, name) {
const attr = this.getAttribute(type, name);
return attr ? attr.value : null;
}
@ -459,8 +458,8 @@ class Note extends Entity {
* @param {string} name - attribute name
* @returns {Promise<string|null>} attribute value of given type and name or null if no such attribute exists.
*/
async getOwnedAttributeValue(type, name) {
const attr = await this.getOwnedAttribute(type, name);
getOwnedAttributeValue(type, name) {
const attr = this.getOwnedAttribute(type, name);
return attr ? attr.value : null;
}
@ -474,12 +473,12 @@ class Note extends Entity {
* @param {string} [value] - attribute value (optional)
* @returns {Promise<void>}
*/
async toggleAttribute(type, enabled, name, value) {
toggleAttribute(type, enabled, name, value) {
if (enabled) {
await this.setAttribute(type, name, value);
this.setAttribute(type, name, value);
}
else {
await this.removeAttribute(type, name, value);
this.removeAttribute(type, name, value);
}
}
@ -491,14 +490,14 @@ class Note extends Entity {
* @param {string} [value] - attribute value (optional)
* @returns {Promise<void>}
*/
async setAttribute(type, name, value) {
const attributes = await this.loadOwnedAttributesToCache();
setAttribute(type, name, value) {
const attributes = this.loadOwnedAttributesToCache();
let attr = attributes.find(attr => attr.type === type && attr.name === name);
if (attr) {
if (attr.value !== value) {
attr.value = value;
await attr.save();
attr.save();
this.invalidateAttributeCache();
}
@ -511,7 +510,7 @@ class Note extends Entity {
value: value !== undefined ? value : ""
});
await attr.save();
attr.save();
this.invalidateAttributeCache();
}
@ -525,13 +524,13 @@ class Note extends Entity {
* @param {string} [value] - attribute value (optional)
* @returns {Promise<void>}
*/
async removeAttribute(type, name, value) {
const attributes = await this.loadOwnedAttributesToCache();
removeAttribute(type, name, value) {
const attributes = this.loadOwnedAttributesToCache();
for (const attribute of attributes) {
if (attribute.type === type && attribute.name === name && (value === undefined || value === attribute.value)) {
attribute.isDeleted = true;
await attribute.save();
attribute.save();
this.invalidateAttributeCache();
}
@ -541,7 +540,7 @@ class Note extends Entity {
/**
* @return {Promise<Attribute>}
*/
async addAttribute(type, name, value = "", isInheritable = false, position = 1000) {
addAttribute(type, name, value = "", isInheritable = false, position = 1000) {
const attr = new Attribute({
noteId: this.noteId,
type: type,
@ -551,111 +550,111 @@ class Note extends Entity {
position: position
});
await attr.save();
attr.save();
this.invalidateAttributeCache();
return attr;
}
async addLabel(name, value = "", isInheritable = false) {
return await this.addAttribute(LABEL, name, value, isInheritable);
addLabel(name, value = "", isInheritable = false) {
return this.addAttribute(LABEL, name, value, isInheritable);
}
async addRelation(name, targetNoteId, isInheritable = false) {
return await this.addAttribute(RELATION, name, targetNoteId, isInheritable);
addRelation(name, targetNoteId, isInheritable = false) {
return this.addAttribute(RELATION, name, targetNoteId, isInheritable);
}
/**
* @param {string} name - label name
* @returns {Promise<boolean>} true if label exists (including inherited)
*/
async hasLabel(name) { return await this.hasAttribute(LABEL, name); }
hasLabel(name) { return this.hasAttribute(LABEL, name); }
/**
* @param {string} name - label name
* @returns {Promise<boolean>} true if label exists (excluding inherited)
*/
async hasOwnedLabel(name) { return await this.hasOwnedAttribute(LABEL, name); }
hasOwnedLabel(name) { return this.hasOwnedAttribute(LABEL, name); }
/**
* @param {string} name - relation name
* @returns {Promise<boolean>} true if relation exists (including inherited)
*/
async hasRelation(name) { return await this.hasAttribute(RELATION, name); }
hasRelation(name) { return this.hasAttribute(RELATION, name); }
/**
* @param {string} name - relation name
* @returns {Promise<boolean>} true if relation exists (excluding inherited)
*/
async hasOwnedRelation(name) { return await this.hasOwnedAttribute(RELATION, name); }
hasOwnedRelation(name) { return this.hasOwnedAttribute(RELATION, name); }
/**
* @param {string} name - label name
* @returns {Promise<Attribute|null>} label if it exists, null otherwise
*/
async getLabel(name) { return await this.getAttribute(LABEL, name); }
getLabel(name) { return this.getAttribute(LABEL, name); }
/**
* @param {string} name - label name
* @returns {Promise<Attribute|null>} label if it exists, null otherwise
*/
async getOwnedLabel(name) { return await this.getOwnedAttribute(LABEL, name); }
getOwnedLabel(name) { return this.getOwnedAttribute(LABEL, name); }
/**
* @param {string} name - relation name
* @returns {Promise<Attribute|null>} relation if it exists, null otherwise
*/
async getRelation(name) { return await this.getAttribute(RELATION, name); }
getRelation(name) { return this.getAttribute(RELATION, name); }
/**
* @param {string} name - relation name
* @returns {Promise<Attribute|null>} relation if it exists, null otherwise
*/
async getOwnedRelation(name) { return await this.getOwnedAttribute(RELATION, name); }
getOwnedRelation(name) { return this.getOwnedAttribute(RELATION, name); }
/**
* @param {string} name - label name
* @returns {Promise<string|null>} label value if label exists, null otherwise
*/
async getLabelValue(name) { return await this.getAttributeValue(LABEL, name); }
getLabelValue(name) { return this.getAttributeValue(LABEL, name); }
/**
* @param {string} name - label name
* @returns {Promise<string|null>} label value if label exists, null otherwise
*/
async getOwnedLabelValue(name) { return await this.getOwnedAttributeValue(LABEL, name); }
getOwnedLabelValue(name) { return this.getOwnedAttributeValue(LABEL, name); }
/**
* @param {string} name - relation name
* @returns {Promise<string|null>} relation value if relation exists, null otherwise
*/
async getRelationValue(name) { return await this.getAttributeValue(RELATION, name); }
getRelationValue(name) { return this.getAttributeValue(RELATION, name); }
/**
* @param {string} name - relation name
* @returns {Promise<string|null>} relation value if relation exists, null otherwise
*/
async getOwnedRelationValue(name) { return await this.getOwnedAttributeValue(RELATION, name); }
getOwnedRelationValue(name) { return this.getOwnedAttributeValue(RELATION, name); }
/**
* @param {string} name
* @returns {Promise<Note>|null} target note of the relation or null (if target is empty or note was not found)
*/
async getRelationTarget(name) {
const relation = await this.getRelation(name);
getRelationTarget(name) {
const relation = this.getRelation(name);
return relation ? await repository.getNote(relation.value) : null;
return relation ? this.repository.getNote(relation.value) : null;
}
/**
* @param {string} name
* @returns {Promise<Note>|null} target note of the relation or null (if target is empty or note was not found)
*/
async getOwnedRelationTarget(name) {
const relation = await this.getOwnedRelation(name);
getOwnedRelationTarget(name) {
const relation = this.getOwnedRelation(name);
return relation ? await repository.getNote(relation.value) : null;
return relation ? this.repository.getNote(relation.value) : null;
}
/**
@ -666,7 +665,7 @@ class Note extends Entity {
* @param {string} [value] - label value (optional)
* @returns {Promise<void>}
*/
async toggleLabel(enabled, name, value) { return await this.toggleAttribute(LABEL, enabled, name, value); }
toggleLabel(enabled, name, value) { return this.toggleAttribute(LABEL, enabled, name, value); }
/**
* Based on enabled, relation is either set or removed.
@ -676,7 +675,7 @@ class Note extends Entity {
* @param {string} [value] - relation value (noteId)
* @returns {Promise<void>}
*/
async toggleRelation(enabled, name, value) { return await this.toggleAttribute(RELATION, enabled, name, value); }
toggleRelation(enabled, name, value) { return this.toggleAttribute(RELATION, enabled, name, value); }
/**
* Update's given label's value or creates it if it doesn't exist
@ -685,7 +684,7 @@ class Note extends Entity {
* @param {string} [value] - label value
* @returns {Promise<void>}
*/
async setLabel(name, value) { return await this.setAttribute(LABEL, name, value); }
setLabel(name, value) { return this.setAttribute(LABEL, name, value); }
/**
* Update's given relation's value or creates it if it doesn't exist
@ -694,7 +693,7 @@ class Note extends Entity {
* @param {string} [value] - relation value (noteId)
* @returns {Promise<void>}
*/
async setRelation(name, value) { return await this.setAttribute(RELATION, name, value); }
setRelation(name, value) { return this.setAttribute(RELATION, name, value); }
/**
* Remove label name-value pair, if it exists.
@ -703,7 +702,7 @@ class Note extends Entity {
* @param {string} [value] - label value
* @returns {Promise<void>}
*/
async removeLabel(name, value) { return await this.removeAttribute(LABEL, name, value); }
removeLabel(name, value) { return this.removeAttribute(LABEL, name, value); }
/**
* Remove relation name-value pair, if it exists.
@ -712,13 +711,13 @@ class Note extends Entity {
* @param {string} [value] - relation value (noteId)
* @returns {Promise<void>}
*/
async removeRelation(name, value) { return await this.removeAttribute(RELATION, name, value); }
removeRelation(name, value) { return this.removeAttribute(RELATION, name, value); }
/**
* @return {Promise<string[]>} return list of all descendant noteIds of this note. Returning just noteIds because number of notes can be huge. Includes also this note's noteId
*/
async getDescendantNoteIds() {
return await sql.getColumn(`
getDescendantNoteIds() {
return sql.getColumn(`
WITH RECURSIVE
tree(noteId) AS (
SELECT ?
@ -740,7 +739,7 @@ class Note extends Entity {
* @param {string} [value] - attribute value
* @returns {Promise<Note[]>}
*/
async getDescendantNotesWithAttribute(type, name, value) {
getDescendantNotesWithAttribute(type, name, value) {
const params = [this.noteId, name];
let valueCondition = "";
@ -749,7 +748,7 @@ class Note extends Entity {
valueCondition = " AND attributes.value = ?";
}
const notes = await repository.getEntities(`
const notes = this.repository.getEntities(`
WITH RECURSIVE
tree(noteId) AS (
SELECT ?
@ -778,7 +777,7 @@ class Note extends Entity {
* @param {string} [value] - label value
* @returns {Promise<Note[]>}
*/
async getDescendantNotesWithLabel(name, value) { return await this.getDescendantNotesWithAttribute(LABEL, name, value); }
getDescendantNotesWithLabel(name, value) { return this.getDescendantNotesWithAttribute(LABEL, name, value); }
/**
* Finds descendant notes with given relation name and value. Only own relations are considered, not inherited ones
@ -787,15 +786,15 @@ class Note extends Entity {
* @param {string} [value] - relation value
* @returns {Promise<Note[]>}
*/
async getDescendantNotesWithRelation(name, value) { return await this.getDescendantNotesWithAttribute(RELATION, name, value); }
getDescendantNotesWithRelation(name, value) { return this.getDescendantNotesWithAttribute(RELATION, name, value); }
/**
* Returns note revisions of this note.
*
* @returns {Promise<NoteRevision[]>}
*/
async getRevisions() {
return await repository.getEntities("SELECT * FROM note_revisions WHERE noteId = ?", [this.noteId]);
getRevisions() {
return this.repository.getEntities("SELECT * FROM note_revisions WHERE noteId = ?", [this.noteId]);
}
/**
@ -804,8 +803,8 @@ class Note extends Entity {
* @deprecated - not intended for general use
* @returns {Promise<Attribute[]>}
*/
async getLinks() {
return await repository.getEntities(`
getLinks() {
return this.repository.getEntities(`
SELECT *
FROM attributes
WHERE noteId = ? AND
@ -817,22 +816,22 @@ class Note extends Entity {
/**
* @returns {Promise<Branch[]>}
*/
async getBranches() {
return await repository.getEntities("SELECT * FROM branches WHERE isDeleted = 0 AND noteId = ?", [this.noteId]);
getBranches() {
return this.repository.getEntities("SELECT * FROM branches WHERE isDeleted = 0 AND noteId = ?", [this.noteId]);
}
/**
* @returns {boolean} - true if note has children
*/
async hasChildren() {
return (await this.getChildNotes()).length > 0;
hasChildren() {
return (this.getChildNotes()).length > 0;
}
/**
* @returns {Promise<Note[]>} child notes of this note
*/
async getChildNotes() {
return await repository.getEntities(`
getChildNotes() {
return this.repository.getEntities(`
SELECT notes.*
FROM branches
JOIN notes USING(noteId)
@ -845,8 +844,8 @@ class Note extends Entity {
/**
* @returns {Promise<Branch[]>} child branches of this note
*/
async getChildBranches() {
return await repository.getEntities(`
getChildBranches() {
return this.repository.getEntities(`
SELECT branches.*
FROM branches
WHERE branches.isDeleted = 0
@ -857,8 +856,8 @@ class Note extends Entity {
/**
* @returns {Promise<Note[]>} parent notes of this note (note can have multiple parents because of cloning)
*/
async getParentNotes() {
return await repository.getEntities(`
getParentNotes() {
return this.repository.getEntities(`
SELECT parent_notes.*
FROM
branches AS child_tree
@ -871,15 +870,15 @@ class Note extends Entity {
/**
* @return {Promise<string[][]>} - array of notePaths (each represented by array of noteIds constituting the particular note path)
*/
async getAllNotePaths() {
getAllNotePaths() {
if (this.noteId === 'root') {
return [['root']];
}
const notePaths = [];
for (const parentNote of await this.getParentNotes()) {
for (const parentPath of await parentNote.getAllNotePaths()) {
for (const parentNote of this.getParentNotes()) {
for (const parentPath of parentNote.getAllNotePaths()) {
parentPath.push(this.noteId);
notePaths.push(parentPath);
}
@ -892,8 +891,8 @@ class Note extends Entity {
* @param ancestorNoteId
* @return {Promise<boolean>} - true if ancestorNoteId occurs in at least one of the note's paths
*/
async isDescendantOfNote(ancestorNoteId) {
const notePaths = await this.getAllNotePaths();
isDescendantOfNote(ancestorNoteId) {
const notePaths = this.getAllNotePaths();
return notePaths.some(path => path.includes(ancestorNoteId));
}

View File

@ -2,7 +2,6 @@
const Entity = require('./entity');
const protectedSessionService = require('../services/protected_session');
const repository = require('../services/repository');
const utils = require('../services/utils');
const sql = require('../services/sql');
const dateUtils = require('../services/date_utils');
@ -47,8 +46,8 @@ class NoteRevision extends Entity {
}
}
async getNote() {
return await repository.getNote(this.noteId);
getNote() {
return this.repository.getNote(this.noteId);
}
/** @returns {boolean} true if the note has string content (not binary) */
@ -66,9 +65,9 @@ class NoteRevision extends Entity {
*/
/** @returns {Promise<*>} */
async getContent(silentNotFoundError = false) {
getContent(silentNotFoundError = false) {
if (this.content === undefined) {
const res = await sql.getRow(`SELECT content, hash FROM note_revision_contents WHERE noteRevisionId = ?`, [this.noteRevisionId]);
const res = sql.getRow(`SELECT content, hash FROM note_revision_contents WHERE noteRevisionId = ?`, [this.noteRevisionId]);
if (!res) {
if (silentNotFoundError) {
@ -102,11 +101,11 @@ class NoteRevision extends Entity {
}
/** @returns {Promise} */
async setContent(content) {
setContent(content) {
// force updating note itself so that utcDateModified is represented correctly even for the content
this.forcedChange = true;
this.contentLength = content === null ? 0 : content.length;
await this.save();
this.save();
this.content = content;
@ -126,9 +125,9 @@ class NoteRevision extends Entity {
}
}
await sql.upsert("note_revision_contents", "noteRevisionId", pojo);
sql.upsert("note_revision_contents", "noteRevisionId", pojo);
await syncTableService.addNoteRevisionContentSync(this.noteRevisionId);
syncTableService.addNoteRevisionContentSync(this.noteRevisionId);
}
beforeSaving() {

View File

@ -2,7 +2,7 @@
const appInfo = require('../../services/app_info');
async function getAppInfo() {
function getAppInfo() {
return appInfo;
}

View File

@ -6,19 +6,19 @@ const attributeService = require('../../services/attributes');
const repository = require('../../services/repository');
const Attribute = require('../../entities/attribute');
async function getEffectiveNoteAttributes(req) {
const note = await repository.getNote(req.params.noteId);
function getEffectiveNoteAttributes(req) {
const note = repository.getNote(req.params.noteId);
return await note.getAttributes();
return note.getAttributes();
}
async function updateNoteAttribute(req) {
function updateNoteAttribute(req) {
const noteId = req.params.noteId;
const body = req.body;
let attribute;
if (body.attributeId) {
attribute = await repository.getAttribute(body.attributeId);
attribute = repository.getAttribute(body.attributeId);
if (attribute.noteId !== noteId) {
return [400, `Attribute ${body.attributeId} is not owned by ${noteId}`];
@ -30,11 +30,11 @@ async function updateNoteAttribute(req) {
if (body.type !== 'relation' || !!body.value.trim()) {
const newAttribute = attribute.createClone(body.type, body.name, body.value);
await newAttribute.save();
newAttribute.save();
}
attribute.isDeleted = true;
await attribute.save();
attribute.save();
return {
attributeId: attribute.attributeId
@ -60,18 +60,18 @@ async function updateNoteAttribute(req) {
attribute.isDeleted = true;
}
await attribute.save();
attribute.save();
return {
attributeId: attribute.attributeId
};
}
async function deleteNoteAttribute(req) {
function deleteNoteAttribute(req) {
const noteId = req.params.noteId;
const attributeId = req.params.attributeId;
const attribute = await repository.getAttribute(attributeId);
const attribute = repository.getAttribute(attributeId);
if (attribute) {
if (attribute.noteId !== noteId) {
@ -79,17 +79,17 @@ async function deleteNoteAttribute(req) {
}
attribute.isDeleted = true;
await attribute.save();
attribute.save();
}
}
async function updateNoteAttributes2(req) {
function updateNoteAttributes2(req) {
const noteId = req.params.noteId;
const incomingAttributes = req.body;
const note = await repository.getNote(noteId);
const note = repository.getNote(noteId);
let existingAttrs = await note.getAttributes();
let existingAttrs = note.getAttributes();
let position = 0;
@ -107,14 +107,14 @@ async function updateNoteAttributes2(req) {
if (perfectMatchAttr.position !== position) {
perfectMatchAttr.position = position;
await perfectMatchAttr.save();
perfectMatchAttr.save();
}
continue; // nothing to update
}
if (incAttr.type === 'relation') {
const targetNote = await repository.getNote(incAttr.value);
const targetNote = repository.getNote(incAttr.value);
if (!targetNote || targetNote.isDeleted) {
log.error(`Target note of relation ${JSON.stringify(incAttr)} does not exist or is deleted`);
@ -130,7 +130,7 @@ async function updateNoteAttributes2(req) {
if (matchedAttr) {
matchedAttr.value = incAttr.value;
matchedAttr.position = position;
await matchedAttr.save();
matchedAttr.save();
existingAttrs = existingAttrs.filter(attr => attr.attributeId !== matchedAttr.attributeId);
continue;
@ -139,17 +139,17 @@ async function updateNoteAttributes2(req) {
// no existing attribute has been matched so we need to create a new one
// type, name and isInheritable are immutable so even if there is an attribute with matching type & name, we need to create a new one and delete the former one
await note.addAttribute(incAttr.type, incAttr.name, incAttr.value, incAttr.isInheritable, position);
note.addAttribute(incAttr.type, incAttr.name, incAttr.value, incAttr.isInheritable, position);
}
// all the remaining existing attributes are not defined anymore and should be deleted
for (const toDeleteAttr of existingAttrs) {
toDeleteAttr.isDeleted = true;
await toDeleteAttr.save();
toDeleteAttr.save();
}
}
async function updateNoteAttributes(req) {
function updateNoteAttributes(req) {
const noteId = req.params.noteId;
const attributes = req.body;
@ -157,7 +157,7 @@ async function updateNoteAttributes(req) {
let attributeEntity;
if (attribute.attributeId) {
attributeEntity = await repository.getAttribute(attribute.attributeId);
attributeEntity = repository.getAttribute(attribute.attributeId);
if (attributeEntity.noteId !== noteId) {
return [400, `Attribute ${attributeEntity.noteId} is not owned by ${noteId}`];
@ -170,11 +170,11 @@ async function updateNoteAttributes(req) {
if (attribute.type !== 'relation' || !!attribute.value.trim()) {
const newAttribute = attributeEntity.createClone(attribute.type, attribute.name, attribute.value, attribute.isInheritable);
await newAttribute.save();
newAttribute.save();
}
attributeEntity.isDeleted = true;
await attributeEntity.save();
attributeEntity.save();
continue;
}
@ -203,34 +203,34 @@ async function updateNoteAttributes(req) {
attributeEntity.value = attribute.value;
}
await attributeEntity.save();
attributeEntity.save();
}
const note = await repository.getNote(noteId);
const note = repository.getNote(noteId);
note.invalidateAttributeCache();
return await note.getAttributes();
return note.getAttributes();
}
async function getAttributeNames(req) {
function getAttributeNames(req) {
const type = req.query.type;
const query = req.query.query;
return attributeService.getAttributeNames(type, query);
}
async function getValuesForAttribute(req) {
function getValuesForAttribute(req) {
const attributeName = req.params.attributeName;
return await sql.getColumn("SELECT DISTINCT value FROM attributes WHERE isDeleted = 0 AND name = ? AND type = 'label' AND value != '' ORDER BY value", [attributeName]);
return sql.getColumn("SELECT DISTINCT value FROM attributes WHERE isDeleted = 0 AND name = ? AND type = 'label' AND value != '' ORDER BY value", [attributeName]);
}
async function createRelation(req) {
function createRelation(req) {
const sourceNoteId = req.params.noteId;
const targetNoteId = req.params.targetNoteId;
const name = req.params.name;
let attribute = await repository.getEntity(`SELECT * FROM attributes WHERE isDeleted = 0 AND noteId = ? AND type = 'relation' AND name = ? AND value = ?`, [sourceNoteId, name, targetNoteId]);
let attribute = repository.getEntity(`SELECT * FROM attributes WHERE isDeleted = 0 AND noteId = ? AND type = 'relation' AND name = ? AND value = ?`, [sourceNoteId, name, targetNoteId]);
if (!attribute) {
attribute = new Attribute();
@ -239,22 +239,22 @@ async function createRelation(req) {
attribute.type = 'relation';
attribute.value = targetNoteId;
await attribute.save();
attribute.save();
}
return attribute;
}
async function deleteRelation(req) {
function deleteRelation(req) {
const sourceNoteId = req.params.noteId;
const targetNoteId = req.params.targetNoteId;
const name = req.params.name;
let attribute = await repository.getEntity(`SELECT * FROM attributes WHERE isDeleted = 0 AND noteId = ? AND type = 'relation' AND name = ? AND value = ?`, [sourceNoteId, name, targetNoteId]);
let attribute = repository.getEntity(`SELECT * FROM attributes WHERE isDeleted = 0 AND noteId = ? AND type = 'relation' AND name = ? AND value = ?`, [sourceNoteId, name, targetNoteId]);
if (attribute) {
attribute.isDeleted = true;
await attribute.save();
attribute.save();
}
}

View File

@ -7,7 +7,7 @@ const log = require('../../services/log');
const utils = require('../../services/utils');
const optionService = require('../../services/options');
async function getAutocomplete(req) {
function getAutocomplete(req) {
const query = req.query.query.trim();
const activeNoteId = req.query.activeNoteId || 'none';
@ -16,10 +16,10 @@ async function getAutocomplete(req) {
const timestampStarted = Date.now();
if (query.length === 0) {
results = await getRecentNotes(activeNoteId);
results = getRecentNotes(activeNoteId);
}
else {
results = await searchService.searchNotesForAutocomplete(query);
results = searchService.searchNotesForAutocomplete(query);
}
const msTaken = Date.now() - timestampStarted;
@ -31,15 +31,15 @@ async function getAutocomplete(req) {
return results;
}
async function getRecentNotes(activeNoteId) {
function getRecentNotes(activeNoteId) {
let extraCondition = '';
const hoistedNoteId = await optionService.getOption('hoistedNoteId');
const hoistedNoteId = optionService.getOption('hoistedNoteId');
if (hoistedNoteId !== 'root') {
extraCondition = `AND recent_notes.notePath LIKE '%${utils.sanitizeSql(hoistedNoteId)}%'`;
}
const recentNotes = await repository.getEntities(`
const recentNotes = repository.getEntities(`
SELECT
recent_notes.*
FROM

View File

@ -4,7 +4,7 @@ const fs = require('fs');
const dateUtils = require('../../services/date_utils');
const {LOG_DIR} = require('../../services/data_dir.js');
async function getBackendLog() {
function getBackendLog() {
const file = `${LOG_DIR}/trilium-${dateUtils.localNowDate()}.log`;
return fs.readFileSync(file, 'utf8');

View File

@ -13,11 +13,11 @@ const TaskContext = require('../../services/task_context');
* for not deleted branches. There may be multiple deleted note-parent note relationships.
*/
async function moveBranchToParent(req) {
function moveBranchToParent(req) {
const {branchId, parentBranchId} = req.params;
const parentBranch = await repository.getBranch(parentBranchId);
const branchToMove = await repository.getBranch(branchId);
const parentBranch = repository.getBranch(parentBranchId);
const branchToMove = repository.getBranch(branchId);
if (!parentBranch || !branchToMove) {
return [400, `One or both branches ${branchId}, ${parentBranchId} have not been found`];
@ -27,35 +27,35 @@ async function moveBranchToParent(req) {
return { success: true }; // no-op
}
const validationResult = await treeService.validateParentChild(parentBranch.noteId, branchToMove.noteId, branchId);
const validationResult = treeService.validateParentChild(parentBranch.noteId, branchToMove.noteId, branchId);
if (!validationResult.success) {
return [200, validationResult];
}
const maxNotePos = await sql.getValue('SELECT MAX(notePosition) FROM branches WHERE parentNoteId = ? AND isDeleted = 0', [parentBranch.noteId]);
const maxNotePos = sql.getValue('SELECT MAX(notePosition) FROM branches WHERE parentNoteId = ? AND isDeleted = 0', [parentBranch.noteId]);
const newNotePos = maxNotePos === null ? 0 : maxNotePos + 10;
// expanding so that the new placement of the branch is immediately visible
parentBranch.isExpanded = true;
await parentBranch.save();
parentBranch.save();
const newBranch = branchToMove.createClone(parentBranch.noteId, newNotePos);
await newBranch.save();
newBranch.save();
branchToMove.isDeleted = true;
await branchToMove.save();
branchToMove.save();
return { success: true };
}
async function moveBranchBeforeNote(req) {
function moveBranchBeforeNote(req) {
const {branchId, beforeBranchId} = req.params;
const branchToMove = await repository.getBranch(branchId);
const beforeNote = await repository.getBranch(beforeBranchId);
const branchToMove = repository.getBranch(branchId);
const beforeNote = repository.getBranch(beforeBranchId);
const validationResult = await treeService.validateParentChild(beforeNote.parentNoteId, branchToMove.noteId, branchId);
const validationResult = treeService.validateParentChild(beforeNote.parentNoteId, branchToMove.noteId, branchId);
if (!validationResult.success) {
return [200, validationResult];
@ -63,33 +63,33 @@ async function moveBranchBeforeNote(req) {
// we don't change utcDateModified so other changes are prioritized in case of conflict
// also we would have to sync all those modified branches otherwise hash checks would fail
await sql.execute("UPDATE branches SET notePosition = notePosition + 10 WHERE parentNoteId = ? AND notePosition >= ? AND isDeleted = 0",
sql.execute("UPDATE branches SET notePosition = notePosition + 10 WHERE parentNoteId = ? AND notePosition >= ? AND isDeleted = 0",
[beforeNote.parentNoteId, beforeNote.notePosition]);
await syncTableService.addNoteReorderingSync(beforeNote.parentNoteId);
syncTableService.addNoteReorderingSync(beforeNote.parentNoteId);
if (branchToMove.parentNoteId === beforeNote.parentNoteId) {
branchToMove.notePosition = beforeNote.notePosition;
await branchToMove.save();
branchToMove.save();
}
else {
const newBranch = branchToMove.createClone(beforeNote.parentNoteId, beforeNote.notePosition);
await newBranch.save();
newBranch.save();
branchToMove.isDeleted = true;
await branchToMove.save();
branchToMove.save();
}
return { success: true };
}
async function moveBranchAfterNote(req) {
function moveBranchAfterNote(req) {
const {branchId, afterBranchId} = req.params;
const branchToMove = await repository.getBranch(branchId);
const afterNote = await repository.getBranch(afterBranchId);
const branchToMove = repository.getBranch(branchId);
const afterNote = repository.getBranch(afterBranchId);
const validationResult = await treeService.validateParentChild(afterNote.parentNoteId, branchToMove.noteId, branchId);
const validationResult = treeService.validateParentChild(afterNote.parentNoteId, branchToMove.noteId, branchId);
if (!validationResult.success) {
return [200, validationResult];
@ -97,42 +97,42 @@ async function moveBranchAfterNote(req) {
// we don't change utcDateModified so other changes are prioritized in case of conflict
// also we would have to sync all those modified branches otherwise hash checks would fail
await sql.execute("UPDATE branches SET notePosition = notePosition + 10 WHERE parentNoteId = ? AND notePosition > ? AND isDeleted = 0",
sql.execute("UPDATE branches SET notePosition = notePosition + 10 WHERE parentNoteId = ? AND notePosition > ? AND isDeleted = 0",
[afterNote.parentNoteId, afterNote.notePosition]);
await syncTableService.addNoteReorderingSync(afterNote.parentNoteId);
syncTableService.addNoteReorderingSync(afterNote.parentNoteId);
const movedNotePosition = afterNote.notePosition + 10;
if (branchToMove.parentNoteId === afterNote.parentNoteId) {
branchToMove.notePosition = movedNotePosition;
await branchToMove.save();
branchToMove.save();
}
else {
const newBranch = branchToMove.createClone(afterNote.parentNoteId, movedNotePosition);
await newBranch.save();
newBranch.save();
branchToMove.isDeleted = true;
await branchToMove.save();
branchToMove.save();
}
return { success: true };
}
async function setExpanded(req) {
function setExpanded(req) {
const {branchId, expanded} = req.params;
if (branchId !== 'root') {
await sql.execute("UPDATE branches SET isExpanded = ? WHERE branchId = ?", [expanded, branchId]);
sql.execute("UPDATE branches SET isExpanded = ? WHERE branchId = ?", [expanded, branchId]);
// we don't sync expanded label
// also this does not trigger updates to the frontend, this would trigger too many reloads
}
}
async function setExpandedForSubtree(req) {
function setExpandedForSubtree(req) {
const {branchId, expanded} = req.params;
let branchIds = await sql.getColumn(`
let branchIds = sql.getColumn(`
WITH RECURSIVE
tree(branchId, noteId) AS (
SELECT branchId, noteId FROM branches WHERE branchId = ?
@ -146,20 +146,20 @@ async function setExpandedForSubtree(req) {
// root is always expanded
branchIds = branchIds.filter(branchId => branchId !== 'root');
await sql.executeMany(`UPDATE branches SET isExpanded = ${expanded} WHERE branchId IN (???)`, branchIds);
sql.executeMany(`UPDATE branches SET isExpanded = ${expanded} WHERE branchId IN (???)`, branchIds);
return {
branchIds
};
}
async function deleteBranch(req) {
function deleteBranch(req) {
const last = req.query.last === 'true';
const branch = await repository.getBranch(req.params.branchId);
const branch = repository.getBranch(req.params.branchId);
const taskContext = TaskContext.getInstance(req.query.taskId, 'delete-notes');
const deleteId = utils.randomString(10);
const noteDeleted = await noteService.deleteBranch(branch, deleteId, taskContext);
const noteDeleted = noteService.deleteBranch(branch, deleteId, taskContext);
if (last) {
taskContext.taskSucceeded();
@ -170,13 +170,13 @@ async function deleteBranch(req) {
};
}
async function setPrefix(req) {
function setPrefix(req) {
const branchId = req.params.branchId;
const prefix = utils.isEmptyOrWhitespace(req.body.prefix) ? null : req.body.prefix;
const branch = await repository.getBranch(branchId);
const branch = repository.getBranch(branchId);
branch.prefix = prefix;
await branch.save();
branch.save();
}
module.exports = {

View File

@ -12,11 +12,11 @@ const utils = require('../../services/utils');
const path = require('path');
const Attribute = require('../../entities/attribute');
async function findClippingNote(todayNote, pageUrl) {
const notes = await todayNote.getDescendantNotesWithLabel('pageUrl', pageUrl);
function findClippingNote(todayNote, pageUrl) {
const notes = todayNote.getDescendantNotesWithLabel('pageUrl', pageUrl);
for (const note of notes) {
if (await note.getOwnedLabelValue('clipType') === 'clippings') {
if (note.getOwnedLabelValue('clipType') === 'clippings') {
return note;
}
}
@ -24,76 +24,76 @@ async function findClippingNote(todayNote, pageUrl) {
return null;
}
async function getClipperInboxNote() {
let clipperInbox = await attributeService.getNoteWithLabel('clipperInbox');
function getClipperInboxNote() {
let clipperInbox = attributeService.getNoteWithLabel('clipperInbox');
if (!clipperInbox) {
clipperInbox = await dateNoteService.getDateNote(dateUtils.localNowDate());
clipperInbox = dateNoteService.getDateNote(dateUtils.localNowDate());
}
return clipperInbox;
}
async function addClipping(req) {
function addClipping(req) {
const {title, content, pageUrl, images} = req.body;
const clipperInbox = await getClipperInboxNote();
const clipperInbox = getClipperInboxNote();
let clippingNote = await findClippingNote(clipperInbox, pageUrl);
let clippingNote = findClippingNote(clipperInbox, pageUrl);
if (!clippingNote) {
clippingNote = (await noteService.createNewNote({
clippingNote = (noteService.createNewNote({
parentNoteId: clipperInbox.noteId,
title: title,
content: '',
type: 'text'
})).note;
await clippingNote.setLabel('clipType', 'clippings');
await clippingNote.setLabel('pageUrl', pageUrl);
clippingNote.setLabel('clipType', 'clippings');
clippingNote.setLabel('pageUrl', pageUrl);
}
const rewrittenContent = await addImagesToNote(images, clippingNote, content);
const rewrittenContent = addImagesToNote(images, clippingNote, content);
const existingContent = await clippingNote.getContent();
const existingContent = clippingNote.getContent();
await clippingNote.setContent(existingContent + (existingContent.trim() ? "<br/>" : "") + rewrittenContent);
clippingNote.setContent(existingContent + (existingContent.trim() ? "<br/>" : "") + rewrittenContent);
return {
noteId: clippingNote.noteId
};
}
async function createNote(req) {
function createNote(req) {
const {title, content, pageUrl, images, clipType} = req.body;
log.info(`Creating clipped note from ${pageUrl}`);
const clipperInbox = await getClipperInboxNote();
const clipperInbox = getClipperInboxNote();
const {note} = await noteService.createNewNote({
const {note} = noteService.createNewNote({
parentNoteId: clipperInbox.noteId,
title,
content,
type: 'text'
});
await note.setLabel('clipType', clipType);
note.setLabel('clipType', clipType);
if (pageUrl) {
await note.setLabel('pageUrl', pageUrl);
note.setLabel('pageUrl', pageUrl);
}
const rewrittenContent = await addImagesToNote(images, note, content);
const rewrittenContent = addImagesToNote(images, note, content);
await note.setContent(rewrittenContent);
note.setContent(rewrittenContent);
return {
noteId: note.noteId
};
}
async function addImagesToNote(images, note, content) {
function addImagesToNote(images, note, content) {
let rewrittenContent = content;
if (images) {
@ -107,15 +107,15 @@ async function addImagesToNote(images, note, content) {
const buffer = Buffer.from(dataUrl.split(",")[1], 'base64');
const {note: imageNote, url} = await imageService.saveImage(note.noteId, buffer, filename, true);
const {note: imageNote, url} = imageService.saveImage(note.noteId, buffer, filename, true);
await new Attribute({
new Attribute({
noteId: imageNote.noteId,
type: 'label',
name: 'hideInAutocomplete'
}).save();
await new Attribute({
new Attribute({
noteId: note.noteId,
type: 'relation',
name: 'imageLink',
@ -131,7 +131,7 @@ async function addImagesToNote(images, note, content) {
return rewrittenContent;
}
async function openNote(req) {
function openNote(req) {
if (utils.isElectron()) {
ws.sendMessageToAllClients({
type: 'open-note',
@ -149,7 +149,7 @@ async function openNote(req) {
}
}
async function handshake() {
function handshake() {
return {
appName: "trilium",
protocolVersion: appInfo.clipperProtocolVersion

View File

@ -2,17 +2,17 @@
const cloningService = require('../../services/cloning');
async function cloneNoteToParent(req) {
function cloneNoteToParent(req) {
const {noteId, parentBranchId} = req.params;
const {prefix} = req.body;
return await cloningService.cloneNoteToParent(noteId, parentBranchId, prefix);
return cloningService.cloneNoteToParent(noteId, parentBranchId, prefix);
}
async function cloneNoteAfter(req) {
function cloneNoteAfter(req) {
const {noteId, afterBranchId} = req.params;
return await cloningService.cloneNoteAfter(noteId, afterBranchId);
return cloningService.cloneNoteAfter(noteId, afterBranchId);
}
module.exports = {

View File

@ -5,24 +5,24 @@ const log = require('../../services/log');
const backupService = require('../../services/backup');
const consistencyChecksService = require('../../services/consistency_checks');
async function anonymize() {
return await backupService.anonymize();
function anonymize() {
return backupService.anonymize();
}
async function backupDatabase() {
function backupDatabase() {
return {
backupFile: await backupService.backupNow("now")
backupFile: backupService.backupNow("now")
};
}
async function vacuumDatabase() {
await sql.execute("VACUUM");
function vacuumDatabase() {
sql.execute("VACUUM");
log.info("Database has been vacuumed.");
}
async function findAndFixConsistencyIssues() {
await consistencyChecksService.runOnDemandChecks(true);
function findAndFixConsistencyIssues() {
consistencyChecksService.runOnDemandChecks(true);
}
module.exports = {

View File

@ -5,19 +5,19 @@ const sql = require('../../services/sql');
const dateUtils = require('../../services/date_utils');
const noteService = require('../../services/notes');
async function getDateNote(req) {
return await dateNoteService.getDateNote(req.params.date);
function getDateNote(req) {
return dateNoteService.getDateNote(req.params.date);
}
async function getMonthNote(req) {
return await dateNoteService.getMonthNote(req.params.month);
function getMonthNote(req) {
return dateNoteService.getMonthNote(req.params.month);
}
async function getYearNote(req) {
return await dateNoteService.getYearNote(req.params.year);
function getYearNote(req) {
return dateNoteService.getYearNote(req.params.year);
}
async function getDateNotesForMonth(req) {
function getDateNotesForMonth(req) {
const month = req.params.month;
return sql.getMap(`
@ -33,12 +33,12 @@ async function getDateNotesForMonth(req) {
AND attr.value LIKE '${month}%'`);
}
async function createSqlConsole() {
function createSqlConsole() {
const today = dateUtils.localNowDate();
const todayNote = await dateNoteService.getDateNote(today);
const todayNote = dateNoteService.getDateNote(today);
const {note} = await noteService.createNewNote({
const {note} = noteService.createNewNote({
parentNoteId: todayNote.noteId,
title: 'SQL Console',
content: "SELECT title, isDeleted, isProtected FROM notes WHERE noteId = ''\n\n\n\n",
@ -46,7 +46,7 @@ async function createSqlConsole() {
mime: 'text/x-sqlite;schema=trilium'
});
await note.setLabel("sqlConsole", today);
note.setLabel("sqlConsole", today);
return note;
}

View File

@ -7,9 +7,9 @@ const repository = require("../../services/repository");
const TaskContext = require("../../services/task_context");
const log = require("../../services/log");
async function exportBranch(req, res) {
function exportBranch(req, res) {
const {branchId, type, format, version, taskId} = req.params;
const branch = await repository.getBranch(branchId);
const branch = repository.getBranch(branchId);
if (!branch) {
const message = `Cannot export branch ${branchId} since it does not exist.`;
@ -25,15 +25,15 @@ async function exportBranch(req, res) {
if (type === 'subtree' && (format === 'html' || format === 'markdown')) {
const start = Date.now();
await zipExportService.exportToZip(taskContext, branch, format, res);
zipExportService.exportToZip(taskContext, branch, format, res);
console.log("Export took", Date.now() - start, "ms");
}
else if (type === 'single') {
await singleExportService.exportSingleNote(taskContext, branch, format, res);
singleExportService.exportSingleNote(taskContext, branch, format, res);
}
else if (format === 'opml') {
await opmlExportService.exportToOpml(taskContext, branch, version, res);
opmlExportService.exportToOpml(taskContext, branch, version, res);
}
else {
return [404, "Unrecognized export format " + format];

View File

@ -5,33 +5,33 @@ const repository = require('../../services/repository');
const utils = require('../../services/utils');
const noteRevisionService = require('../../services/note_revisions');
async function updateFile(req) {
function updateFile(req) {
const {noteId} = req.params;
const file = req.file;
const note = await repository.getNote(noteId);
const note = repository.getNote(noteId);
if (!note) {
return [404, `Note ${noteId} doesn't exist.`];
}
await noteRevisionService.createNoteRevision(note);
noteRevisionService.createNoteRevision(note);
note.mime = file.mimetype.toLowerCase();
await note.setContent(file.buffer);
note.setContent(file.buffer);
await note.setLabel('originalFileName', file.originalname);
note.setLabel('originalFileName', file.originalname);
await noteRevisionService.protectNoteRevisions(note);
noteRevisionService.protectNoteRevisions(note);
return {
uploaded: true
};
}
async function downloadNoteFile(noteId, res, contentDisposition = true) {
const note = await repository.getNote(noteId);
function downloadNoteFile(noteId, res, contentDisposition = true) {
const note = repository.getNote(noteId);
if (!note) {
return res.status(404).send(`Note ${noteId} doesn't exist.`);
@ -51,19 +51,19 @@ async function downloadNoteFile(noteId, res, contentDisposition = true) {
res.setHeader('Content-Type', note.mime);
res.send(await note.getContent());
res.send(note.getContent());
}
async function downloadFile(req, res) {
function downloadFile(req, res) {
const noteId = req.params.noteId;
return await downloadNoteFile(noteId, res);
return downloadNoteFile(noteId, res);
}
async function openFile(req, res) {
function openFile(req, res) {
const noteId = req.params.noteId;
return await downloadNoteFile(noteId, res, false);
return downloadNoteFile(noteId, res, false);
}
module.exports = {

View File

@ -5,8 +5,8 @@ const repository = require('../../services/repository');
const RESOURCE_DIR = require('../../services/resource_dir').RESOURCE_DIR;
const fs = require('fs');
async function returnImage(req, res) {
const image = await repository.getNote(req.params.noteId);
function returnImage(req, res) {
const image = repository.getNote(req.params.noteId);
if (!image) {
return res.sendStatus(404);
@ -21,14 +21,14 @@ async function returnImage(req, res) {
res.set('Content-Type', image.mime);
res.send(await image.getContent());
res.send(image.getContent());
}
async function uploadImage(req) {
function uploadImage(req) {
const {noteId} = req.query;
const {file} = req;
const note = await repository.getNote(noteId);
const note = repository.getNote(noteId);
if (!note) {
return [404, `Note ${noteId} doesn't exist.`];
@ -38,7 +38,7 @@ async function uploadImage(req) {
return [400, "Unknown image type: " + file.mimetype];
}
const {url} = await imageService.saveImage(noteId, file.buffer, file.originalname, true);
const {url} = imageService.saveImage(noteId, file.buffer, file.originalname, true);
return {
uploaded: true,
@ -46,11 +46,11 @@ async function uploadImage(req) {
};
}
async function updateImage(req) {
function updateImage(req) {
const {noteId} = req.params;
const {file} = req;
const note = await repository.getNote(noteId);
const note = repository.getNote(noteId);
if (!note) {
return [404, `Note ${noteId} doesn't exist.`];
@ -63,7 +63,7 @@ async function updateImage(req) {
};
}
await imageService.updateImage(noteId, file.buffer, file.originalname);
imageService.updateImage(noteId, file.buffer, file.originalname);
return { uploaded: true };
}

View File

@ -12,7 +12,7 @@ const noteCacheService = require('../../services/note_cache/note_cache.js');
const log = require('../../services/log');
const TaskContext = require('../../services/task_context.js');
async function importToBranch(req) {
function importToBranch(req) {
const {parentNoteId} = req.params;
const {taskId, last} = req.body;
@ -31,7 +31,7 @@ async function importToBranch(req) {
return [400, "No file has been uploaded"];
}
const parentNote = await repository.getNote(parentNoteId);
const parentNote = repository.getNote(parentNoteId);
if (!parentNote) {
return [404, `Note ${parentNoteId} doesn't exist.`];
@ -49,19 +49,19 @@ async function importToBranch(req) {
try {
if (extension === '.tar' && options.explodeArchives) {
note = await tarImportService.importTar(taskContext, file.buffer, parentNote);
note = tarImportService.importTar(taskContext, file.buffer, parentNote);
} else if (extension === '.zip' && options.explodeArchives) {
const start = Date.now();
note = await zipImportService.importZip(taskContext, file.buffer, parentNote);
note = zipImportService.importZip(taskContext, file.buffer, parentNote);
console.log("Import took", Date.now() - start, "ms");
} else if (extension === '.opml' && options.explodeArchives) {
note = await opmlImportService.importOpml(taskContext, file.buffer, parentNote);
note = opmlImportService.importOpml(taskContext, file.buffer, parentNote);
} else if (extension === '.enex' && options.explodeArchives) {
note = await enexImportService.importEnex(taskContext, file, parentNote);
note = enexImportService.importEnex(taskContext, file, parentNote);
} else {
note = await singleImportService.importSingleFile(taskContext, file, parentNote);
note = singleImportService.importSingleFile(taskContext, file, parentNote);
}
}
catch (e) {

View File

@ -3,12 +3,12 @@
const keyboardActions = require('../../services/keyboard_actions');
const sql = require('../../services/sql');
async function getKeyboardActions() {
return await keyboardActions.getKeyboardActions();
function getKeyboardActions() {
return keyboardActions.getKeyboardActions();
}
async function getShortcutsForNotes() {
return await sql.getMap(`
function getShortcutsForNotes() {
return sql.getMap(`
SELECT value, noteId
FROM attributes
WHERE isDeleted = 0

View File

@ -2,8 +2,8 @@
const sql = require('../../services/sql');
async function getRelations(noteIds) {
return (await sql.getManyRows(`
function getRelations(noteIds) {
return (sql.getManyRows(`
SELECT noteId, name, value AS targetNoteId
FROM attributes
WHERE (noteId IN (???) OR value IN (???))
@ -14,7 +14,7 @@ async function getRelations(noteIds) {
`, Array.from(noteIds)));
}
async function getLinkMap(req) {
function getLinkMap(req) {
const {noteId} = req.params;
const {maxNotes, maxDepth} = req.body;
@ -24,7 +24,7 @@ async function getLinkMap(req) {
let depth = 0;
while (noteIds.size < maxNotes) {
relations = await getRelations(noteIds);
relations = getRelations(noteIds);
if (depth === maxDepth) {
break;

View File

@ -14,8 +14,8 @@ const sql = require('../../services/sql');
const optionService = require('../../services/options');
const ApiToken = require('../../entities/api_token');
async function loginSync(req) {
if (!await sqlInit.schemaExists()) {
function loginSync(req) {
if (!sqlInit.schemaExists()) {
return [500, { message: "DB schema does not exist, can't sync." }];
}
@ -36,7 +36,7 @@ async function loginSync(req) {
return [400, { message: `Non-matching sync versions, local is version ${appInfo.syncVersion}, remote is ${syncVersion}. It is recommended to run same version of Trilium on both sides of sync.` }];
}
const documentSecret = await options.getOption('documentSecret');
const documentSecret = options.getOption('documentSecret');
const expectedHash = utils.hmac(documentSecret, timestampStr);
const givenHash = req.body.hash;
@ -49,28 +49,28 @@ async function loginSync(req) {
return {
sourceId: sourceIdService.getCurrentSourceId(),
maxSyncId: await sql.getValue("SELECT MAX(id) FROM sync WHERE isSynced = 1")
maxSyncId: sql.getValue("SELECT MAX(id) FROM sync WHERE isSynced = 1")
};
}
async function loginToProtectedSession(req) {
function loginToProtectedSession(req) {
const password = req.body.password;
if (!await passwordEncryptionService.verifyPassword(password)) {
if (!passwordEncryptionService.verifyPassword(password)) {
return {
success: false,
message: "Given current password doesn't match hash"
};
}
const decryptedDataKey = await passwordEncryptionService.getDataKey(password);
const decryptedDataKey = passwordEncryptionService.getDataKey(password);
const protectedSessionId = protectedSessionService.setDataKey(decryptedDataKey);
// this is set here so that event handlers have access to the protected session
cls.set('protectedSessionId', protectedSessionId);
await eventService.emit(eventService.ENTER_PROTECTED_SESSION);
eventService.emit(eventService.ENTER_PROTECTED_SESSION);
return {
success: true,
@ -78,18 +78,18 @@ async function loginToProtectedSession(req) {
};
}
async function token(req) {
function token(req) {
const username = req.body.username;
const password = req.body.password;
const isUsernameValid = username === await optionService.getOption('username');
const isPasswordValid = await passwordEncryptionService.verifyPassword(password);
const isUsernameValid = username === optionService.getOption('username');
const isPasswordValid = passwordEncryptionService.verifyPassword(password);
if (!isUsernameValid || !isPasswordValid) {
return [401, "Incorrect username/password"];
}
const apiToken = await new ApiToken({
const apiToken = new ApiToken({
token: utils.randomSecureToken()
}).save();

View File

@ -7,23 +7,23 @@ const noteRevisionService = require('../../services/note_revisions');
const utils = require('../../services/utils');
const path = require('path');
async function getNoteRevisions(req) {
return await repository.getEntities(`
function getNoteRevisions(req) {
return repository.getEntities(`
SELECT * FROM note_revisions
WHERE noteId = ? AND isErased = 0
ORDER BY utcDateCreated DESC`, [req.params.noteId]);
}
async function getNoteRevision(req) {
const noteRevision = await repository.getNoteRevision(req.params.noteRevisionId);
function getNoteRevision(req) {
const noteRevision = repository.getNoteRevision(req.params.noteRevisionId);
if (noteRevision.type === 'file') {
if (noteRevision.isStringNote()) {
noteRevision.content = (await noteRevision.getContent()).substr(0, 10000);
noteRevision.content = (noteRevision.getContent()).substr(0, 10000);
}
}
else {
noteRevision.content = await noteRevision.getContent();
noteRevision.content = noteRevision.getContent();
if (noteRevision.content && noteRevision.type === 'image') {
noteRevision.content = noteRevision.content.toString('base64');
@ -57,8 +57,8 @@ function getRevisionFilename(noteRevision) {
return filename;
}
async function downloadNoteRevision(req, res) {
const noteRevision = await repository.getNoteRevision(req.params.noteRevisionId);
function downloadNoteRevision(req, res) {
const noteRevision = repository.getNoteRevision(req.params.noteRevisionId);
if (noteRevision.noteId !== req.params.noteId) {
return res.status(400).send(`Note revision ${req.params.noteRevisionId} does not belong to note ${req.params.noteId}`);
@ -73,55 +73,55 @@ async function downloadNoteRevision(req, res) {
res.setHeader('Content-Disposition', utils.getContentDisposition(filename));
res.setHeader('Content-Type', noteRevision.mime);
res.send(await noteRevision.getContent());
res.send(noteRevision.getContent());
}
/**
* @param {NoteRevision} noteRevision
*/
async function eraseOneNoteRevision(noteRevision) {
function eraseOneNoteRevision(noteRevision) {
noteRevision.isErased = true;
noteRevision.title = null;
await noteRevision.setContent(null);
await noteRevision.save();
noteRevision.setContent(null);
noteRevision.save();
}
async function eraseAllNoteRevisions(req) {
const noteRevisionsToErase = await repository.getEntities(
function eraseAllNoteRevisions(req) {
const noteRevisionsToErase = repository.getEntities(
'SELECT * FROM note_revisions WHERE isErased = 0 AND noteId = ?',
[req.params.noteId]);
for (const noteRevision of noteRevisionsToErase) {
await eraseOneNoteRevision(noteRevision);
eraseOneNoteRevision(noteRevision);
}
}
async function eraseNoteRevision(req) {
const noteRevision = await repository.getNoteRevision(req.params.noteRevisionId);
function eraseNoteRevision(req) {
const noteRevision = repository.getNoteRevision(req.params.noteRevisionId);
if (noteRevision && !noteRevision.isErased) {
await eraseOneNoteRevision(noteRevision);
eraseOneNoteRevision(noteRevision);
}
}
async function restoreNoteRevision(req) {
const noteRevision = await repository.getNoteRevision(req.params.noteRevisionId);
function restoreNoteRevision(req) {
const noteRevision = repository.getNoteRevision(req.params.noteRevisionId);
if (noteRevision && !noteRevision.isErased) {
const note = await noteRevision.getNote();
const note = noteRevision.getNote();
await noteRevisionService.createNoteRevision(note);
noteRevisionService.createNoteRevision(note);
note.title = noteRevision.title;
await note.setContent(await noteRevision.getContent());
await note.save();
note.setContent(noteRevision.getContent());
note.save();
}
}
async function getEditedNotesOnDate(req) {
function getEditedNotesOnDate(req) {
const date = utils.sanitizeSql(req.params.date);
const notes = await repository.getEntities(`
const notes = repository.getEntities(`
SELECT notes.*
FROM notes
WHERE noteId IN (

View File

@ -6,16 +6,16 @@ const repository = require('../../services/repository');
const utils = require('../../services/utils');
const TaskContext = require('../../services/task_context');
async function getNote(req) {
function getNote(req) {
const noteId = req.params.noteId;
const note = await repository.getNote(noteId);
const note = repository.getNote(noteId);
if (!note) {
return [404, "Note " + noteId + " has not been found."];
}
if (note.isStringNote()) {
note.content = await note.getContent();
note.content = note.getContent();
if (note.type === 'file') {
note.content = note.content.substr(0, 10000);
@ -25,13 +25,13 @@ async function getNote(req) {
return note;
}
async function createNote(req) {
function createNote(req) {
const params = Object.assign({}, req.body); // clone
params.parentNoteId = req.params.parentNoteId;
const { target, targetBranchId } = req.query;
const { note, branch } = await noteService.createNewNoteWithTarget(target, targetBranchId, params);
const { note, branch } = noteService.createNewNoteWithTarget(target, targetBranchId, params);
return {
note,
@ -39,14 +39,14 @@ async function createNote(req) {
};
}
async function updateNote(req) {
function updateNote(req) {
const note = req.body;
const noteId = req.params.noteId;
return await noteService.updateNote(noteId, note);
return noteService.updateNote(noteId, note);
}
async function deleteNote(req) {
function deleteNote(req) {
const noteId = req.params.noteId;
const taskId = req.query.taskId;
const last = req.query.last === 'true';
@ -54,61 +54,61 @@ async function deleteNote(req) {
// note how deleteId is separate from taskId - single taskId produces separate deleteId for each "top level" deleted note
const deleteId = utils.randomString(10);
const note = await repository.getNote(noteId);
const note = repository.getNote(noteId);
const taskContext = TaskContext.getInstance(taskId, 'delete-notes');
for (const branch of await note.getBranches()) {
await noteService.deleteBranch(branch, deleteId, taskContext);
for (const branch of note.getBranches()) {
noteService.deleteBranch(branch, deleteId, taskContext);
}
if (last) {
await taskContext.taskSucceeded();
taskContext.taskSucceeded();
}
}
async function undeleteNote(req) {
const note = await repository.getNote(req.params.noteId);
function undeleteNote(req) {
const note = repository.getNote(req.params.noteId);
const taskContext = TaskContext.getInstance(utils.randomString(10), 'undelete-notes');
await noteService.undeleteNote(note, note.deleteId, taskContext);
noteService.undeleteNote(note, note.deleteId, taskContext);
await taskContext.taskSucceeded();
taskContext.taskSucceeded();
}
async function sortNotes(req) {
function sortNotes(req) {
const noteId = req.params.noteId;
await treeService.sortNotesAlphabetically(noteId);
treeService.sortNotesAlphabetically(noteId);
}
async function protectNote(req) {
function protectNote(req) {
const noteId = req.params.noteId;
const note = await repository.getNote(noteId);
const note = repository.getNote(noteId);
const protect = !!parseInt(req.params.isProtected);
const includingSubTree = !!parseInt(req.query.subtree);
const taskContext = new TaskContext(utils.randomString(10), 'protect-notes', {protect});
await noteService.protectNoteRecursively(note, protect, includingSubTree, taskContext);
noteService.protectNoteRecursively(note, protect, includingSubTree, taskContext);
taskContext.taskSucceeded();
}
async function setNoteTypeMime(req) {
function setNoteTypeMime(req) {
// can't use [] destructuring because req.params is not iterable
const noteId = req.params[0];
const type = req.params[1];
const mime = req.params[2];
const note = await repository.getNote(noteId);
const note = repository.getNote(noteId);
note.type = type;
note.mime = mime;
await note.save();
note.save();
}
async function getRelationMap(req) {
function getRelationMap(req) {
const noteIds = req.body.noteIds;
const resp = {
// noteId => title
@ -126,12 +126,12 @@ async function getRelationMap(req) {
const questionMarks = noteIds.map(noteId => '?').join(',');
const notes = await repository.getEntities(`SELECT * FROM notes WHERE isDeleted = 0 AND noteId IN (${questionMarks})`, noteIds);
const notes = repository.getEntities(`SELECT * FROM notes WHERE isDeleted = 0 AND noteId IN (${questionMarks})`, noteIds);
for (const note of notes) {
resp.noteTitles[note.noteId] = note.title;
resp.relations = resp.relations.concat((await note.getRelations())
resp.relations = resp.relations.concat((note.getRelations())
.filter(relation => noteIds.includes(relation.value))
.map(relation => ({
attributeId: relation.attributeId,
@ -140,7 +140,7 @@ async function getRelationMap(req) {
name: relation.name
})));
for (const relationDefinition of await note.getRelationDefinitions()) {
for (const relationDefinition of note.getRelationDefinitions()) {
if (relationDefinition.value.inverseRelation) {
resp.inverseRelations[relationDefinition.name] = relationDefinition.value.inverseRelation;
}
@ -150,11 +150,11 @@ async function getRelationMap(req) {
return resp;
}
async function changeTitle(req) {
function changeTitle(req) {
const noteId = req.params.noteId;
const title = req.body.title;
const note = await repository.getNote(noteId);
const note = repository.getNote(noteId);
if (!note) {
return [404, `Note ${noteId} has not been found`];
@ -166,15 +166,15 @@ async function changeTitle(req) {
note.title = title;
await note.save();
note.save();
return note;
}
async function duplicateNote(req) {
function duplicateNote(req) {
const {noteId, parentNoteId} = req.params;
return await noteService.duplicateNote(noteId, parentNoteId);
return noteService.duplicateNote(noteId, parentNoteId);
}
module.exports = {

View File

@ -40,8 +40,8 @@ const ALLOWED_OPTIONS = new Set([
'nativeTitleBarVisible'
]);
async function getOptions() {
const optionMap = await optionService.getOptionsMap();
function getOptions() {
const optionMap = optionService.getOptionsMap();
const resultMap = {};
for (const optionName in optionMap) {
@ -53,17 +53,17 @@ async function getOptions() {
return resultMap;
}
async function updateOption(req) {
function updateOption(req) {
const {name, value} = req.params;
if (!await update(name, value)) {
if (!update(name, value)) {
return [400, "not allowed option to change"];
}
}
async function updateOptions(req) {
function updateOptions(req) {
for (const optionName in req.body) {
if (!await update(optionName, req.body[optionName])) {
if (!update(optionName, req.body[optionName])) {
// this should be improved
// it should return 400 instead of current 500, but at least it now rollbacks transaction
throw new Error(`${optionName} is not allowed to change`);
@ -71,7 +71,7 @@ async function updateOptions(req) {
}
}
async function update(name, value) {
function update(name, value) {
if (!isAllowed(name)) {
return false;
}
@ -80,18 +80,18 @@ async function update(name, value) {
log.info(`Updating option ${name} to ${value}`);
}
await optionService.setOption(name, value);
optionService.setOption(name, value);
return true;
}
async function getUserThemes() {
const notes = await attributes.getNotesWithLabel('appTheme');
function getUserThemes() {
const notes = attributes.getNotesWithLabel('appTheme');
const ret = [];
for (const note of notes) {
let value = await note.getOwnedLabelValue('appTheme');
let value = note.getOwnedLabelValue('appTheme');
if (!value) {
value = note.title.toLowerCase().replace(/[^a-z0-9]/gi, '-');

View File

@ -2,8 +2,8 @@
const changePasswordService = require('../../services/change_password');
async function changePassword(req) {
return await changePasswordService.changePassword(req.body.current_password, req.body.new_password);
function changePassword(req) {
return changePasswordService.changePassword(req.body.current_password, req.body.new_password);
}
module.exports = {

View File

@ -5,12 +5,12 @@ const protectedSessionService = require('../../services/protected_session');
const noteService = require('../../services/notes');
const noteCacheService = require('../../services/note_cache/note_cache.js');
async function getRecentChanges(req) {
function getRecentChanges(req) {
const {ancestorNoteId} = req.params;
let recentChanges = [];
const noteRevisions = await sql.getRows(`
const noteRevisions = sql.getRows(`
SELECT
notes.noteId,
notes.isDeleted AS current_isDeleted,
@ -31,7 +31,7 @@ async function getRecentChanges(req) {
}
}
const notes = await sql.getRows(`
const notes = sql.getRows(`
SELECT
notes.noteId,
notes.isDeleted AS current_isDeleted,
@ -75,7 +75,7 @@ async function getRecentChanges(req) {
else {
const deleteId = change.current_deleteId;
const undeletedParentBranches = await noteService.getUndeletedParentBranches(change.noteId, deleteId);
const undeletedParentBranches = noteService.getUndeletedParentBranches(change.noteId, deleteId);
// note (and the subtree) can be undeleted if there's at least one undeleted parent (whose branch would be undeleted by this op)
change.canBeUndeleted = undeletedParentBranches.length > 0;

View File

@ -2,8 +2,8 @@
const RecentNote = require('../../entities/recent_note');
async function addRecentNote(req) {
await new RecentNote({
function addRecentNote(req) {
new RecentNote({
noteId: req.body.noteId,
notePath: req.body.notePath
}).save();

View File

@ -5,15 +5,15 @@ const attributeService = require('../../services/attributes');
const repository = require('../../services/repository');
const syncService = require('../../services/sync');
async function exec(req) {
function exec(req) {
try {
const result = await scriptService.executeScript(req.body.script, req.body.params, req.body.startNoteId,
const result = scriptService.executeScript(req.body.script, req.body.params, req.body.startNoteId,
req.body.currentNoteId, req.body.originEntityName, req.body.originEntityId);
return {
success: true,
executionResult: result,
maxSyncId: await syncService.getMaxSyncId()
maxSyncId: syncService.getMaxSyncId()
};
}
catch (e) {
@ -21,21 +21,21 @@ async function exec(req) {
}
}
async function run(req) {
const note = await repository.getNote(req.params.noteId);
function run(req) {
const note = repository.getNote(req.params.noteId);
const result = await scriptService.executeNote(note, { originEntity: note });
const result = scriptService.executeNote(note, { originEntity: note });
return { executionResult: result };
}
async function getBundlesWithLabel(label, value) {
const notes = await attributeService.getNotesWithLabel(label, value);
function getBundlesWithLabel(label, value) {
const notes = attributeService.getNotesWithLabel(label, value);
const bundles = [];
for (const note of notes) {
const bundle = await scriptService.getScriptBundleForFrontend(note);
const bundle = scriptService.getScriptBundleForFrontend(note);
if (bundle) {
bundles.push(bundle);
@ -45,20 +45,20 @@ async function getBundlesWithLabel(label, value) {
return bundles;
}
async function getStartupBundles() {
return await getBundlesWithLabel("run", "frontendStartup");
function getStartupBundles() {
return getBundlesWithLabel("run", "frontendStartup");
}
async function getWidgetBundles() {
return await getBundlesWithLabel("widget");
function getWidgetBundles() {
return getBundlesWithLabel("widget");
}
async function getRelationBundles(req) {
function getRelationBundles(req) {
const noteId = req.params.noteId;
const note = await repository.getNote(noteId);
const note = repository.getNote(noteId);
const relationName = req.params.relationName;
const attributes = await note.getAttributes();
const attributes = note.getAttributes();
const filtered = attributes.filter(attr => attr.type === 'relation' && attr.name === relationName);
const targetNoteIds = filtered.map(relation => relation.value);
const uniqueNoteIds = Array.from(new Set(targetNoteIds));
@ -66,13 +66,13 @@ async function getRelationBundles(req) {
const bundles = [];
for (const noteId of uniqueNoteIds) {
const note = await repository.getNote(noteId);
const note = repository.getNote(noteId);
if (!note.isJavaScript() || note.getScriptEnv() !== 'frontend') {
continue;
}
const bundle = await scriptService.getScriptBundleForFrontend(note);
const bundle = scriptService.getScriptBundleForFrontend(note);
if (bundle) {
bundles.push(bundle);
@ -82,10 +82,10 @@ async function getRelationBundles(req) {
return bundles;
}
async function getBundle(req) {
const note = await repository.getNote(req.params.noteId);
function getBundle(req) {
const note = repository.getNote(req.params.noteId);
return await scriptService.getScriptBundleForFrontend(note);
return scriptService.getScriptBundleForFrontend(note);
}
module.exports = {

View File

@ -6,8 +6,8 @@ const log = require('../../services/log');
const scriptService = require('../../services/script');
const searchService = require('../../services/search/search');
async function searchNotes(req) {
const {count, results} = await searchService.searchNotes(req.params.searchString);
function searchNotes(req) {
const {count, results} = searchService.searchNotes(req.params.searchString);
try {
return {
@ -23,8 +23,8 @@ async function searchNotes(req) {
}
}
async function searchFromNote(req) {
const note = await repository.getNote(req.params.noteId);
function searchFromNote(req) {
const note = repository.getNote(req.params.noteId);
if (!note) {
return [404, `Note ${req.params.noteId} has not been found.`];
@ -38,7 +38,7 @@ async function searchFromNote(req) {
return [400, `Note ${req.params.noteId} is not search note.`]
}
const json = await note.getJsonContent();
const json = note.getJsonContent();
if (!json || !json.searchString) {
return [];
@ -50,9 +50,9 @@ async function searchFromNote(req) {
if (json.searchString.startsWith('=')) {
const relationName = json.searchString.substr(1).trim();
noteIds = await searchFromRelation(note, relationName);
noteIds = searchFromRelation(note, relationName);
} else {
noteIds = await searchService.searchForNoteIds(json.searchString);
noteIds = searchService.searchForNoteIds(json.searchString);
}
}
catch (e) {
@ -71,8 +71,8 @@ async function searchFromNote(req) {
return noteIds.map(noteCacheService.getNotePath).filter(res => !!res);
}
async function searchFromRelation(note, relationName) {
const scriptNote = await note.getRelationTarget(relationName);
function searchFromRelation(note, relationName) {
const scriptNote = note.getRelationTarget(relationName);
if (!scriptNote) {
log.info(`Search note's relation ${relationName} has not been found.`);
@ -92,7 +92,7 @@ async function searchFromRelation(note, relationName) {
return [];
}
const result = await scriptService.executeNote(scriptNote, { originEntity: note });
const result = scriptService.executeNote(scriptNote, { originEntity: note });
if (!Array.isArray(result)) {
log.info(`Result from ${scriptNote.noteId} is not an array.`);

View File

@ -5,7 +5,7 @@ const imageService = require('../../services/image');
const dateNoteService = require('../../services/date_notes');
const noteService = require('../../services/notes');
async function uploadImage(req) {
function uploadImage(req) {
const file = req.file;
if (!["image/png", "image/jpeg", "image/gif"].includes(file.mimetype)) {
@ -14,19 +14,19 @@ async function uploadImage(req) {
const originalName = "Sender image." + imageType(file.buffer).ext;
const parentNote = await dateNoteService.getDateNote(req.headers['x-local-date']);
const parentNote = dateNoteService.getDateNote(req.headers['x-local-date']);
const {noteId} = await imageService.saveImage(parentNote.noteId, file.buffer, originalName, true);
const {noteId} = imageService.saveImage(parentNote.noteId, file.buffer, originalName, true);
return {
noteId: noteId
};
}
async function saveNote(req) {
const parentNote = await dateNoteService.getDateNote(req.headers['x-local-date']);
function saveNote(req) {
const parentNote = dateNoteService.getDateNote(req.headers['x-local-date']);
const {note, branch} = await noteService.createNewNote({
const {note, branch} = noteService.createNewNote({
parentNoteId: parentNote.noteId,
title: req.body.title,
content: req.body.content,

View File

@ -5,27 +5,27 @@ const setupService = require('../../services/setup');
const log = require('../../services/log');
const appInfo = require('../../services/app_info');
async function getStatus() {
function getStatus() {
return {
isInitialized: await sqlInit.isDbInitialized(),
schemaExists: await sqlInit.schemaExists(),
isInitialized: sqlInit.isDbInitialized(),
schemaExists: sqlInit.schemaExists(),
syncVersion: appInfo.syncVersion
};
}
async function setupNewDocument(req) {
function setupNewDocument(req) {
const { username, password, theme } = req.body;
await sqlInit.createInitialDatabase(username, password, theme);
sqlInit.createInitialDatabase(username, password, theme);
}
async function setupSyncFromServer(req) {
function setupSyncFromServer(req) {
const { syncServerHost, syncProxy, username, password } = req.body;
return await setupService.setupSyncFromSyncServer(syncServerHost, syncProxy, username, password);
return setupService.setupSyncFromSyncServer(syncServerHost, syncProxy, username, password);
}
async function saveSyncSeed(req) {
function saveSyncSeed(req) {
const {options, syncVersion} = req.body;
if (appInfo.syncVersion !== syncVersion) {
@ -38,14 +38,14 @@ async function saveSyncSeed(req) {
}]
}
await sqlInit.createDatabaseForSync(options);
sqlInit.createDatabaseForSync(options);
}
async function getSyncSeed() {
function getSyncSeed() {
log.info("Serving sync seed.");
return {
options: await setupService.getSyncSeedOptions(),
options: setupService.getSyncSeedOptions(),
syncVersion: appInfo.syncVersion
};
}

View File

@ -3,16 +3,16 @@
const noteCacheService = require('../../services/note_cache/note_cache_service');
const repository = require('../../services/repository');
async function getSimilarNotes(req) {
function getSimilarNotes(req) {
const noteId = req.params.noteId;
const note = await repository.getNote(noteId);
const note = repository.getNote(noteId);
if (!note) {
return [404, `Note ${noteId} not found.`];
}
const results = await noteCacheService.findSimilarNotes(noteId);
const results = noteCacheService.findSimilarNotes(noteId);
return results
.filter(note => note.noteId !== noteId);

View File

@ -2,21 +2,21 @@
const sql = require('../../services/sql');
async function getSchema() {
const tableNames = await sql.getColumn(`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name`);
function getSchema() {
const tableNames = sql.getColumn(`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name`);
const tables = [];
for (const tableName of tableNames) {
tables.push({
name: tableName,
columns: await sql.getRows(`PRAGMA table_info(${tableName})`)
columns: sql.getRows(`PRAGMA table_info(${tableName})`)
});
}
return tables;
}
async function execute(req) {
function execute(req) {
const queries = req.body.query.split("\n---");
try {
@ -27,7 +27,7 @@ async function execute(req) {
continue;
}
results.push(await sql.getRows(query));
results.push(sql.getRows(query));
}
return {

View File

@ -13,13 +13,13 @@ const dateUtils = require('../../services/date_utils');
const entityConstructor = require('../../entities/entity_constructor');
const utils = require('../../services/utils');
async function testSync() {
function testSync() {
try {
if (!await syncOptions.isSyncSetup()) {
if (!syncOptions.isSyncSetup()) {
return { success: false, message: "Sync server host is not configured. Please configure sync first." };
}
await syncService.login();
syncService.login();
// login was successful so we'll kick off sync now
// this is important in case when sync server has been just initialized
@ -35,40 +35,40 @@ async function testSync() {
}
}
async function getStats() {
if (!await sqlInit.schemaExists()) {
function getStats() {
if (!sqlInit.schemaExists()) {
// fail silently but prevent errors from not existing options table
return {};
}
return {
initialized: await optionService.getOption('initialized') === 'true',
initialized: optionService.getOption('initialized') === 'true',
stats: syncService.stats
};
}
async function checkSync() {
function checkSync() {
return {
entityHashes: await contentHashService.getEntityHashes(),
maxSyncId: await sql.getValue('SELECT MAX(id) FROM sync WHERE isSynced = 1')
entityHashes: contentHashService.getEntityHashes(),
maxSyncId: sql.getValue('SELECT MAX(id) FROM sync WHERE isSynced = 1')
};
}
async function syncNow() {
function syncNow() {
log.info("Received request to trigger sync now.");
return await syncService.sync();
return syncService.sync();
}
async function fillSyncRows() {
await syncTableService.fillAllSyncRows();
function fillSyncRows() {
syncTableService.fillAllSyncRows();
log.info("Sync rows have been filled.");
}
async function forceFullSync() {
await optionService.setOption('lastSyncedPull', 0);
await optionService.setOption('lastSyncedPush', 0);
function forceFullSync() {
optionService.setOption('lastSyncedPull', 0);
optionService.setOption('lastSyncedPush', 0);
log.info("Forcing full sync.");
@ -76,38 +76,38 @@ async function forceFullSync() {
syncService.sync();
}
async function forceNoteSync(req) {
function forceNoteSync(req) {
const noteId = req.params.noteId;
const now = dateUtils.utcNowDateTime();
await sql.execute(`UPDATE notes SET utcDateModified = ? WHERE noteId = ?`, [now, noteId]);
await syncTableService.addNoteSync(noteId);
sql.execute(`UPDATE notes SET utcDateModified = ? WHERE noteId = ?`, [now, noteId]);
syncTableService.addNoteSync(noteId);
await sql.execute(`UPDATE note_contents SET utcDateModified = ? WHERE noteId = ?`, [now, noteId]);
await syncTableService.addNoteContentSync(noteId);
sql.execute(`UPDATE note_contents SET utcDateModified = ? WHERE noteId = ?`, [now, noteId]);
syncTableService.addNoteContentSync(noteId);
for (const branchId of await sql.getColumn("SELECT branchId FROM branches WHERE noteId = ?", [noteId])) {
await sql.execute(`UPDATE branches SET utcDateModified = ? WHERE branchId = ?`, [now, branchId]);
for (const branchId of sql.getColumn("SELECT branchId FROM branches WHERE noteId = ?", [noteId])) {
sql.execute(`UPDATE branches SET utcDateModified = ? WHERE branchId = ?`, [now, branchId]);
await syncTableService.addBranchSync(branchId);
syncTableService.addBranchSync(branchId);
}
for (const attributeId of await sql.getColumn("SELECT attributeId FROM attributes WHERE noteId = ?", [noteId])) {
await sql.execute(`UPDATE attributes SET utcDateModified = ? WHERE attributeId = ?`, [now, attributeId]);
for (const attributeId of sql.getColumn("SELECT attributeId FROM attributes WHERE noteId = ?", [noteId])) {
sql.execute(`UPDATE attributes SET utcDateModified = ? WHERE attributeId = ?`, [now, attributeId]);
await syncTableService.addAttributeSync(attributeId);
syncTableService.addAttributeSync(attributeId);
}
for (const noteRevisionId of await sql.getColumn("SELECT noteRevisionId FROM note_revisions WHERE noteId = ?", [noteId])) {
await sql.execute(`UPDATE note_revisions SET utcDateModified = ? WHERE noteRevisionId = ?`, [now, noteRevisionId]);
await syncTableService.addNoteRevisionSync(noteRevisionId);
for (const noteRevisionId of sql.getColumn("SELECT noteRevisionId FROM note_revisions WHERE noteId = ?", [noteId])) {
sql.execute(`UPDATE note_revisions SET utcDateModified = ? WHERE noteRevisionId = ?`, [now, noteRevisionId]);
syncTableService.addNoteRevisionSync(noteRevisionId);
await sql.execute(`UPDATE note_revision_contents SET utcDateModified = ? WHERE noteRevisionId = ?`, [now, noteRevisionId]);
await syncTableService.addNoteRevisionContentSync(noteRevisionId);
sql.execute(`UPDATE note_revision_contents SET utcDateModified = ? WHERE noteRevisionId = ?`, [now, noteRevisionId]);
syncTableService.addNoteRevisionContentSync(noteRevisionId);
}
await syncTableService.addRecentNoteSync(noteId);
syncTableService.addRecentNoteSync(noteId);
log.info("Forcing note sync for " + noteId);
@ -115,16 +115,16 @@ async function forceNoteSync(req) {
syncService.sync();
}
async function getChanged(req) {
function getChanged(req) {
const startTime = Date.now();
const lastSyncId = parseInt(req.query.lastSyncId);
const syncs = await sql.getRows("SELECT * FROM sync WHERE isSynced = 1 AND id > ? LIMIT 1000", [lastSyncId]);
const syncs = sql.getRows("SELECT * FROM sync WHERE isSynced = 1 AND id > ? LIMIT 1000", [lastSyncId]);
const ret = {
syncs: await syncService.getSyncRecords(syncs),
maxSyncId: await sql.getValue('SELECT MAX(id) FROM sync WHERE isSynced = 1')
syncs: syncService.getSyncRecords(syncs),
maxSyncId: sql.getValue('SELECT MAX(id) FROM sync WHERE isSynced = 1')
};
if (ret.syncs.length > 0) {
@ -134,28 +134,28 @@ async function getChanged(req) {
return ret;
}
async function update(req) {
function update(req) {
const sourceId = req.body.sourceId;
const entities = req.body.entities;
for (const {sync, entity} of entities) {
await syncUpdateService.updateEntity(sync, entity, sourceId);
syncUpdateService.updateEntity(sync, entity, sourceId);
}
}
async function syncFinished() {
function syncFinished() {
// after first sync finishes, the application is ready to be used
// this is meaningless but at the same time harmless (idempotent) for further syncs
await sqlInit.dbInitialized();
sqlInit.dbInitialized();
}
async function queueSector(req) {
function queueSector(req) {
const entityName = utils.sanitizeSqlIdentifier(req.params.entityName);
const sector = utils.sanitizeSqlIdentifier(req.params.sector);
const entityPrimaryKey = entityConstructor.getEntityFromEntityName(entityName).primaryKeyName;
await syncTableService.addEntitySyncsForSector(entityName, entityPrimaryKey, sector);
syncTableService.addEntitySyncsForSector(entityName, entityPrimaryKey, sector);
}
module.exports = {

View File

@ -4,15 +4,15 @@ const sql = require('../../services/sql');
const optionService = require('../../services/options');
const treeService = require('../../services/tree');
async function getNotesAndBranchesAndAttributes(noteIds) {
function getNotesAndBranchesAndAttributes(noteIds) {
noteIds = Array.from(new Set(noteIds));
const notes = await treeService.getNotes(noteIds);
const notes = treeService.getNotes(noteIds);
noteIds = notes.map(note => note.noteId);
// joining child note to filter out not completely synchronised notes which would then cause errors later
// cannot do that with parent because of root note's 'none' parent
const branches = await sql.getManyRows(`
const branches = sql.getManyRows(`
SELECT
branches.branchId,
branches.noteId,
@ -28,7 +28,7 @@ async function getNotesAndBranchesAndAttributes(noteIds) {
// sorting in memory is faster
branches.sort((a, b) => a.notePosition - b.notePosition < 0 ? -1 : 1);
const attributes = await sql.getManyRows(`
const attributes = sql.getManyRows(`
SELECT
attributeId,
noteId,
@ -50,12 +50,12 @@ async function getNotesAndBranchesAndAttributes(noteIds) {
};
}
async function getTree() {
const hoistedNoteId = await optionService.getOption('hoistedNoteId');
function getTree() {
const hoistedNoteId = optionService.getOption('hoistedNoteId');
// we fetch all branches of notes, even if that particular branch isn't visible
// this allows us to e.g. detect and properly display clones
const noteIds = await sql.getColumn(`
const noteIds = sql.getColumn(`
WITH RECURSIVE
tree(branchId, noteId, isExpanded) AS (
SELECT branchId, noteId, isExpanded FROM branches WHERE noteId = ?
@ -68,11 +68,11 @@ async function getTree() {
noteIds.push('root');
return await getNotesAndBranchesAndAttributes(noteIds);
return getNotesAndBranchesAndAttributes(noteIds);
}
async function load(req) {
return await getNotesAndBranchesAndAttributes(req.body.noteIds);
function load(req) {
return getNotesAndBranchesAndAttributes(req.body.noteIds);
}
module.exports = {

View File

@ -6,11 +6,11 @@ const scriptService = require('../services/script');
function register(router) {
// explicitly no CSRF middleware since it's meant to allow integration from external services
router.all('/custom/:path*', async (req, res, next) => {
router.all('/custom/:path*', (req, res, next) => {
// express puts content after first slash into 0 index element
const path = req.params.path + req.params[0];
const attrs = await repository.getEntities("SELECT * FROM attributes WHERE isDeleted = 0 AND type = 'label' AND name IN ('customRequestHandler', 'customResourceProvider')");
const attrs = repository.getEntities("SELECT * FROM attributes WHERE isDeleted = 0 AND type = 'label' AND name IN ('customRequestHandler', 'customResourceProvider')");
for (const attr of attrs) {
const regex = new RegExp(attr.value);
@ -29,12 +29,12 @@ function register(router) {
}
if (attr.name === 'customRequestHandler') {
const note = await attr.getNote();
const note = attr.getNote();
log.info(`Handling custom request "${path}" with note ${note.noteId}`);
try {
await scriptService.executeNote(note, {
scriptService.executeNote(note, {
pathParams: match.slice(1),
req,
res
@ -47,7 +47,7 @@ function register(router) {
}
}
else if (attr.name === 'customResourceProvider') {
await fileUploadService.downloadNoteFile(attr.noteId, res);
fileUploadService.downloadNoteFile(attr.noteId, res);
}
else {
throw new Error("Unrecognized attribute name " + attr.name);

View File

@ -8,8 +8,8 @@ const optionService = require('../services/options');
const log = require('../services/log');
const env = require('../services/env');
async function index(req, res) {
const options = await optionService.getOptionsMap();
function index(req, res) {
const options = optionService.getOptionsMap();
let view = req.cookies['trilium-device'] === 'mobile' ? 'mobile' : 'desktop';
@ -22,17 +22,17 @@ async function index(req, res) {
mainFontSize: parseInt(options.mainFontSize),
treeFontSize: parseInt(options.treeFontSize),
detailFontSize: parseInt(options.detailFontSize),
sourceId: await sourceIdService.generateSourceId(),
maxSyncIdAtLoad: await sql.getValue("SELECT MAX(id) FROM sync"),
sourceId: sourceIdService.generateSourceId(),
maxSyncIdAtLoad: sql.getValue("SELECT MAX(id) FROM sync"),
instanceName: config.General ? config.General.instanceName : null,
appCssNoteIds: await getAppCssNoteIds(),
appCssNoteIds: getAppCssNoteIds(),
isDev: env.isDev(),
isMainWindow: !req.query.extra
});
}
async function getAppCssNoteIds() {
return (await attributeService.getNotesWithLabels(['appCss', 'appTheme']))
function getAppCssNoteIds() {
return (attributeService.getNotesWithLabels(['appCss', 'appTheme']))
.map(note => note.noteId);
}

View File

@ -8,12 +8,12 @@ function loginPage(req, res) {
res.render('login', { failedAuth: false });
}
async function login(req, res) {
const userName = await optionService.getOption('username');
function login(req, res) {
const userName = optionService.getOption('username');
const guessedPassword = req.body.password;
if (req.body.username === userName && await verifyPassword(guessedPassword)) {
if (req.body.username === userName && verifyPassword(guessedPassword)) {
const rememberMe = req.body.remember_me;
req.session.regenerate(() => {
@ -32,10 +32,10 @@ async function login(req, res) {
}
}
async function verifyPassword(guessedPassword) {
const hashed_password = utils.fromBase64(await optionService.getOption('passwordVerificationHash'));
function verifyPassword(guessedPassword) {
const hashed_password = utils.fromBase64(optionService.getOption('passwordVerificationHash'));
const guess_hashed = await myScryptService.getVerificationHash(guessedPassword);
const guess_hashed = myScryptService.getVerificationHash(guessedPassword);
return guess_hashed.equals(hashed_password);
}

View File

@ -79,23 +79,23 @@ function apiRoute(method, path, routeHandler) {
}
function route(method, path, middleware, routeHandler, resultHandler, transactional = true) {
router[method](path, ...middleware, async (req, res, next) => {
router[method](path, ...middleware, (req, res, next) => {
try {
cls.namespace.bindEmitter(req);
cls.namespace.bindEmitter(res);
const result = await cls.init(async () => {
const result = cls.init(() => {
cls.set('sourceId', req.headers['trilium-source-id']);
cls.set('localNowDateTime', req.headers['`trilium-local-now-datetime`']);
protectedSessionService.setProtectedSessionId(req);
if (transactional) {
return await sql.transactional(async () => {
return await routeHandler(req, res, next);
return sql.transactional(() => {
return routeHandler(req, res, next);
});
}
else {
return await routeHandler(req, res, next);
return routeHandler(req, res, next);
}
});

View File

@ -4,11 +4,11 @@ const sqlInit = require('../services/sql_init');
const setupService = require('../services/setup');
const utils = require('../services/utils');
async function setupPage(req, res) {
if (await sqlInit.isDbInitialized()) {
function setupPage(req, res) {
if (sqlInit.isDbInitialized()) {
if (utils.isElectron()) {
const windowService = require('../services/window');
await windowService.createMainWindow();
windowService.createMainWindow();
windowService.closeSetupWindow();
}
else {
@ -18,7 +18,7 @@ async function setupPage(req, res) {
// we got here because DB is not completely initialized so if schema exists
// it means we're in sync in progress state.
const syncInProgress = await sqlInit.schemaExists();
const syncInProgress = sqlInit.schemaExists();
if (syncInProgress) {
// trigger sync if it's not already running

View File

@ -41,7 +41,7 @@ const BUILTIN_ATTRIBUTES = [
{ type: 'relation', name: 'renderNote', isDangerous: true }
];
async function getNotesWithLabel(name, value) {
function getNotesWithLabel(name, value) {
let valueCondition = "";
let params = [name];
@ -50,25 +50,25 @@ async function getNotesWithLabel(name, value) {
params.push(value);
}
return await repository.getEntities(`SELECT notes.* FROM notes JOIN attributes USING(noteId)
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);
}
async function getNotesWithLabels(names) {
function getNotesWithLabels(names) {
const questionMarks = names.map(() => "?").join(", ");
return await repository.getEntities(`SELECT notes.* FROM notes JOIN attributes USING(noteId)
return repository.getEntities(`SELECT notes.* FROM notes JOIN attributes USING(noteId)
WHERE notes.isDeleted = 0 AND attributes.isDeleted = 0 AND attributes.name IN (${questionMarks}) ORDER BY position`, names);
}
async function getNoteWithLabel(name, value) {
const notes = await getNotesWithLabel(name, value);
function getNoteWithLabel(name, value) {
const notes = getNotesWithLabel(name, value);
return notes.length > 0 ? notes[0] : null;
}
async function createLabel(noteId, name, value = "") {
return await createAttribute({
function createLabel(noteId, name, value = "") {
return createAttribute({
noteId: noteId,
type: 'label',
name: name,
@ -76,8 +76,8 @@ async function createLabel(noteId, name, value = "") {
});
}
async function createRelation(noteId, name, targetNoteId) {
return await createAttribute({
function createRelation(noteId, name, targetNoteId) {
return createAttribute({
noteId: noteId,
type: 'relation',
name: name,
@ -85,14 +85,14 @@ async function createRelation(noteId, name, targetNoteId) {
});
}
async function createAttribute(attribute) {
return await new Attribute(attribute).save();
function createAttribute(attribute) {
return new Attribute(attribute).save();
}
async function getAttributeNames(type, nameLike) {
function getAttributeNames(type, nameLike) {
nameLike = nameLike.toLowerCase();
const names = await sql.getColumn(
const names = sql.getColumn(
`SELECT DISTINCT name
FROM attributes
WHERE isDeleted = 0

View File

@ -7,8 +7,8 @@ const utils = require('./utils');
const passwordEncryptionService = require('./password_encryption');
const optionService = require('./options');
async function checkAuth(req, res, next) {
if (!await sqlInit.isDbInitialized()) {
function checkAuth(req, res, next) {
if (!sqlInit.isDbInitialized()) {
res.redirect("setup");
}
else if (!req.session.loggedIn && !utils.isElectron()) {
@ -21,7 +21,7 @@ async function checkAuth(req, res, next) {
// for electron things which need network stuff
// currently we're doing that for file upload because handling form data seems to be difficult
async function checkApiAuthOrElectron(req, res, next) {
function checkApiAuthOrElectron(req, res, next) {
if (!req.session.loggedIn && !utils.isElectron()) {
reject(req, res, "Not authorized");
}
@ -30,7 +30,7 @@ async function checkApiAuthOrElectron(req, res, next) {
}
}
async function checkApiAuth(req, res, next) {
function checkApiAuth(req, res, next) {
if (!req.session.loggedIn) {
reject(req, res, "Not authorized");
}
@ -39,8 +39,8 @@ async function checkApiAuth(req, res, next) {
}
}
async function checkAppInitialized(req, res, next) {
if (!await sqlInit.isDbInitialized()) {
function checkAppInitialized(req, res, next) {
if (!sqlInit.isDbInitialized()) {
res.redirect("setup");
}
else {
@ -48,8 +48,8 @@ async function checkAppInitialized(req, res, next) {
}
}
async function checkAppNotInitialized(req, res, next) {
if (await sqlInit.isDbInitialized()) {
function checkAppNotInitialized(req, res, next) {
if (sqlInit.isDbInitialized()) {
reject(req, res, "App already initialized.");
}
else {
@ -57,10 +57,10 @@ async function checkAppNotInitialized(req, res, next) {
}
}
async function checkToken(req, res, next) {
function checkToken(req, res, next) {
const token = req.headers.authorization;
if (await sql.getValue("SELECT COUNT(*) FROM api_tokens WHERE isDeleted = 0 AND token = ?", [token]) === 0) {
if (sql.getValue("SELECT COUNT(*) FROM api_tokens WHERE isDeleted = 0 AND token = ?", [token]) === 0) {
reject(req, res, "Not authorized");
}
else {
@ -74,15 +74,15 @@ function reject(req, res, message) {
res.status(401).send(message);
}
async function checkBasicAuth(req, res, next) {
function checkBasicAuth(req, res, next) {
const header = req.headers.authorization || '';
const token = header.split(/\s+/).pop() || '';
const auth = new Buffer.from(token, 'base64').toString();
const [username, password] = auth.split(/:/);
const dbUsername = await optionService.getOption('username');
const dbUsername = optionService.getOption('username');
if (dbUsername !== username || !await passwordEncryptionService.verifyPassword(password)) {
if (dbUsername !== username || !passwordEncryptionService.verifyPassword(password)) {
res.status(401).send('Incorrect username and/or password');
}
else {

View File

@ -111,8 +111,8 @@ function BackendScriptApi(currentNote, apiParams) {
* @param {string} searchString
* @returns {Promise<Note|null>}
*/
this.searchForNote = async searchString => {
const notes = await searchService.searchForNotes(searchString);
this.searchForNote = searchString => {
const notes = searchService.searchForNotes(searchString);
return notes.length > 0 ? notes[0] : null;
};
@ -185,7 +185,7 @@ function BackendScriptApi(currentNote, apiParams) {
* @param {string} content
* @return {Promise<{note: Note, branch: Branch}>}
*/
this.createTextNote = async (parentNoteId, title, content = '') => await noteService.createNewNote({
this.createTextNote = (parentNoteId, title, content = '') => noteService.createNewNote({
parentNoteId,
title,
content,
@ -201,7 +201,7 @@ function BackendScriptApi(currentNote, apiParams) {
* @param {object} content
* @return {Promise<{note: Note, branch: Branch}>}
*/
this.createDataNote = async (parentNoteId, title, content = {}) => await noteService.createNewNote({
this.createDataNote = (parentNoteId, title, content = {}) => noteService.createNewNote({
parentNoteId,
title,
content: JSON.stringify(content, null, '\t'),
@ -256,11 +256,11 @@ function BackendScriptApi(currentNote, apiParams) {
* @param {CreateNoteExtraOptions} [extraOptions={}]
* @returns {Promise<{note: Note, branch: Branch}>} object contains newly created entities note and branch
*/
this.createNote = async (parentNoteId, title, content = "", extraOptions= {}) => {
this.createNote = (parentNoteId, title, content = "", extraOptions= {}) => {
extraOptions.parentNoteId = parentNoteId;
extraOptions.title = title;
const parentNote = await repository.getNote(parentNoteId);
const parentNote = repository.getNote(parentNoteId);
// code note type can be inherited, otherwise text is default
extraOptions.type = parentNote.type === 'code' ? 'code' : 'text';
@ -275,10 +275,10 @@ function BackendScriptApi(currentNote, apiParams) {
extraOptions.content = content;
}
const {note, branch} = await noteService.createNewNote(extraOptions);
const {note, branch} = noteService.createNewNote(extraOptions);
for (const attr of extraOptions.attributes || []) {
await attributeService.createAttribute({
attributeService.createAttribute({
noteId: note.noteId,
type: attr.type,
name: attr.name,

View File

@ -11,28 +11,28 @@ const attributeService = require('./attributes');
const cls = require('./cls');
const utils = require('./utils');
async function regularBackup() {
await periodBackup('lastDailyBackupDate', 'daily', 24 * 3600);
function regularBackup() {
periodBackup('lastDailyBackupDate', 'daily', 24 * 3600);
await periodBackup('lastWeeklyBackupDate', 'weekly', 7 * 24 * 3600);
periodBackup('lastWeeklyBackupDate', 'weekly', 7 * 24 * 3600);
await periodBackup('lastMonthlyBackupDate', 'monthly', 30 * 24 * 3600);
periodBackup('lastMonthlyBackupDate', 'monthly', 30 * 24 * 3600);
}
async function periodBackup(optionName, fileName, periodInSeconds) {
function periodBackup(optionName, fileName, periodInSeconds) {
const now = new Date();
const lastDailyBackupDate = dateUtils.parseDateTime(await optionService.getOption(optionName));
const lastDailyBackupDate = dateUtils.parseDateTime(optionService.getOption(optionName));
if (now.getTime() - lastDailyBackupDate.getTime() > periodInSeconds * 1000) {
await backupNow(fileName);
backupNow(fileName);
await optionService.setOption(optionName, dateUtils.utcNowDateTime());
optionService.setOption(optionName, dateUtils.utcNowDateTime());
}
}
const COPY_ATTEMPT_COUNT = 50;
async function copyFile(backupFile) {
function copyFile(backupFile) {
const sql = require('./sql');
try {
@ -45,7 +45,7 @@ async function copyFile(backupFile) {
for (; attemptCount < COPY_ATTEMPT_COUNT && !success; attemptCount++) {
try {
await sql.executeWithoutTransaction(`VACUUM INTO '${backupFile}'`);
sql.executeWithoutTransaction(`VACUUM INTO '${backupFile}'`);
success = true;
} catch (e) {
@ -60,10 +60,10 @@ async function copyFile(backupFile) {
async function backupNow(name) {
// we don't want to backup DB in the middle of sync with potentially inconsistent DB state
return await syncMutexService.doExclusively(async () => {
return await syncMutexService.doExclusively(() => {
const backupFile = `${dataDir.BACKUP_DIR}/backup-${name}.db`;
const success = await copyFile(backupFile);
const success = copyFile(backupFile);
if (success) {
log.info("Created backup at " + backupFile);
@ -76,45 +76,45 @@ async function backupNow(name) {
});
}
async function anonymize() {
function anonymize() {
if (!fs.existsSync(dataDir.ANONYMIZED_DB_DIR)) {
fs.mkdirSync(dataDir.ANONYMIZED_DB_DIR, 0o700);
}
const anonymizedFile = dataDir.ANONYMIZED_DB_DIR + "/" + "anonymized-" + dateUtils.getDateTimeForFile() + ".db";
const success = await copyFile(anonymizedFile);
const success = copyFile(anonymizedFile);
if (!success) {
return { success: false };
}
const db = await sqlite.open({
const db = sqlite.open({
filename: anonymizedFile,
driver: sqlite3.Database
});
await db.run("UPDATE api_tokens SET token = 'API token value'");
await db.run("UPDATE notes SET title = 'title'");
await db.run("UPDATE note_contents SET content = 'text' WHERE content IS NOT NULL");
await db.run("UPDATE note_revisions SET title = 'title'");
await db.run("UPDATE note_revision_contents SET content = 'text' WHERE content IS NOT NULL");
db.run("UPDATE api_tokens SET token = 'API token value'");
db.run("UPDATE notes SET title = 'title'");
db.run("UPDATE note_contents SET content = 'text' WHERE content IS NOT NULL");
db.run("UPDATE note_revisions SET title = 'title'");
db.run("UPDATE note_revision_contents SET content = 'text' WHERE content IS NOT NULL");
// we want to delete all non-builtin attributes because they can contain sensitive names and values
// on the other hand builtin/system attrs should not contain any sensitive info
const builtinAttrs = attributeService.getBuiltinAttributeNames().map(name => "'" + utils.sanitizeSql(name) + "'").join(', ');
await db.run(`UPDATE attributes SET name = 'name', value = 'value' WHERE type = 'label' AND name NOT IN(${builtinAttrs})`);
await db.run(`UPDATE attributes SET name = 'name' WHERE type = 'relation' AND name NOT IN (${builtinAttrs})`);
await db.run("UPDATE branches SET prefix = 'prefix' WHERE prefix IS NOT NULL");
await db.run(`UPDATE options SET value = 'anonymized' WHERE name IN
db.run(`UPDATE attributes SET name = 'name', value = 'value' WHERE type = 'label' AND name NOT IN(${builtinAttrs})`);
db.run(`UPDATE attributes SET name = 'name' WHERE type = 'relation' AND name NOT IN (${builtinAttrs})`);
db.run("UPDATE branches SET prefix = 'prefix' WHERE prefix IS NOT NULL");
db.run(`UPDATE options SET value = 'anonymized' WHERE name IN
('documentId', 'documentSecret', 'encryptedDataKey',
'passwordVerificationHash', 'passwordVerificationSalt',
'passwordDerivedKeySalt', 'username', 'syncServerHost', 'syncProxy')
AND value != ''`);
await db.run("VACUUM");
db.run("VACUUM");
await db.close();
db.close();
return {
success: true,
@ -126,12 +126,10 @@ if (!fs.existsSync(dataDir.BACKUP_DIR)) {
fs.mkdirSync(dataDir.BACKUP_DIR, 0o700);
}
sqlInit.dbReady.then(() => {
setInterval(cls.wrap(regularBackup), 4 * 60 * 60 * 1000);
setInterval(cls.wrap(regularBackup), 4 * 60 * 60 * 1000);
// kickoff first backup soon after start up
setTimeout(cls.wrap(regularBackup), 5 * 60 * 1000);
});
// kickoff first backup soon after start up
setTimeout(cls.wrap(regularBackup), 5 * 60 * 1000);
module.exports = {
backupNow,

View File

@ -6,21 +6,21 @@ const myScryptService = require('./my_scrypt');
const utils = require('./utils');
const passwordEncryptionService = require('./password_encryption');
async function changePassword(currentPassword, newPassword) {
if (!await passwordEncryptionService.verifyPassword(currentPassword)) {
function changePassword(currentPassword, newPassword) {
if (!passwordEncryptionService.verifyPassword(currentPassword)) {
return {
success: false,
message: "Given current password doesn't match hash"
};
}
const newPasswordVerificationKey = utils.toBase64(await myScryptService.getVerificationHash(newPassword));
const decryptedDataKey = await passwordEncryptionService.getDataKey(currentPassword);
const newPasswordVerificationKey = utils.toBase64(myScryptService.getVerificationHash(newPassword));
const decryptedDataKey = passwordEncryptionService.getDataKey(currentPassword);
await sql.transactional(async () => {
await passwordEncryptionService.setDataKey(newPassword, decryptedDataKey);
sql.transactional(() => {
passwordEncryptionService.setDataKey(newPassword, decryptedDataKey);
await optionService.setOption('passwordVerificationHash', newPasswordVerificationKey);
optionService.setOption('passwordVerificationHash', newPasswordVerificationKey);
});
return {

View File

@ -9,20 +9,20 @@ const Branch = require('../entities/branch');
const TaskContext = require("./task_context.js");
const utils = require('./utils');
async function cloneNoteToParent(noteId, parentBranchId, prefix) {
const parentBranch = await repository.getBranch(parentBranchId);
function cloneNoteToParent(noteId, parentBranchId, prefix) {
const parentBranch = repository.getBranch(parentBranchId);
if (await isNoteDeleted(noteId) || await isNoteDeleted(parentBranch.noteId)) {
if (isNoteDeleted(noteId) || isNoteDeleted(parentBranch.noteId)) {
return { success: false, message: 'Note is deleted.' };
}
const validationResult = await treeService.validateParentChild(parentBranch.noteId, noteId);
const validationResult = treeService.validateParentChild(parentBranch.noteId, noteId);
if (!validationResult.success) {
return validationResult;
}
const branch = await new Branch({
const branch = new Branch({
noteId: noteId,
parentNoteId: parentBranch.noteId,
prefix: prefix,
@ -30,23 +30,23 @@ async function cloneNoteToParent(noteId, parentBranchId, prefix) {
}).save();
parentBranch.isExpanded = true; // the new target should be expanded so it immediately shows up to the user
await parentBranch.save();
parentBranch.save();
return { success: true, branchId: branch.branchId };
}
async function ensureNoteIsPresentInParent(noteId, parentNoteId, prefix) {
if (await isNoteDeleted(noteId) || await isNoteDeleted(parentNoteId)) {
function ensureNoteIsPresentInParent(noteId, parentNoteId, prefix) {
if (isNoteDeleted(noteId) || isNoteDeleted(parentNoteId)) {
return { success: false, message: 'Note is deleted.' };
}
const validationResult = await treeService.validateParentChild(parentNoteId, noteId);
const validationResult = treeService.validateParentChild(parentNoteId, noteId);
if (!validationResult.success) {
return validationResult;
}
await new Branch({
new Branch({
noteId: noteId,
parentNoteId: parentNoteId,
prefix: prefix,
@ -54,32 +54,32 @@ async function ensureNoteIsPresentInParent(noteId, parentNoteId, prefix) {
}).save();
}
async function ensureNoteIsAbsentFromParent(noteId, parentNoteId) {
const branch = await repository.getEntity(`SELECT * FROM branches WHERE noteId = ? AND parentNoteId = ? AND isDeleted = 0`, [noteId, parentNoteId]);
function ensureNoteIsAbsentFromParent(noteId, parentNoteId) {
const branch = repository.getEntity(`SELECT * FROM branches WHERE noteId = ? AND parentNoteId = ? AND isDeleted = 0`, [noteId, parentNoteId]);
if (branch) {
const deleteId = utils.randomString(10);
await noteService.deleteBranch(branch, deleteId, new TaskContext());
noteService.deleteBranch(branch, deleteId, new TaskContext());
}
}
async function toggleNoteInParent(present, noteId, parentNoteId, prefix) {
function toggleNoteInParent(present, noteId, parentNoteId, prefix) {
if (present) {
await ensureNoteIsPresentInParent(noteId, parentNoteId, prefix);
ensureNoteIsPresentInParent(noteId, parentNoteId, prefix);
}
else {
await ensureNoteIsAbsentFromParent(noteId, parentNoteId);
ensureNoteIsAbsentFromParent(noteId, parentNoteId);
}
}
async function cloneNoteAfter(noteId, afterBranchId) {
const afterNote = await repository.getBranch(afterBranchId);
function cloneNoteAfter(noteId, afterBranchId) {
const afterNote = repository.getBranch(afterBranchId);
if (await isNoteDeleted(noteId) || await isNoteDeleted(afterNote.parentNoteId)) {
if (isNoteDeleted(noteId) || isNoteDeleted(afterNote.parentNoteId)) {
return { success: false, message: 'Note is deleted.' };
}
const validationResult = await treeService.validateParentChild(afterNote.parentNoteId, noteId);
const validationResult = treeService.validateParentChild(afterNote.parentNoteId, noteId);
if (!validationResult.success) {
return validationResult;
@ -87,12 +87,12 @@ async function cloneNoteAfter(noteId, afterBranchId) {
// we don't change utcDateModified so other changes are prioritized in case of conflict
// also we would have to sync all those modified branches otherwise hash checks would fail
await sql.execute("UPDATE branches SET notePosition = notePosition + 10 WHERE parentNoteId = ? AND notePosition > ? AND isDeleted = 0",
sql.execute("UPDATE branches SET notePosition = notePosition + 10 WHERE parentNoteId = ? AND notePosition > ? AND isDeleted = 0",
[afterNote.parentNoteId, afterNote.notePosition]);
await syncTable.addNoteReorderingSync(afterNote.parentNoteId);
syncTable.addNoteReorderingSync(afterNote.parentNoteId);
const branch = await new Branch({
const branch = new Branch({
noteId: noteId,
parentNoteId: afterNote.parentNoteId,
notePosition: afterNote.notePosition + 10,
@ -102,8 +102,8 @@ async function cloneNoteAfter(noteId, afterBranchId) {
return { success: true, branchId: branch.branchId };
}
async function isNoteDeleted(noteId) {
const note = await repository.getNote(noteId);
function isNoteDeleted(noteId) {
const note = repository.getNote(noteId);
return note.isDeleted;
}

View File

@ -1,12 +1,12 @@
const clsHooked = require('cls-hooked');
const namespace = clsHooked.createNamespace("trilium");
async function init(callback) {
return await namespace.runAndReturn(callback);
function init(callback) {
return namespace.runAndReturn(callback);
}
function wrap(callback) {
return async () => await init(callback);
return () => init(callback);
}
function get(key) {

View File

@ -19,12 +19,12 @@ class ConsistencyChecks {
this.fixedIssues = false;
}
async findAndFixIssues(query, fixerCb) {
const results = await sql.getRows(query);
findAndFixIssues(query, fixerCb) {
const results = sql.getRows(query);
for (const res of results) {
try {
await fixerCb(res);
fixerCb(res);
if (this.autoFix) {
this.fixedIssues = true;
@ -40,9 +40,9 @@ class ConsistencyChecks {
return results;
}
async checkTreeCycles() {
checkTreeCycles() {
const childToParents = {};
const rows = await sql.getRows("SELECT noteId, parentNoteId FROM branches WHERE isDeleted = 0");
const rows = sql.getRows("SELECT noteId, parentNoteId FROM branches WHERE isDeleted = 0");
for (const row of rows) {
const childNoteId = row.noteId;
@ -90,18 +90,18 @@ class ConsistencyChecks {
}
}
async findBrokenReferenceIssues() {
await this.findAndFixIssues(`
findBrokenReferenceIssues() {
this.findAndFixIssues(`
SELECT branchId, branches.noteId
FROM branches
LEFT JOIN notes USING (noteId)
WHERE branches.isDeleted = 0
AND notes.noteId IS NULL`,
async ({branchId, noteId}) => {
({branchId, noteId}) => {
if (this.autoFix) {
const branch = await repository.getBranch(branchId);
const branch = repository.getBranch(branchId);
branch.isDeleted = true;
await branch.save();
branch.save();
logFix(`Branch ${branchId} has been deleted since it references missing note ${noteId}`);
} else {
@ -109,18 +109,18 @@ class ConsistencyChecks {
}
});
await this.findAndFixIssues(`
this.findAndFixIssues(`
SELECT branchId, branches.noteId AS parentNoteId
FROM branches
LEFT JOIN notes ON notes.noteId = branches.parentNoteId
WHERE branches.isDeleted = 0
AND branches.branchId != 'root'
AND notes.noteId IS NULL`,
async ({branchId, parentNoteId}) => {
({branchId, parentNoteId}) => {
if (this.autoFix) {
const branch = await repository.getBranch(branchId);
const branch = repository.getBranch(branchId);
branch.parentNoteId = 'root';
await branch.save();
branch.save();
logFix(`Branch ${branchId} was set to root parent since it was referencing missing parent note ${parentNoteId}`);
} else {
@ -128,17 +128,17 @@ class ConsistencyChecks {
}
});
await this.findAndFixIssues(`
this.findAndFixIssues(`
SELECT attributeId, attributes.noteId
FROM attributes
LEFT JOIN notes USING (noteId)
WHERE attributes.isDeleted = 0
AND notes.noteId IS NULL`,
async ({attributeId, noteId}) => {
({attributeId, noteId}) => {
if (this.autoFix) {
const attribute = await repository.getAttribute(attributeId);
const attribute = repository.getAttribute(attributeId);
attribute.isDeleted = true;
await attribute.save();
attribute.save();
logFix(`Attribute ${attributeId} has been deleted since it references missing source note ${noteId}`);
} else {
@ -146,18 +146,18 @@ class ConsistencyChecks {
}
});
await this.findAndFixIssues(`
this.findAndFixIssues(`
SELECT attributeId, attributes.value AS noteId
FROM attributes
LEFT JOIN notes ON notes.noteId = attributes.value
WHERE attributes.isDeleted = 0
AND attributes.type = 'relation'
AND notes.noteId IS NULL`,
async ({attributeId, noteId}) => {
({attributeId, noteId}) => {
if (this.autoFix) {
const attribute = await repository.getAttribute(attributeId);
const attribute = repository.getAttribute(attributeId);
attribute.isDeleted = true;
await attribute.save();
attribute.save();
logFix(`Relation ${attributeId} has been deleted since it references missing note ${noteId}`)
} else {
@ -166,24 +166,24 @@ class ConsistencyChecks {
});
}
async findExistencyIssues() {
findExistencyIssues() {
// principle for fixing inconsistencies is that if the note itself is deleted (isDeleted=true) then all related entities should be also deleted (branches, attributes)
// but if note is not deleted, then at least one branch should exist.
// the order here is important - first we might need to delete inconsistent branches and after that
// another check might create missing branch
await this.findAndFixIssues(`
this.findAndFixIssues(`
SELECT branchId,
noteId
FROM branches
JOIN notes USING (noteId)
WHERE notes.isDeleted = 1
AND branches.isDeleted = 0`,
async ({branchId, noteId}) => {
({branchId, noteId}) => {
if (this.autoFix) {
const branch = await repository.getBranch(branchId);
const branch = repository.getBranch(branchId);
branch.isDeleted = true;
await branch.save();
branch.save();
logFix(`Branch ${branchId} has been deleted since associated note ${noteId} is deleted.`);
} else {
@ -191,18 +191,18 @@ class ConsistencyChecks {
}
});
await this.findAndFixIssues(`
this.findAndFixIssues(`
SELECT branchId,
parentNoteId
FROM branches
JOIN notes AS parentNote ON parentNote.noteId = branches.parentNoteId
WHERE parentNote.isDeleted = 1
AND branches.isDeleted = 0
`, async ({branchId, parentNoteId}) => {
`, ({branchId, parentNoteId}) => {
if (this.autoFix) {
const branch = await repository.getBranch(branchId);
const branch = repository.getBranch(branchId);
branch.isDeleted = true;
await branch.save();
branch.save();
logFix(`Branch ${branchId} has been deleted since associated parent note ${parentNoteId} is deleted.`);
} else {
@ -210,15 +210,15 @@ class ConsistencyChecks {
}
});
await this.findAndFixIssues(`
this.findAndFixIssues(`
SELECT DISTINCT notes.noteId
FROM notes
LEFT JOIN branches ON notes.noteId = branches.noteId AND branches.isDeleted = 0
WHERE notes.isDeleted = 0
AND branches.branchId IS NULL
`, async ({noteId}) => {
`, ({noteId}) => {
if (this.autoFix) {
const branch = await new Branch({
const branch = new Branch({
parentNoteId: 'root',
noteId: noteId,
prefix: 'recovered'
@ -231,7 +231,7 @@ class ConsistencyChecks {
});
// there should be a unique relationship between note and its parent
await this.findAndFixIssues(`
this.findAndFixIssues(`
SELECT noteId,
parentNoteId
FROM branches
@ -239,9 +239,9 @@ class ConsistencyChecks {
GROUP BY branches.parentNoteId,
branches.noteId
HAVING COUNT(1) > 1`,
async ({noteId, parentNoteId}) => {
({noteId, parentNoteId}) => {
if (this.autoFix) {
const branches = await repository.getEntities(
const branches = repository.getEntities(
`SELECT *
FROM branches
WHERE noteId = ?
@ -254,7 +254,7 @@ class ConsistencyChecks {
// delete all but the first branch
for (const branch of branches.slice(1)) {
branch.isDeleted = true;
await branch.save();
branch.save();
logFix(`Removing branch ${branch.branchId} since it's parent-child duplicate of branch ${origBranch.branchId}`);
}
@ -264,17 +264,17 @@ class ConsistencyChecks {
});
}
async findLogicIssues() {
await this.findAndFixIssues(`
findLogicIssues() {
this.findAndFixIssues(`
SELECT noteId, type
FROM notes
WHERE isDeleted = 0
AND type NOT IN ('text', 'code', 'render', 'file', 'image', 'search', 'relation-map', 'book')`,
async ({noteId, type}) => {
({noteId, type}) => {
if (this.autoFix) {
const note = await repository.getNote(noteId);
const note = repository.getNote(noteId);
note.type = 'file'; // file is a safe option to recover notes if type is not known
await note.save();
note.save();
logFix(`Note ${noteId} type has been change to file since it had invalid type=${type}`)
} else {
@ -282,29 +282,29 @@ class ConsistencyChecks {
}
});
await this.findAndFixIssues(`
this.findAndFixIssues(`
SELECT notes.noteId
FROM notes
LEFT JOIN note_contents USING (noteId)
WHERE note_contents.noteId IS NULL`,
async ({noteId}) => {
({noteId}) => {
if (this.autoFix) {
const note = await repository.getNote(noteId);
const note = repository.getNote(noteId);
if (note.isProtected) {
// this is wrong for non-erased notes but we cannot set a valid value for protected notes
await sql.upsert("note_contents", "noteId", {
sql.upsert("note_contents", "noteId", {
noteId: noteId,
content: null,
hash: "consistency_checks",
utcDateModified: dateUtils.utcNowDateTime()
});
await syncTableService.addNoteContentSync(noteId);
syncTableService.addNoteContentSync(noteId);
}
else {
// empty string might be wrong choice for some note types but it's a best guess
await note.setContent(note.isErased ? null : '');
note.setContent(note.isErased ? null : '');
}
logFix(`Note ${noteId} content was set to empty string since there was no corresponding row`);
@ -313,18 +313,18 @@ class ConsistencyChecks {
}
});
await this.findAndFixIssues(`
this.findAndFixIssues(`
SELECT noteId
FROM notes
JOIN note_contents USING (noteId)
WHERE isDeleted = 0
AND isProtected = 0
AND content IS NULL`,
async ({noteId}) => {
({noteId}) => {
if (this.autoFix) {
const note = await repository.getNote(noteId);
const note = repository.getNote(noteId);
// empty string might be wrong choice for some note types but it's a best guess
await note.setContent('');
note.setContent('');
logFix(`Note ${noteId} content was set to empty string since it was null even though it is not deleted`);
} else {
@ -332,13 +332,13 @@ class ConsistencyChecks {
}
});
await this.findAndFixIssues(`
this.findAndFixIssues(`
SELECT noteId
FROM notes
JOIN note_contents USING (noteId)
WHERE isErased = 1
AND content IS NOT NULL`,
async ({noteId}) => {
({noteId}) => {
// we always fix this issue because there does not seem to be a good way to prevent it.
// Scenario in which this can happen:
@ -355,23 +355,23 @@ class ConsistencyChecks {
//
// So instead we just fix such cases afterwards here.
await sql.execute(`UPDATE note_contents SET content = NULL WHERE noteId = ?`, [noteId]);
sql.execute(`UPDATE note_contents SET content = NULL WHERE noteId = ?`, [noteId]);
logFix(`Note ${noteId} content has been set to null since the note is erased`);
});
await this.findAndFixIssues(`
this.findAndFixIssues(`
SELECT noteId, noteRevisionId
FROM notes
JOIN note_revisions USING (noteId)
WHERE notes.isErased = 1
AND note_revisions.isErased = 0`,
async ({noteId, noteRevisionId}) => {
({noteId, noteRevisionId}) => {
if (this.autoFix) {
const noteRevision = await repository.getNoteRevision(noteRevisionId);
const noteRevision = repository.getNoteRevision(noteRevisionId);
noteRevision.isErased = true;
await noteRevision.setContent(null);
await noteRevision.save();
noteRevision.setContent(null);
noteRevision.save();
logFix(`Note revision ${noteRevisionId} has been erased since its note ${noteId} is also erased.`);
} else {
@ -379,18 +379,18 @@ class ConsistencyChecks {
}
});
await this.findAndFixIssues(`
this.findAndFixIssues(`
SELECT note_revisions.noteRevisionId
FROM note_revisions
LEFT JOIN note_revision_contents USING (noteRevisionId)
WHERE note_revision_contents.noteRevisionId IS NULL
AND note_revisions.isProtected = 0`,
async ({noteRevisionId}) => {
({noteRevisionId}) => {
if (this.autoFix) {
const noteRevision = await repository.getNoteRevision(noteRevisionId);
await noteRevision.setContent(null);
const noteRevision = repository.getNoteRevision(noteRevisionId);
noteRevision.setContent(null);
noteRevision.isErased = true;
await noteRevision.save();
noteRevision.save();
logFix(`Note revision content ${noteRevisionId} was created and set to erased since it did not exist.`);
} else {
@ -398,17 +398,17 @@ class ConsistencyChecks {
}
});
await this.findAndFixIssues(`
this.findAndFixIssues(`
SELECT noteRevisionId
FROM note_revisions
JOIN note_revision_contents USING (noteRevisionId)
WHERE isErased = 0
AND content IS NULL`,
async ({noteRevisionId}) => {
({noteRevisionId}) => {
if (this.autoFix) {
const noteRevision = await repository.getNoteRevision(noteRevisionId);
const noteRevision = repository.getNoteRevision(noteRevisionId);
noteRevision.isErased = true;
await noteRevision.save();
noteRevision.save();
logFix(`Note revision ${noteRevisionId} content was set to empty string since it was null even though it is not erased`);
} else {
@ -416,15 +416,15 @@ class ConsistencyChecks {
}
});
await this.findAndFixIssues(`
this.findAndFixIssues(`
SELECT noteRevisionId
FROM note_revisions
JOIN note_revision_contents USING (noteRevisionId)
WHERE isErased = 1
AND content IS NOT NULL`,
async ({noteRevisionId}) => {
({noteRevisionId}) => {
if (this.autoFix) {
await sql.execute(`UPDATE note_revision_contents SET content = NULL WHERE noteRevisionId = ?`, [noteRevisionId]);
sql.execute(`UPDATE note_revision_contents SET content = NULL WHERE noteRevisionId = ?`, [noteRevisionId]);
logFix(`Note revision ${noteRevisionId} content was set to null since the note revision is erased`);
}
@ -433,16 +433,16 @@ class ConsistencyChecks {
}
});
await this.findAndFixIssues(`
this.findAndFixIssues(`
SELECT noteId
FROM notes
WHERE isErased = 1
AND isDeleted = 0`,
async ({noteId}) => {
({noteId}) => {
if (this.autoFix) {
const note = await repository.getNote(noteId);
const note = repository.getNote(noteId);
note.isDeleted = true;
await note.save();
note.save();
logFix(`Note ${noteId} was set to deleted since it is erased`);
}
@ -451,23 +451,23 @@ class ConsistencyChecks {
}
});
await this.findAndFixIssues(`
this.findAndFixIssues(`
SELECT parentNoteId
FROM branches
JOIN notes ON notes.noteId = branches.parentNoteId
WHERE notes.isDeleted = 0
AND notes.type == 'search'
AND branches.isDeleted = 0`,
async ({parentNoteId}) => {
({parentNoteId}) => {
if (this.autoFix) {
const branches = await repository.getEntities(`SELECT *
const branches = repository.getEntities(`SELECT *
FROM branches
WHERE isDeleted = 0
AND parentNoteId = ?`, [parentNoteId]);
for (const branch of branches) {
branch.parentNoteId = 'root';
await branch.save();
branch.save();
logFix(`Child branch ${branch.branchId} has been moved to root since it was a child of a search note ${parentNoteId}`)
}
@ -476,17 +476,17 @@ class ConsistencyChecks {
}
});
await this.findAndFixIssues(`
this.findAndFixIssues(`
SELECT attributeId
FROM attributes
WHERE isDeleted = 0
AND type = 'relation'
AND value = ''`,
async ({attributeId}) => {
({attributeId}) => {
if (this.autoFix) {
const relation = await repository.getAttribute(attributeId);
const relation = repository.getAttribute(attributeId);
relation.isDeleted = true;
await relation.save();
relation.save();
logFix(`Removed relation ${relation.attributeId} of name "${relation.name} with empty target.`);
} else {
@ -494,7 +494,7 @@ class ConsistencyChecks {
}
});
await this.findAndFixIssues(`
this.findAndFixIssues(`
SELECT attributeId,
type
FROM attributes
@ -503,11 +503,11 @@ class ConsistencyChecks {
AND type != 'label-definition'
AND type != 'relation'
AND type != 'relation-definition'`,
async ({attributeId, type}) => {
({attributeId, type}) => {
if (this.autoFix) {
const attribute = await repository.getAttribute(attributeId);
const attribute = repository.getAttribute(attributeId);
attribute.type = 'label';
await attribute.save();
attribute.save();
logFix(`Attribute ${attributeId} type was changed to label since it had invalid type '${type}'`);
} else {
@ -515,18 +515,18 @@ class ConsistencyChecks {
}
});
await this.findAndFixIssues(`
this.findAndFixIssues(`
SELECT attributeId,
attributes.noteId
FROM attributes
JOIN notes ON attributes.noteId = notes.noteId
WHERE attributes.isDeleted = 0
AND notes.isDeleted = 1`,
async ({attributeId, noteId}) => {
({attributeId, noteId}) => {
if (this.autoFix) {
const attribute = await repository.getAttribute(attributeId);
const attribute = repository.getAttribute(attributeId);
attribute.isDeleted = true;
await attribute.save();
attribute.save();
logFix(`Removed attribute ${attributeId} because owning note ${noteId} is also deleted.`);
} else {
@ -534,7 +534,7 @@ class ConsistencyChecks {
}
});
await this.findAndFixIssues(`
this.findAndFixIssues(`
SELECT attributeId,
attributes.value AS targetNoteId
FROM attributes
@ -542,11 +542,11 @@ class ConsistencyChecks {
WHERE attributes.type = 'relation'
AND attributes.isDeleted = 0
AND notes.isDeleted = 1`,
async ({attributeId, targetNoteId}) => {
({attributeId, targetNoteId}) => {
if (this.autoFix) {
const attribute = await repository.getAttribute(attributeId);
const attribute = repository.getAttribute(attributeId);
attribute.isDeleted = true;
await attribute.save();
attribute.save();
logFix(`Removed attribute ${attributeId} because target note ${targetNoteId} is also deleted.`);
} else {
@ -555,8 +555,8 @@ class ConsistencyChecks {
});
}
async runSyncRowChecks(entityName, key) {
await this.findAndFixIssues(`
runSyncRowChecks(entityName, key) {
this.findAndFixIssues(`
SELECT
${key} as entityId
FROM
@ -564,9 +564,9 @@ class ConsistencyChecks {
LEFT JOIN sync ON sync.entityName = '${entityName}' AND entityId = ${key}
WHERE
sync.id IS NULL AND ` + (entityName === 'options' ? 'options.isSynced = 1' : '1'),
async ({entityId}) => {
({entityId}) => {
if (this.autoFix) {
await syncTableService.addEntitySync(entityName, entityId);
syncTableService.addEntitySync(entityName, entityId);
logFix(`Created missing sync record for entityName=${entityName}, entityId=${entityId}`);
} else {
@ -574,7 +574,7 @@ class ConsistencyChecks {
}
});
await this.findAndFixIssues(`
this.findAndFixIssues(`
SELECT
id, entityId
FROM
@ -583,9 +583,9 @@ class ConsistencyChecks {
WHERE
sync.entityName = '${entityName}'
AND ${key} IS NULL`,
async ({id, entityId}) => {
({id, entityId}) => {
if (this.autoFix) {
await sql.execute("DELETE FROM sync WHERE entityName = ? AND entityId = ?", [entityName, entityId]);
sql.execute("DELETE FROM sync WHERE entityName = ? AND entityId = ?", [entityName, entityId]);
logFix(`Deleted extra sync record id=${id}, entityName=${entityName}, entityId=${entityId}`);
} else {
@ -594,43 +594,43 @@ class ConsistencyChecks {
});
}
async findSyncRowsIssues() {
await this.runSyncRowChecks("notes", "noteId");
await this.runSyncRowChecks("note_contents", "noteId");
await this.runSyncRowChecks("note_revisions", "noteRevisionId");
await this.runSyncRowChecks("branches", "branchId");
await this.runSyncRowChecks("recent_notes", "noteId");
await this.runSyncRowChecks("attributes", "attributeId");
await this.runSyncRowChecks("api_tokens", "apiTokenId");
await this.runSyncRowChecks("options", "name");
findSyncRowsIssues() {
this.runSyncRowChecks("notes", "noteId");
this.runSyncRowChecks("note_contents", "noteId");
this.runSyncRowChecks("note_revisions", "noteRevisionId");
this.runSyncRowChecks("branches", "branchId");
this.runSyncRowChecks("recent_notes", "noteId");
this.runSyncRowChecks("attributes", "attributeId");
this.runSyncRowChecks("api_tokens", "apiTokenId");
this.runSyncRowChecks("options", "name");
}
async runAllChecks() {
runAllChecks() {
this.unrecoveredConsistencyErrors = false;
this.fixedIssues = false;
await this.findBrokenReferenceIssues();
this.findBrokenReferenceIssues();
await this.findExistencyIssues();
this.findExistencyIssues();
await this.findLogicIssues();
this.findLogicIssues();
await this.findSyncRowsIssues();
this.findSyncRowsIssues();
// root branch should always be expanded
await sql.execute("UPDATE branches SET isExpanded = 1 WHERE branchId = 'root'");
sql.execute("UPDATE branches SET isExpanded = 1 WHERE branchId = 'root'");
if (this.unrecoveredConsistencyErrors) {
// we run this only if basic checks passed since this assumes basic data consistency
await this.checkTreeCycles();
this.checkTreeCycles();
}
return !this.unrecoveredConsistencyErrors;
}
async showEntityStat(name, query) {
const map = await sql.getMap(query);
showEntityStat(name, query) {
const map = sql.getMap(query);
map[0] = map[0] || 0;
map[1] = map[1] || 0;
@ -638,20 +638,20 @@ class ConsistencyChecks {
log.info(`${name} deleted: ${map[1]}, not deleted ${map[0]}`);
}
async runDbDiagnostics() {
await this.showEntityStat("Notes", `SELECT isDeleted, count(1)
runDbDiagnostics() {
this.showEntityStat("Notes", `SELECT isDeleted, count(1)
FROM notes
GROUP BY isDeleted`);
await this.showEntityStat("Note revisions", `SELECT isErased, count(1)
this.showEntityStat("Note revisions", `SELECT isErased, count(1)
FROM note_revisions
GROUP BY isErased`);
await this.showEntityStat("Branches", `SELECT isDeleted, count(1)
this.showEntityStat("Branches", `SELECT isDeleted, count(1)
FROM branches
GROUP BY isDeleted`);
await this.showEntityStat("Attributes", `SELECT isDeleted, count(1)
this.showEntityStat("Attributes", `SELECT isDeleted, count(1)
FROM attributes
GROUP BY isDeleted`);
await this.showEntityStat("API tokens", `SELECT isDeleted, count(1)
this.showEntityStat("API tokens", `SELECT isDeleted, count(1)
FROM api_tokens
GROUP BY isDeleted`);
}
@ -659,12 +659,12 @@ class ConsistencyChecks {
async runChecks() {
let elapsedTimeMs;
await syncMutexService.doExclusively(async () => {
await syncMutexService.doExclusively(() => {
const startTime = new Date();
await this.runDbDiagnostics();
this.runDbDiagnostics();
await this.runAllChecks();
this.runAllChecks();
elapsedTimeMs = Date.now() - startTime.getTime();
});
@ -687,24 +687,22 @@ function logError(message) {
log.info("Consistency error: " + message);
}
async function runPeriodicChecks() {
const autoFix = await optionsService.getOptionBool('autoFixConsistencyIssues');
function runPeriodicChecks() {
const autoFix = optionsService.getOptionBool('autoFixConsistencyIssues');
const consistencyChecks = new ConsistencyChecks(autoFix);
await consistencyChecks.runChecks();
consistencyChecks.runChecks();
}
async function runOnDemandChecks(autoFix) {
function runOnDemandChecks(autoFix) {
const consistencyChecks = new ConsistencyChecks(autoFix);
await consistencyChecks.runChecks();
consistencyChecks.runChecks();
}
sqlInit.dbReady.then(() => {
setInterval(cls.wrap(runPeriodicChecks), 60 * 60 * 1000);
setInterval(cls.wrap(runPeriodicChecks), 60 * 60 * 1000);
// kickoff checks soon after startup (to not block the initial load)
setTimeout(cls.wrap(runPeriodicChecks), 20 * 1000);
});
// kickoff checks soon after startup (to not block the initial load)
setTimeout(cls.wrap(runPeriodicChecks), 20 * 1000);
module.exports = {
runOnDemandChecks

View File

@ -11,8 +11,8 @@ const NoteRevision = require('../entities/note_revision');
const RecentNote = require('../entities/recent_note');
const Option = require('../entities/option');
async function getSectorHashes(tableName, primaryKeyName, whereBranch) {
const hashes = await sql.getRows(`SELECT ${primaryKeyName} AS id, hash FROM ${tableName} `
function getSectorHashes(tableName, primaryKeyName, whereBranch) {
const hashes = sql.getRows(`SELECT ${primaryKeyName} AS id, hash FROM ${tableName} `
+ (whereBranch ? `WHERE ${whereBranch} ` : '')
+ ` ORDER BY ${primaryKeyName}`);
@ -29,19 +29,19 @@ async function getSectorHashes(tableName, primaryKeyName, whereBranch) {
return map;
}
async function getEntityHashes() {
function getEntityHashes() {
const startTime = new Date();
const hashes = {
notes: await getSectorHashes(Note.entityName, Note.primaryKeyName),
note_contents: await getSectorHashes("note_contents", "noteId"),
branches: await getSectorHashes(Branch.entityName, Branch.primaryKeyName),
note_revisions: await getSectorHashes(NoteRevision.entityName, NoteRevision.primaryKeyName),
note_revision_contents: await getSectorHashes("note_revision_contents", "noteRevisionId"),
recent_notes: await getSectorHashes(RecentNote.entityName, RecentNote.primaryKeyName),
options: await getSectorHashes(Option.entityName, Option.primaryKeyName, "isSynced = 1"),
attributes: await getSectorHashes(Attribute.entityName, Attribute.primaryKeyName),
api_tokens: await getSectorHashes(ApiToken.entityName, ApiToken.primaryKeyName),
notes: getSectorHashes(Note.entityName, Note.primaryKeyName),
note_contents: getSectorHashes("note_contents", "noteId"),
branches: getSectorHashes(Branch.entityName, Branch.primaryKeyName),
note_revisions: getSectorHashes(NoteRevision.entityName, NoteRevision.primaryKeyName),
note_revision_contents: getSectorHashes("note_revision_contents", "noteRevisionId"),
recent_notes: getSectorHashes(RecentNote.entityName, RecentNote.primaryKeyName),
options: getSectorHashes(Option.entityName, Option.primaryKeyName, "isSynced = 1"),
attributes: getSectorHashes(Attribute.entityName, Attribute.primaryKeyName),
api_tokens: getSectorHashes(ApiToken.entityName, ApiToken.primaryKeyName),
};
const elapsedTimeMs = Date.now() - startTime.getTime();
@ -51,8 +51,8 @@ async function getEntityHashes() {
return hashes;
}
async function checkContentHashes(otherHashes) {
const entityHashes = await getEntityHashes();
function checkContentHashes(otherHashes) {
const entityHashes = getEntityHashes();
const failedChecks = [];
for (const entityName in entityHashes) {

View File

@ -13,8 +13,8 @@ const DATE_LABEL = 'dateNote';
const DAYS = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
const MONTHS = ['January','February','March','April','May','June','July','August','September','October','November','December'];
async function createNote(parentNoteId, noteTitle) {
return (await noteService.createNewNote({
function createNote(parentNoteId, noteTitle) {
return (noteService.createNewNote({
parentNoteId: parentNoteId,
title: noteTitle,
content: '',
@ -23,20 +23,20 @@ async function createNote(parentNoteId, noteTitle) {
})).note;
}
async function getNoteStartingWith(parentNoteId, startsWith) {
return await repository.getEntity(`SELECT notes.* FROM notes JOIN branches USING(noteId)
function getNoteStartingWith(parentNoteId, startsWith) {
return repository.getEntity(`SELECT notes.* FROM notes JOIN branches USING(noteId)
WHERE parentNoteId = ? AND title LIKE '${startsWith}%'
AND notes.isDeleted = 0 AND isProtected = 0
AND branches.isDeleted = 0`, [parentNoteId]);
}
/** @return {Promise<Note>} */
async function getRootCalendarNote() {
function getRootCalendarNote() {
// some caching here could be useful (e.g. in CLS)
let rootNote = await attributeService.getNoteWithLabel(CALENDAR_ROOT_LABEL);
let rootNote = attributeService.getNoteWithLabel(CALENDAR_ROOT_LABEL);
if (!rootNote) {
rootNote = (await noteService.createNewNote({
rootNote = (noteService.createNewNote({
parentNoteId: 'root',
title: 'Calendar',
target: 'into',
@ -45,36 +45,36 @@ async function getRootCalendarNote() {
content: ''
})).note;
await attributeService.createLabel(rootNote.noteId, CALENDAR_ROOT_LABEL);
await attributeService.createLabel(rootNote.noteId, 'sorted');
attributeService.createLabel(rootNote.noteId, CALENDAR_ROOT_LABEL);
attributeService.createLabel(rootNote.noteId, 'sorted');
}
return rootNote;
}
/** @return {Promise<Note>} */
async function getYearNote(dateStr, rootNote) {
function getYearNote(dateStr, rootNote) {
if (!rootNote) {
rootNote = await getRootCalendarNote();
rootNote = getRootCalendarNote();
}
const yearStr = dateStr.substr(0, 4);
let yearNote = await attributeService.getNoteWithLabel(YEAR_LABEL, yearStr);
let yearNote = attributeService.getNoteWithLabel(YEAR_LABEL, yearStr);
if (!yearNote) {
yearNote = await getNoteStartingWith(rootNote.noteId, yearStr);
yearNote = getNoteStartingWith(rootNote.noteId, yearStr);
if (!yearNote) {
yearNote = await createNote(rootNote.noteId, yearStr);
yearNote = createNote(rootNote.noteId, yearStr);
await attributeService.createLabel(yearNote.noteId, YEAR_LABEL, yearStr);
await attributeService.createLabel(yearNote.noteId, 'sorted');
attributeService.createLabel(yearNote.noteId, YEAR_LABEL, yearStr);
attributeService.createLabel(yearNote.noteId, 'sorted');
const yearTemplateAttr = await rootNote.getOwnedAttribute('relation', 'yearTemplate');
const yearTemplateAttr = rootNote.getOwnedAttribute('relation', 'yearTemplate');
if (yearTemplateAttr) {
await attributeService.createRelation(yearNote.noteId, 'template', yearTemplateAttr.value);
attributeService.createRelation(yearNote.noteId, 'template', yearTemplateAttr.value);
}
}
}
@ -82,8 +82,8 @@ async function getYearNote(dateStr, rootNote) {
return yearNote;
}
async function getMonthNoteTitle(rootNote, monthNumber, dateObj) {
const pattern = await rootNote.getOwnedLabelValue("monthPattern") || "{monthNumberPadded} - {month}";
function getMonthNoteTitle(rootNote, monthNumber, dateObj) {
const pattern = rootNote.getOwnedLabelValue("monthPattern") || "{monthNumberPadded} - {month}";
const monthName = MONTHS[dateObj.getMonth()];
return pattern
@ -92,35 +92,35 @@ async function getMonthNoteTitle(rootNote, monthNumber, dateObj) {
}
/** @return {Promise<Note>} */
async function getMonthNote(dateStr, rootNote) {
function getMonthNote(dateStr, rootNote) {
if (!rootNote) {
rootNote = await getRootCalendarNote();
rootNote = getRootCalendarNote();
}
const monthStr = dateStr.substr(0, 7);
const monthNumber = dateStr.substr(5, 2);
let monthNote = await attributeService.getNoteWithLabel(MONTH_LABEL, monthStr);
let monthNote = attributeService.getNoteWithLabel(MONTH_LABEL, monthStr);
if (!monthNote) {
const yearNote = await getYearNote(dateStr, rootNote);
const yearNote = getYearNote(dateStr, rootNote);
monthNote = await getNoteStartingWith(yearNote.noteId, monthNumber);
monthNote = getNoteStartingWith(yearNote.noteId, monthNumber);
if (!monthNote) {
const dateObj = dateUtils.parseLocalDate(dateStr);
const noteTitle = await getMonthNoteTitle(rootNote, monthNumber, dateObj);
const noteTitle = getMonthNoteTitle(rootNote, monthNumber, dateObj);
monthNote = await createNote(yearNote.noteId, noteTitle);
monthNote = createNote(yearNote.noteId, noteTitle);
await attributeService.createLabel(monthNote.noteId, MONTH_LABEL, monthStr);
await attributeService.createLabel(monthNote.noteId, 'sorted');
attributeService.createLabel(monthNote.noteId, MONTH_LABEL, monthStr);
attributeService.createLabel(monthNote.noteId, 'sorted');
const monthTemplateAttr = await rootNote.getOwnedAttribute('relation', 'monthTemplate');
const monthTemplateAttr = rootNote.getOwnedAttribute('relation', 'monthTemplate');
if (monthTemplateAttr) {
await attributeService.createRelation(monthNote.noteId, 'template', monthTemplateAttr.value);
attributeService.createRelation(monthNote.noteId, 'template', monthTemplateAttr.value);
}
}
}
@ -128,8 +128,8 @@ async function getMonthNote(dateStr, rootNote) {
return monthNote;
}
async function getDateNoteTitle(rootNote, dayNumber, dateObj) {
const pattern = await rootNote.getOwnedLabelValue("datePattern") || "{dayInMonthPadded} - {weekDay}";
function getDateNoteTitle(rootNote, dayNumber, dateObj) {
const pattern = rootNote.getOwnedLabelValue("datePattern") || "{dayInMonthPadded} - {weekDay}";
const weekDay = DAYS[dateObj.getDay()];
return pattern
@ -140,31 +140,31 @@ async function getDateNoteTitle(rootNote, dayNumber, dateObj) {
}
/** @return {Promise<Note>} */
async function getDateNote(dateStr) {
const rootNote = await getRootCalendarNote();
function getDateNote(dateStr) {
const rootNote = getRootCalendarNote();
const dayNumber = dateStr.substr(8, 2);
let dateNote = await attributeService.getNoteWithLabel(DATE_LABEL, dateStr);
let dateNote = attributeService.getNoteWithLabel(DATE_LABEL, dateStr);
if (!dateNote) {
const monthNote = await getMonthNote(dateStr, rootNote);
const monthNote = getMonthNote(dateStr, rootNote);
dateNote = await getNoteStartingWith(monthNote.noteId, dayNumber);
dateNote = getNoteStartingWith(monthNote.noteId, dayNumber);
if (!dateNote) {
const dateObj = dateUtils.parseLocalDate(dateStr);
const noteTitle = await getDateNoteTitle(rootNote, dayNumber, dateObj);
const noteTitle = getDateNoteTitle(rootNote, dayNumber, dateObj);
dateNote = await createNote(monthNote.noteId, noteTitle);
dateNote = createNote(monthNote.noteId, noteTitle);
await attributeService.createLabel(dateNote.noteId, DATE_LABEL, dateStr.substr(0, 10));
attributeService.createLabel(dateNote.noteId, DATE_LABEL, dateStr.substr(0, 10));
const dateTemplateAttr = await rootNote.getOwnedAttribute('relation', 'dateTemplate');
const dateTemplateAttr = rootNote.getOwnedAttribute('relation', 'dateTemplate');
if (dateTemplateAttr) {
await attributeService.createRelation(dateNote.noteId, 'template', dateTemplateAttr.value);
attributeService.createRelation(dateNote.noteId, 'template', dateTemplateAttr.value);
}
}
}
@ -172,8 +172,8 @@ async function getDateNote(dateStr) {
return dateNote;
}
async function getTodayNote() {
return await getDateNote(dateUtils.localNowDate());
function getTodayNote() {
return getDateNote(dateUtils.localNowDate());
}
function getStartOfTheWeek(date, startOfTheWeek) {
@ -193,7 +193,7 @@ function getStartOfTheWeek(date, startOfTheWeek) {
return new Date(date.setDate(diff));
}
async function getWeekNote(dateStr, options = {}) {
function getWeekNote(dateStr, options = {}) {
const startOfTheWeek = options.startOfTheWeek || "monday";
const dateObj = getStartOfTheWeek(dateUtils.parseLocalDate(dateStr), startOfTheWeek);

View File

@ -25,13 +25,13 @@ function subscribe(eventTypes, listener) {
}
}
async function emit(eventType, data) {
function emit(eventType, data) {
const listeners = eventListeners[eventType];
if (listeners) {
for (const listener of listeners) {
try {
await listener(data);
listener(data);
}
catch (e) {
log.error("Listener threw error: " + e.stack);

View File

@ -3,20 +3,20 @@
const repository = require("../repository");
const utils = require('../utils');
async function exportToOpml(taskContext, branch, version, res) {
function exportToOpml(taskContext, branch, version, res) {
if (!['1.0', '2.0'].includes(version)) {
throw new Error("Unrecognized OPML version " + version);
}
const opmlVersion = parseInt(version);
const note = await branch.getNote();
const note = branch.getNote();
async function exportNoteInner(branchId) {
const branch = await repository.getBranch(branchId);
const note = await branch.getNote();
function exportNoteInner(branchId) {
const branch = repository.getBranch(branchId);
const note = branch.getNote();
if (await note.hasOwnedLabel('excludeFromExport')) {
if (note.hasOwnedLabel('excludeFromExport')) {
return;
}
@ -24,13 +24,13 @@ async function exportToOpml(taskContext, branch, version, res) {
if (opmlVersion === 1) {
const preparedTitle = escapeXmlAttribute(title);
const preparedContent = note.isStringNote() ? prepareText(await note.getContent()) : '';
const preparedContent = note.isStringNote() ? prepareText(note.getContent()) : '';
res.write(`<outline title="${preparedTitle}" text="${preparedContent}">\n`);
}
else if (opmlVersion === 2) {
const preparedTitle = escapeXmlAttribute(title);
const preparedContent = note.isStringNote() ? escapeXmlAttribute(await note.getContent()) : '';
const preparedContent = note.isStringNote() ? escapeXmlAttribute(note.getContent()) : '';
res.write(`<outline text="${preparedTitle}" _note="${preparedContent}">\n`);
}
@ -40,8 +40,8 @@ async function exportToOpml(taskContext, branch, version, res) {
taskContext.increaseProgressCount();
for (const child of await note.getChildBranches()) {
await exportNoteInner(child.branchId);
for (const child of note.getChildBranches()) {
exportNoteInner(child.branchId);
}
res.write('</outline>');
@ -60,7 +60,7 @@ async function exportToOpml(taskContext, branch, version, res) {
</head>
<body>`);
await exportNoteInner(branch.branchId);
exportNoteInner(branch.branchId);
res.write(`</body>
</opml>`);

View File

@ -5,8 +5,8 @@ const html = require('html');
const utils = require('../utils');
const mdService = require('./md');
async function exportSingleNote(taskContext, branch, format, res) {
const note = await branch.getNote();
function exportSingleNote(taskContext, branch, format, res) {
const note = branch.getNote();
if (note.type === 'image' || note.type === 'file') {
return [400, `Note type ${note.type} cannot be exported as single file.`];
@ -18,7 +18,7 @@ async function exportSingleNote(taskContext, branch, format, res) {
let payload, extension, mime;
let content = await note.getContent();
let content = note.getContent();
if (note.type === 'text') {
if (format === 'html') {

View File

@ -20,7 +20,7 @@ const yazl = require("yazl");
* @param {Branch} branch
* @param {string} format - 'html' or 'markdown'
*/
async function exportToZip(taskContext, branch, format, res) {
function exportToZip(taskContext, branch, format, res) {
const zipFile = new yazl.ZipFile();
const noteIdToMeta = {};
@ -80,10 +80,10 @@ async function exportToZip(taskContext, branch, format, res) {
return getUniqueFilename(existingFileNames, fileName);
}
async function getNoteMeta(branch, parentMeta, existingFileNames) {
const note = await branch.getNote();
function getNoteMeta(branch, parentMeta, existingFileNames) {
const note = branch.getNote();
if (await note.hasOwnedLabel('excludeFromExport')) {
if (note.hasOwnedLabel('excludeFromExport')) {
return;
}
@ -122,7 +122,7 @@ async function exportToZip(taskContext, branch, format, res) {
type: note.type,
mime: note.mime,
// we don't export utcDateCreated and utcDateModified of any entity since that would be a bit misleading
attributes: (await note.getOwnedAttributes()).map(attribute => ({
attributes: (note.getOwnedAttributes()).map(attribute => ({
type: attribute.type,
name: attribute.name,
value: attribute.value,
@ -139,12 +139,12 @@ async function exportToZip(taskContext, branch, format, res) {
noteIdToMeta[note.noteId] = meta;
const childBranches = await note.getChildBranches();
const childBranches = note.getChildBranches();
const available = !note.isProtected || protectedSessionService.isProtectedSessionAvailable();
// if it's a leaf then we'll export it even if it's empty
if (available && ((await note.getContent()).length > 0 || childBranches.length === 0)) {
if (available && ((note.getContent()).length > 0 || childBranches.length === 0)) {
meta.dataFileName = getDataFileName(note, baseFileName, existingFileNames);
}
@ -156,7 +156,7 @@ async function exportToZip(taskContext, branch, format, res) {
const childExistingNames = {};
for (const childBranch of childBranches) {
const note = await getNoteMeta(childBranch, meta, childExistingNames);
const note = getNoteMeta(childBranch, meta, childExistingNames);
// can be undefined if export is disabled for this note
if (note) {
@ -258,7 +258,7 @@ ${content}
// noteId => file path
const notePaths = {};
async function saveNote(noteMeta, filePathPrefix) {
function saveNote(noteMeta, filePathPrefix) {
if (noteMeta.isClone) {
const targetUrl = getTargetUrl(noteMeta.noteId, noteMeta);
@ -271,12 +271,12 @@ ${content}
return;
}
const note = await repository.getNote(noteMeta.noteId);
const note = repository.getNote(noteMeta.noteId);
notePaths[note.noteId] = filePathPrefix + (noteMeta.dataFileName || noteMeta.dirFileName);
if (noteMeta.dataFileName) {
const content = prepareContent(noteMeta.title, await note.getContent(), noteMeta);
const content = prepareContent(noteMeta.title, note.getContent(), noteMeta);
zipFile.addBuffer(content, filePathPrefix + noteMeta.dataFileName, {mtime: dateUtils.parseDateTime(note.utcDateModified)});
}
@ -289,12 +289,12 @@ ${content}
zipFile.addEmptyDirectory(directoryPath, {mtime: dateUtils.parseDateTime(note.utcDateModified)});
for (const childMeta of noteMeta.children) {
await saveNote(childMeta, directoryPath + '/');
saveNote(childMeta, directoryPath + '/');
}
}
}
async function saveNavigation(rootMeta, navigationMeta) {
function saveNavigation(rootMeta, navigationMeta) {
function saveNavigationInner(meta) {
let html = '<li>';
@ -336,7 +336,7 @@ ${content}
zipFile.addBuffer(prettyHtml, navigationMeta.dataFileName);
}
async function saveIndex(rootMeta, indexMeta) {
function saveIndex(rootMeta, indexMeta) {
let firstNonEmptyNote;
let curMeta = rootMeta;
@ -367,14 +367,14 @@ ${content}
zipFile.addBuffer(fullHtml, indexMeta.dataFileName);
}
async function saveCss(rootMeta, cssMeta) {
function saveCss(rootMeta, cssMeta) {
const cssContent = fs.readFileSync(RESOURCE_DIR + '/libraries/ckeditor/ckeditor-content.css');
zipFile.addBuffer(cssContent, cssMeta.dataFileName);
}
const existingFileNames = format === 'html' ? ['navigation', 'index'] : [];
const rootMeta = await getNoteMeta(branch, { notePath: [] }, existingFileNames);
const rootMeta = getNoteMeta(branch, { notePath: [] }, existingFileNames);
const metaFile = {
formatVersion: 1,
@ -421,15 +421,15 @@ ${content}
zipFile.addBuffer(metaFileJson, "!!!meta.json");
await saveNote(rootMeta, '');
saveNote(rootMeta, '');
if (format === 'html') {
await saveNavigation(rootMeta, navigationMeta);
await saveIndex(rootMeta, indexMeta);
await saveCss(rootMeta, cssMeta);
saveNavigation(rootMeta, navigationMeta);
saveIndex(rootMeta, indexMeta);
saveCss(rootMeta, cssMeta);
}
const note = await branch.getNote();
const note = branch.getNote();
const zipFileName = (branch.prefix ? (branch.prefix + " - ") : "") + note.title + ".zip";
res.setHeader('Content-Disposition', utils.getContentDisposition(zipFileName));

View File

@ -5,14 +5,14 @@ const log = require('./log');
const repository = require('./repository');
const Attribute = require('../entities/attribute');
async function runAttachedRelations(note, relationName, originEntity) {
const runRelations = await note.getRelations(relationName);
function runAttachedRelations(note, relationName, originEntity) {
const runRelations = note.getRelations(relationName);
for (const relation of runRelations) {
const scriptNote = await relation.getTargetNote();
const scriptNote = relation.getTargetNote();
if (scriptNote) {
await scriptService.executeNoteNoException(scriptNote, { originEntity });
scriptService.executeNoteNoException(scriptNote, { originEntity });
}
else {
log.error(`Target note ${relation.value} of atttribute ${relation.attributeId} has not been found.`);
@ -20,90 +20,90 @@ async function runAttachedRelations(note, relationName, originEntity) {
}
}
eventService.subscribe(eventService.NOTE_TITLE_CHANGED, async note => {
await runAttachedRelations(note, 'runOnNoteTitleChange', note);
eventService.subscribe(eventService.NOTE_TITLE_CHANGED, note => {
runAttachedRelations(note, 'runOnNoteTitleChange', note);
if (!note.isRoot()) {
const parents = await note.getParentNotes();
const parents = note.getParentNotes();
for (const parent of parents) {
if (await parent.hasOwnedLabel("sorted")) {
await treeService.sortNotesAlphabetically(parent.noteId);
if (parent.hasOwnedLabel("sorted")) {
treeService.sortNotesAlphabetically(parent.noteId);
}
}
}
});
eventService.subscribe([ eventService.ENTITY_CHANGED, eventService.ENTITY_DELETED ], async ({ entityName, entity }) => {
eventService.subscribe([ eventService.ENTITY_CHANGED, eventService.ENTITY_DELETED ], ({ entityName, entity }) => {
if (entityName === 'attributes') {
await runAttachedRelations(await entity.getNote(), 'runOnAttributeChange', entity);
runAttachedRelations(entity.getNote(), 'runOnAttributeChange', entity);
}
else if (entityName === 'notes') {
await runAttachedRelations(entity, 'runOnNoteChange', entity);
runAttachedRelations(entity, 'runOnNoteChange', entity);
}
});
eventService.subscribe(eventService.ENTITY_CREATED, async ({ entityName, entity }) => {
eventService.subscribe(eventService.ENTITY_CREATED, ({ entityName, entity }) => {
if (entityName === 'attributes') {
await runAttachedRelations(await entity.getNote(), 'runOnAttributeCreation', entity);
runAttachedRelations(entity.getNote(), 'runOnAttributeCreation', entity);
if (entity.type === 'relation' && entity.name === 'template') {
const note = await repository.getNote(entity.noteId);
const note = repository.getNote(entity.noteId);
if (!note.isStringNote()) {
return;
}
const content = await note.getContent();
const content = note.getContent();
if (content && content.trim().length > 0) {
return;
}
const targetNote = await repository.getNote(entity.value);
const targetNote = repository.getNote(entity.value);
if (!targetNote || !targetNote.isStringNote()) {
return;
}
await note.setContent(await targetNote.getContent());
note.setContent(targetNote.getContent());
}
}
else if (entityName === 'notes') {
await runAttachedRelations(entity, 'runOnNoteCreation', entity);
runAttachedRelations(entity, 'runOnNoteCreation', entity);
}
});
eventService.subscribe(eventService.CHILD_NOTE_CREATED, async ({ parentNote, childNote }) => {
await runAttachedRelations(parentNote, 'runOnChildNoteCreation', childNote);
eventService.subscribe(eventService.CHILD_NOTE_CREATED, ({ parentNote, childNote }) => {
runAttachedRelations(parentNote, 'runOnChildNoteCreation', childNote);
});
async function processInverseRelations(entityName, entity, handler) {
function processInverseRelations(entityName, entity, handler) {
if (entityName === 'attributes' && entity.type === 'relation') {
const note = await entity.getNote();
const attributes = (await note.getOwnedAttributes(entity.name)).filter(relation => relation.type === 'relation-definition');
const note = entity.getNote();
const attributes = (note.getOwnedAttributes(entity.name)).filter(relation => relation.type === 'relation-definition');
for (const attribute of attributes) {
const definition = attribute.value;
if (definition.inverseRelation && definition.inverseRelation.trim()) {
const targetNote = await entity.getTargetNote();
const targetNote = entity.getTargetNote();
await handler(definition, note, targetNote);
handler(definition, note, targetNote);
}
}
}
}
eventService.subscribe(eventService.ENTITY_CHANGED, async ({ entityName, entity }) => {
await processInverseRelations(entityName, entity, async (definition, note, targetNote) => {
eventService.subscribe(eventService.ENTITY_CHANGED, ({ entityName, entity }) => {
processInverseRelations(entityName, entity, (definition, note, targetNote) => {
// we need to make sure that also target's inverse attribute exists and if note, then create it
// inverse attribute has to target our note as well
const hasInverseAttribute = (await targetNote.getRelations(definition.inverseRelation))
const hasInverseAttribute = (targetNote.getRelations(definition.inverseRelation))
.some(attr => attr.value === note.noteId);
if (!hasInverseAttribute) {
await new Attribute({
new Attribute({
noteId: targetNote.noteId,
type: 'relation',
name: definition.inverseRelation,
@ -116,16 +116,16 @@ eventService.subscribe(eventService.ENTITY_CHANGED, async ({ entityName, entity
});
});
eventService.subscribe(eventService.ENTITY_DELETED, async ({ entityName, entity }) => {
await processInverseRelations(entityName, entity, async (definition, note, targetNote) => {
eventService.subscribe(eventService.ENTITY_DELETED, ({ entityName, entity }) => {
processInverseRelations(entityName, entity, (definition, note, targetNote) => {
// if one inverse attribute is deleted then the other should be deleted as well
const relations = await targetNote.getOwnedRelations(definition.inverseRelation);
const relations = targetNote.getOwnedRelations(definition.inverseRelation);
let deletedSomething = false;
for (const relation of relations) {
if (relation.value === note.noteId) {
relation.isDeleted = true;
await relation.save();
relation.save();
deletedSomething = true;
}

View File

@ -3,12 +3,10 @@ const sqlInit = require('./sql_init');
const eventService = require('./events');
const hoistedNote = require('./hoisted_note');
eventService.subscribe(eventService.ENTITY_CHANGED, async ({entityName, entity}) => {
eventService.subscribe(eventService.ENTITY_CHANGED, ({entityName, entity}) => {
if (entityName === 'options' && entity.name === 'hoistedNoteId') {
hoistedNote.setHoistedNoteId(entity.value);
}
});
sqlInit.dbReady.then(async () => {
hoistedNote.setHoistedNoteId(await optionService.getOption('hoistedNoteId'));
});
hoistedNote.setHoistedNoteId(optionService.getOption('hoistedNoteId'));

View File

@ -15,7 +15,7 @@ const sanitizeFilename = require('sanitize-filename');
const noteRevisionService = require('./note_revisions.js');
const isSvg = require('is-svg');
async function processImage(uploadBuffer, originalName, shrinkImageSwitch) {
function processImage(uploadBuffer, originalName, shrinkImageSwitch) {
const origImageFormat = getImageType(uploadBuffer);
if (origImageFormat && ["webp", "svg"].includes(origImageFormat.ext)) {
@ -23,7 +23,7 @@ async function processImage(uploadBuffer, originalName, shrinkImageSwitch) {
shrinkImageSwitch = false;
}
const finalImageBuffer = shrinkImageSwitch ? await shrinkImage(uploadBuffer, originalName) : uploadBuffer;
const finalImageBuffer = shrinkImageSwitch ? shrinkImage(uploadBuffer, originalName) : uploadBuffer;
const imageFormat = getImageType(finalImageBuffer);
@ -50,34 +50,34 @@ function getImageMimeFromExtension(ext) {
return 'image/' + (ext === 'svg' ? 'svg+xml' : ext);
}
async function updateImage(noteId, uploadBuffer, originalName) {
function updateImage(noteId, uploadBuffer, originalName) {
log.info(`Updating image ${noteId}: ${originalName}`);
const {buffer, imageFormat} = await processImage(uploadBuffer, originalName, true);
const {buffer, imageFormat} = processImage(uploadBuffer, originalName, true);
const note = await repository.getNote(noteId);
const note = repository.getNote(noteId);
await noteRevisionService.createNoteRevision(note);
noteRevisionService.createNoteRevision(note);
note.mime = getImageMimeFromExtension(imageFormat.ext);
await note.setContent(buffer);
note.setContent(buffer);
await note.setLabel('originalFileName', originalName);
note.setLabel('originalFileName', originalName);
await noteRevisionService.protectNoteRevisions(note);
noteRevisionService.protectNoteRevisions(note);
}
async function saveImage(parentNoteId, uploadBuffer, originalName, shrinkImageSwitch) {
function saveImage(parentNoteId, uploadBuffer, originalName, shrinkImageSwitch) {
log.info(`Saving image ${originalName}`);
const {buffer, imageFormat} = await processImage(uploadBuffer, originalName, shrinkImageSwitch);
const {buffer, imageFormat} = processImage(uploadBuffer, originalName, shrinkImageSwitch);
const fileName = sanitizeFilename(originalName);
const parentNote = await repository.getNote(parentNoteId);
const parentNote = repository.getNote(parentNoteId);
const {note} = await noteService.createNewNote({
const {note} = noteService.createNewNote({
parentNoteId,
title: fileName,
content: buffer,
@ -86,7 +86,7 @@ async function saveImage(parentNoteId, uploadBuffer, originalName, shrinkImageSw
isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable()
});
await note.addLabel('originalFileName', originalName);
note.addLabel('originalFileName', originalName);
return {
fileName,
@ -96,18 +96,18 @@ async function saveImage(parentNoteId, uploadBuffer, originalName, shrinkImageSw
};
}
async function shrinkImage(buffer, originalName) {
function shrinkImage(buffer, originalName) {
// we do resizing with max (100) quality which will be trimmed during optimization step next
const resizedImage = await resize(buffer, 100);
const resizedImage = resize(buffer, 100);
let finalImageBuffer;
const jpegQuality = await optionService.getOptionInt('imageJpegQuality');
const jpegQuality = optionService.getOptionInt('imageJpegQuality');
try {
finalImageBuffer = await optimize(resizedImage, jpegQuality);
finalImageBuffer = optimize(resizedImage, jpegQuality);
} catch (e) {
log.error("Failed to optimize image '" + originalName + "'\nStack: " + e.stack);
finalImageBuffer = await resize(buffer, jpegQuality);
finalImageBuffer = resize(buffer, jpegQuality);
}
// if resizing & shrinking did not help with size then save the original
@ -119,10 +119,10 @@ async function shrinkImage(buffer, originalName) {
return finalImageBuffer;
}
async function resize(buffer, quality) {
const imageMaxWidthHeight = await optionService.getOptionInt('imageMaxWidthHeight');
function resize(buffer, quality) {
const imageMaxWidthHeight = optionService.getOptionInt('imageMaxWidthHeight');
const image = await jimp.read(buffer);
const image = jimp.read(buffer);
if (image.bitmap.width > image.bitmap.height && image.bitmap.width > imageMaxWidthHeight) {
image.resize(imageMaxWidthHeight, jimp.AUTO);
@ -139,8 +139,8 @@ async function resize(buffer, quality) {
return image.getBufferAsync(jimp.MIME_JPEG);
}
async function optimize(buffer, jpegQuality) {
return await imagemin.buffer(buffer, {
function optimize(buffer, jpegQuality) {
return imagemin.buffer(buffer, {
plugins: [
imageminMozJpeg({
quality: jpegQuality

View File

@ -20,7 +20,7 @@ function parseDate(text) {
let note = {};
let resource;
async function importEnex(taskContext, file, parentNote) {
function importEnex(taskContext, file, parentNote) {
const saxStream = sax.createStream(true);
const rootNoteTitle = file.originalname.toLowerCase().endsWith(".enex")
@ -28,7 +28,7 @@ async function importEnex(taskContext, file, parentNote) {
: file.originalname;
// root note is new note into all ENEX/notebook's notes will be imported
const rootNote = (await noteService.createNewNote({
const rootNote = (noteService.createNewNote({
parentNoteId: parentNote.noteId,
title: rootNoteTitle,
content: "",
@ -191,9 +191,9 @@ async function importEnex(taskContext, file, parentNote) {
}
});
async function updateDates(noteId, utcDateCreated, utcDateModified) {
function updateDates(noteId, utcDateCreated, utcDateModified) {
// it's difficult to force custom dateCreated and dateModified to Note entity so we do it post-creation with SQL
await sql.execute(`
sql.execute(`
UPDATE notes
SET dateCreated = ?,
utcDateCreated = ?,
@ -202,20 +202,20 @@ async function importEnex(taskContext, file, parentNote) {
WHERE noteId = ?`,
[utcDateCreated, utcDateCreated, utcDateModified, utcDateModified, noteId]);
await sql.execute(`
sql.execute(`
UPDATE note_contents
SET utcDateModified = ?
WHERE noteId = ?`,
[utcDateModified, noteId]);
}
async function saveNote() {
// make a copy because stream continues with the next async call and note gets overwritten
function saveNote() {
// make a copy because stream continues with the next call and note gets overwritten
let {title, content, attributes, resources, utcDateCreated, utcDateModified} = note;
content = extractContent(content);
const noteEntity = (await noteService.createNewNote({
const noteEntity = (noteService.createNewNote({
parentNoteId: rootNote.noteId,
title,
content,
@ -226,7 +226,7 @@ async function importEnex(taskContext, file, parentNote) {
})).note;
for (const attr of attributes) {
await noteEntity.addAttribute(attr.type, attr.name, attr.value);
noteEntity.addAttribute(attr.type, attr.name, attr.value);
}
utcDateCreated = utcDateCreated || noteEntity.utcDateCreated;
@ -240,14 +240,14 @@ async function importEnex(taskContext, file, parentNote) {
const mediaRegex = new RegExp(`<en-media hash="${hash}"[^>]*>`, 'g');
const fileTypeFromBuffer = await FileType.fromBuffer(resource.content);
const fileTypeFromBuffer = FileType.fromBuffer(resource.content);
if (fileTypeFromBuffer) {
// If fileType returns something for buffer, then set the mime given
resource.mime = fileTypeFromBuffer.mime;
}
const createFileNote = async () => {
const resourceNote = (await noteService.createNewNote({
const createFileNote = () => {
const resourceNote = (noteService.createNewNote({
parentNoteId: noteEntity.noteId,
title: resource.title,
content: resource.content,
@ -257,10 +257,10 @@ async function importEnex(taskContext, file, parentNote) {
})).note;
for (const attr of resource.attributes) {
await noteEntity.addAttribute(attr.type, attr.name, attr.value);
noteEntity.addAttribute(attr.type, attr.name, attr.value);
}
await updateDates(resourceNote.noteId, utcDateCreated, utcDateModified);
updateDates(resourceNote.noteId, utcDateCreated, utcDateModified);
taskContext.increaseProgressCount();
@ -273,9 +273,9 @@ async function importEnex(taskContext, file, parentNote) {
try {
const originalName = "image." + resource.mime.substr(6);
const {url, note: imageNote} = await imageService.saveImage(noteEntity.noteId, resource.content, originalName, taskContext.data.shrinkImages);
const {url, note: imageNote} = imageService.saveImage(noteEntity.noteId, resource.content, originalName, taskContext.data.shrinkImages);
await updateDates(imageNote.noteId, utcDateCreated, utcDateModified);
updateDates(imageNote.noteId, utcDateCreated, utcDateModified);
const imageLink = `<img src="${url}">`;
@ -288,22 +288,22 @@ async function importEnex(taskContext, file, parentNote) {
}
} catch (e) {
log.error("error when saving image from ENEX file: " + e);
await createFileNote();
createFileNote();
}
} else {
await createFileNote();
createFileNote();
}
}
// save updated content with links to files/images
await noteEntity.setContent(content);
noteEntity.setContent(content);
await noteService.scanForLinks(noteEntity);
noteService.scanForLinks(noteEntity);
await updateDates(noteEntity.noteId, utcDateCreated, utcDateModified);
updateDates(noteEntity.noteId, utcDateCreated, utcDateModified);
}
saxStream.on("closetag", async tag => {
saxStream.on("closetag", tag => {
path.pop();
if (tag === 'note') {

View File

@ -10,8 +10,8 @@ const protectedSessionService = require('../protected_session');
* @param {Note} parentNote
* @return {Promise<*[]|*>}
*/
async function importOpml(taskContext, fileBuffer, parentNote) {
const xml = await new Promise(function(resolve, reject)
function importOpml(taskContext, fileBuffer, parentNote) {
const xml = new Promise(function(resolve, reject)
{
parseString(fileBuffer, function (err, result) {
if (err) {
@ -29,7 +29,7 @@ async function importOpml(taskContext, fileBuffer, parentNote) {
const opmlVersion = parseInt(xml.opml.$.version);
async function importOutline(outline, parentNoteId) {
function importOutline(outline, parentNoteId) {
let title, content;
if (opmlVersion === 1) {
@ -44,7 +44,7 @@ async function importOpml(taskContext, fileBuffer, parentNote) {
throw new Error("Unrecognized OPML version " + opmlVersion);
}
const {note} = await noteService.createNewNote({
const {note} = noteService.createNewNote({
parentNoteId,
title,
content,
@ -55,7 +55,7 @@ async function importOpml(taskContext, fileBuffer, parentNote) {
taskContext.increaseProgressCount();
for (const childOutline of (outline.outline || [])) {
await importOutline(childOutline, note.noteId);
importOutline(childOutline, note.noteId);
}
return note;
@ -65,7 +65,7 @@ async function importOpml(taskContext, fileBuffer, parentNote) {
let returnNote = null;
for (const outline of outlines) {
const note = await importOutline(outline, parentNote.noteId);
const note = importOutline(outline, parentNote.noteId);
// first created note will be activated after import
returnNote = returnNote || note;

View File

@ -7,42 +7,42 @@ const commonmark = require('commonmark');
const mimeService = require('./mime');
const utils = require('../../services/utils');
async function importSingleFile(taskContext, file, parentNote) {
function importSingleFile(taskContext, file, parentNote) {
const mime = mimeService.getMime(file.originalname) || file.mimetype;
if (taskContext.data.textImportedAsText) {
if (mime === 'text/html') {
return await importHtml(taskContext, file, parentNote);
return importHtml(taskContext, file, parentNote);
} else if (['text/markdown', 'text/x-markdown'].includes(mime)) {
return await importMarkdown(taskContext, file, parentNote);
return importMarkdown(taskContext, file, parentNote);
} else if (mime === 'text/plain') {
return await importPlainText(taskContext, file, parentNote);
return importPlainText(taskContext, file, parentNote);
}
}
if (taskContext.data.codeImportedAsCode && mimeService.getType(taskContext.data, mime) === 'code') {
return await importCodeNote(taskContext, file, parentNote);
return importCodeNote(taskContext, file, parentNote);
}
if (["image/jpeg", "image/gif", "image/png", "image/webp"].includes(mime)) {
return await importImage(file, parentNote, taskContext);
return importImage(file, parentNote, taskContext);
}
return await importFile(taskContext, file, parentNote);
return importFile(taskContext, file, parentNote);
}
async function importImage(file, parentNote, taskContext) {
const {note} = await imageService.saveImage(parentNote.noteId, file.buffer, file.originalname, taskContext.data.shrinkImages);
function importImage(file, parentNote, taskContext) {
const {note} = imageService.saveImage(parentNote.noteId, file.buffer, file.originalname, taskContext.data.shrinkImages);
taskContext.increaseProgressCount();
return note;
}
async function importFile(taskContext, file, parentNote) {
function importFile(taskContext, file, parentNote) {
const originalName = file.originalname;
const {note} = await noteService.createNewNote({
const {note} = noteService.createNewNote({
parentNoteId: parentNote.noteId,
title: originalName,
content: file.buffer,
@ -51,20 +51,20 @@ async function importFile(taskContext, file, parentNote) {
mime: mimeService.getMime(originalName) || file.mimetype
});
await note.addLabel("originalFileName", originalName);
note.addLabel("originalFileName", originalName);
taskContext.increaseProgressCount();
return note;
}
async function importCodeNote(taskContext, file, parentNote) {
function importCodeNote(taskContext, file, parentNote) {
const title = utils.getNoteTitle(file.originalname, taskContext.data.replaceUnderscoresWithSpaces);
const content = file.buffer.toString("UTF-8");
const detectedMime = mimeService.getMime(file.originalname) || file.mimetype;
const mime = mimeService.normalizeMimeType(detectedMime);
const {note} = await noteService.createNewNote({
const {note} = noteService.createNewNote({
parentNoteId: parentNote.noteId,
title,
content,
@ -78,12 +78,12 @@ async function importCodeNote(taskContext, file, parentNote) {
return note;
}
async function importPlainText(taskContext, file, parentNote) {
function importPlainText(taskContext, file, parentNote) {
const title = utils.getNoteTitle(file.originalname, taskContext.data.replaceUnderscoresWithSpaces);
const plainTextContent = file.buffer.toString("UTF-8");
const htmlContent = convertTextToHtml(plainTextContent);
const {note} = await noteService.createNewNote({
const {note} = noteService.createNewNote({
parentNoteId: parentNote.noteId,
title,
content: htmlContent,
@ -115,7 +115,7 @@ function convertTextToHtml(text) {
return text;
}
async function importMarkdown(taskContext, file, parentNote) {
function importMarkdown(taskContext, file, parentNote) {
const markdownContent = file.buffer.toString("UTF-8");
const reader = new commonmark.Parser();
@ -126,7 +126,7 @@ async function importMarkdown(taskContext, file, parentNote) {
const title = utils.getNoteTitle(file.originalname, taskContext.data.replaceUnderscoresWithSpaces);
const {note} = await noteService.createNewNote({
const {note} = noteService.createNewNote({
parentNoteId: parentNote.noteId,
title,
content: htmlContent,
@ -140,11 +140,11 @@ async function importMarkdown(taskContext, file, parentNote) {
return note;
}
async function importHtml(taskContext, file, parentNote) {
function importHtml(taskContext, file, parentNote) {
const title = utils.getNoteTitle(file.originalname, taskContext.data.replaceUnderscoresWithSpaces);
const content = file.buffer.toString("UTF-8");
const {note} = await noteService.createNewNote({
const {note} = noteService.createNewNote({
parentNoteId: parentNote.noteId,
title,
content,

View File

@ -22,7 +22,7 @@ const treeService = require("../tree");
* @param {Note} importRootNote
* @return {Promise<*>}
*/
async function importTar(taskContext, fileBuffer, importRootNote) {
function importTar(taskContext, fileBuffer, importRootNote) {
// maps from original noteId (in tar file) to newly generated noteId
const noteIdMap = {};
const attributes = [];
@ -77,7 +77,7 @@ async function importTar(taskContext, fileBuffer, importRootNote) {
};
}
async function getParentNoteId(filePath, parentNoteMeta) {
function getParentNoteId(filePath, parentNoteMeta) {
let parentNoteId;
if (parentNoteMeta) {
@ -95,7 +95,7 @@ async function importTar(taskContext, fileBuffer, importRootNote) {
else {
// tar allows creating out of order records - i.e. file in a directory can appear in the tar stream before actual directory
// (out-of-order-directory-records.tar in test set)
parentNoteId = await saveDirectory(parentPath);
parentNoteId = saveDirectory(parentPath);
}
}
@ -123,7 +123,7 @@ async function importTar(taskContext, fileBuffer, importRootNote) {
return { mime, type };
}
async function saveAttributes(note, noteMeta) {
function saveAttributes(note, noteMeta) {
if (!noteMeta) {
return;
}
@ -153,20 +153,20 @@ async function importTar(taskContext, fileBuffer, importRootNote) {
}
}
async function saveDirectory(filePath) {
function saveDirectory(filePath) {
const { parentNoteMeta, noteMeta } = getMeta(filePath);
const noteId = getNoteId(noteMeta, filePath);
const noteTitle = utils.getNoteTitle(filePath, taskContext.data.replaceUnderscoresWithSpaces, noteMeta);
const parentNoteId = await getParentNoteId(filePath, parentNoteMeta);
const parentNoteId = getParentNoteId(filePath, parentNoteMeta);
let note = await repository.getNote(noteId);
let note = repository.getNote(noteId);
if (note) {
return;
}
({note} = await noteService.createNewNote({
({note} = noteService.createNewNote({
parentNoteId: parentNoteId,
title: noteTitle,
content: '',
@ -178,7 +178,7 @@ async function importTar(taskContext, fileBuffer, importRootNote) {
isProtected: importRootNote.isProtected && protectedSessionService.isProtectedSessionAvailable(),
}));
await saveAttributes(note, noteMeta);
saveAttributes(note, noteMeta);
if (!firstNote) {
firstNote = note;
@ -211,7 +211,7 @@ async function importTar(taskContext, fileBuffer, importRootNote) {
return targetNoteId;
}
async function saveNote(filePath, content) {
function saveNote(filePath, content) {
const {parentNoteMeta, noteMeta} = getMeta(filePath);
if (noteMeta && noteMeta.noImport) {
@ -219,10 +219,10 @@ async function importTar(taskContext, fileBuffer, importRootNote) {
}
const noteId = getNoteId(noteMeta, filePath);
const parentNoteId = await getParentNoteId(filePath, parentNoteMeta);
const parentNoteId = getParentNoteId(filePath, parentNoteMeta);
if (noteMeta && noteMeta.isClone) {
await new Branch({
new Branch({
noteId,
parentNoteId,
isExpanded: noteMeta.isExpanded,
@ -300,13 +300,13 @@ async function importTar(taskContext, fileBuffer, importRootNote) {
}
}
let note = await repository.getNote(noteId);
let note = repository.getNote(noteId);
if (note) {
await note.setContent(content);
note.setContent(content);
}
else {
({note} = await noteService.createNewNote({
({note} = noteService.createNewNote({
parentNoteId: parentNoteId,
title: noteTitle,
content: content,
@ -319,7 +319,7 @@ async function importTar(taskContext, fileBuffer, importRootNote) {
isProtected: importRootNote.isProtected && protectedSessionService.isProtectedSessionAvailable(),
}));
await saveAttributes(note, noteMeta);
saveAttributes(note, noteMeta);
if (!firstNote) {
firstNote = note;
@ -366,7 +366,7 @@ async function importTar(taskContext, fileBuffer, importRootNote) {
// stream is the content body (might be an empty stream)
// call next when you are done with this entry
stream.on('end', async function() {
stream.on('end', function() {
const filePath = normalizeFilePath(header.name);
const content = Buffer.concat(chunks);
@ -375,10 +375,10 @@ async function importTar(taskContext, fileBuffer, importRootNote) {
metaFile = JSON.parse(content.toString("UTF-8"));
}
else if (header.type === 'directory') {
await saveDirectory(filePath);
saveDirectory(filePath);
}
else if (header.type === 'file') {
await saveNote(filePath, content);
saveNote(filePath, content);
}
else {
log.info("Ignoring tar import entry with type " + header.type);
@ -393,7 +393,7 @@ async function importTar(taskContext, fileBuffer, importRootNote) {
});
return new Promise(resolve => {
extract.on('finish', async function() {
extract.on('finish', function() {
const createdNoteIds = {};
for (const path in createdPaths) {
@ -403,12 +403,12 @@ async function importTar(taskContext, fileBuffer, importRootNote) {
}
for (const noteId in createdNoteIds) { // now the noteIds are unique
await noteService.scanForLinks(await repository.getNote(noteId));
noteService.scanForLinks(repository.getNote(noteId));
if (!metaFile) {
// if there's no meta file then the notes are created based on the order in that tar file but that
// is usually quite random so we sort the notes in the way they would appear in the file manager
await treeService.sortNotesAlphabetically(noteId, true);
treeService.sortNotesAlphabetically(noteId, true);
}
taskContext.increaseProgressCount();
@ -418,7 +418,7 @@ async function importTar(taskContext, fileBuffer, importRootNote) {
// are already in the database (we don't want to have "broken" relations, not even transitionally)
for (const attr of attributes) {
if (attr.type !== 'relation' || attr.value in createdNoteIds) {
await new Attribute(attr).save();
new Attribute(attr).save();
}
else {
log.info("Relation not imported since target note doesn't exist: " + JSON.stringify(attr));

View File

@ -21,7 +21,7 @@ const yauzl = require("yauzl");
* @param {Note} importRootNote
* @return {Promise<*>}
*/
async function importZip(taskContext, fileBuffer, importRootNote) {
function importZip(taskContext, fileBuffer, importRootNote) {
// maps from original noteId (in tar file) to newly generated noteId
const noteIdMap = {};
const attributes = [];
@ -75,7 +75,7 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
};
}
async function getParentNoteId(filePath, parentNoteMeta) {
function getParentNoteId(filePath, parentNoteMeta) {
let parentNoteId;
if (parentNoteMeta) {
@ -93,7 +93,7 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
else {
// tar allows creating out of order records - i.e. file in a directory can appear in the tar stream before actual directory
// (out-of-order-directory-records.tar in test set)
parentNoteId = await saveDirectory(parentPath);
parentNoteId = saveDirectory(parentPath);
}
}
@ -125,7 +125,7 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
return { mime, type };
}
async function saveAttributes(note, noteMeta) {
function saveAttributes(note, noteMeta) {
if (!noteMeta) {
return;
}
@ -155,20 +155,20 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
}
}
async function saveDirectory(filePath) {
function saveDirectory(filePath) {
const { parentNoteMeta, noteMeta } = getMeta(filePath);
const noteId = getNoteId(noteMeta, filePath);
const noteTitle = utils.getNoteTitle(filePath, taskContext.data.replaceUnderscoresWithSpaces, noteMeta);
const parentNoteId = await getParentNoteId(filePath, parentNoteMeta);
const parentNoteId = getParentNoteId(filePath, parentNoteMeta);
let note = await repository.getNote(noteId);
let note = repository.getNote(noteId);
if (note) {
return;
}
({note} = await noteService.createNewNote({
({note} = noteService.createNewNote({
parentNoteId: parentNoteId,
title: noteTitle,
content: '',
@ -182,7 +182,7 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
createdNoteIds[note.noteId] = true;
await saveAttributes(note, noteMeta);
saveAttributes(note, noteMeta);
if (!firstNote) {
firstNote = note;
@ -215,7 +215,7 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
return targetNoteId;
}
async function saveNote(filePath, content) {
function saveNote(filePath, content) {
const {parentNoteMeta, noteMeta} = getMeta(filePath);
if (noteMeta && noteMeta.noImport) {
@ -223,14 +223,14 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
}
const noteId = getNoteId(noteMeta, filePath);
const parentNoteId = await getParentNoteId(filePath, parentNoteMeta);
const parentNoteId = getParentNoteId(filePath, parentNoteMeta);
if (!parentNoteId) {
throw new Error(`Cannot find parentNoteId for ${filePath}`);
}
if (noteMeta && noteMeta.isClone) {
await new Branch({
new Branch({
noteId,
parentNoteId,
isExpanded: noteMeta.isExpanded,
@ -318,13 +318,13 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
}
}
let note = await repository.getNote(noteId);
let note = repository.getNote(noteId);
if (note) {
await note.setContent(content);
note.setContent(content);
}
else {
({note} = await noteService.createNewNote({
({note} = noteService.createNewNote({
parentNoteId: parentNoteId,
title: noteTitle,
content: content,
@ -339,7 +339,7 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
createdNoteIds[note.noteId] = true;
await saveAttributes(note, noteMeta);
saveAttributes(note, noteMeta);
if (!firstNote) {
firstNote = note;
@ -405,11 +405,11 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
// we're running two passes to make sure that the meta file is loaded before the rest of the files is processed.
await readZipFile(fileBuffer, async (zipfile, entry) => {
readZipFile(fileBuffer, (zipfile, entry) => {
const filePath = normalizeFilePath(entry.fileName);
if (filePath === '!!!meta.json') {
const content = await readContent(zipfile, entry);
const content = readContent(zipfile, entry);
metaFile = JSON.parse(content.toString("UTF-8"));
}
@ -417,16 +417,16 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
zipfile.readEntry();
});
await readZipFile(fileBuffer, async (zipfile, entry) => {
readZipFile(fileBuffer, (zipfile, entry) => {
const filePath = normalizeFilePath(entry.fileName);
if (/\/$/.test(entry.fileName)) {
await saveDirectory(filePath);
saveDirectory(filePath);
}
else if (filePath !== '!!!meta.json') {
const content = await readContent(zipfile, entry);
const content = readContent(zipfile, entry);
await saveNote(filePath, content);
saveNote(filePath, content);
}
taskContext.increaseProgressCount();
@ -434,12 +434,12 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
});
for (const noteId in createdNoteIds) { // now the noteIds are unique
await noteService.scanForLinks(await repository.getNote(noteId));
noteService.scanForLinks(repository.getNote(noteId));
if (!metaFile) {
// if there's no meta file then the notes are created based on the order in that tar file but that
// is usually quite random so we sort the notes in the way they would appear in the file manager
await treeService.sortNotesAlphabetically(noteId, true);
treeService.sortNotesAlphabetically(noteId, true);
}
taskContext.increaseProgressCount();
@ -449,7 +449,7 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
// are already in the database (we don't want to have "broken" relations, not even transitionally)
for (const attr of attributes) {
if (attr.type !== 'relation' || attr.value in createdNoteIds) {
await new Attribute(attr).save();
new Attribute(attr).save();
}
else {
log.info("Relation not imported since target note doesn't exist: " + JSON.stringify(attr));

View File

@ -391,14 +391,14 @@ for (const action of DEFAULT_KEYBOARD_ACTIONS) {
}
}
async function getKeyboardActions() {
function getKeyboardActions() {
const actions = JSON.parse(JSON.stringify(DEFAULT_KEYBOARD_ACTIONS));
for (const action of actions) {
action.effectiveShortcuts = action.effectiveShortcuts ? action.defaultShortcuts.slice() : [];
}
for (const option of await optionService.getOptions()) {
for (const option of optionService.getOptions()) {
if (option.name.startsWith('keyboardShortcuts')) {
let actionName = option.name.substr(17);
actionName = actionName.charAt(0).toLowerCase() + actionName.slice(1);

View File

@ -7,13 +7,13 @@ const log = require('./log');
const utils = require('./utils');
const resourceDir = require('./resource_dir');
async function migrate() {
function migrate() {
const migrations = [];
// backup before attempting migration
await backupService.backupNow("before-migration");
backupService.backupNow("before-migration");
const currentDbVersion = parseInt(await optionService.getOption('dbVersion'));
const currentDbVersion = parseInt(optionService.getOption('dbVersion'));
fs.readdirSync(resourceDir.MIGRATIONS_DIR).forEach(file => {
const match = file.match(/([0-9]{4})__([a-zA-Z0-9_ ]+)\.(sql|js)/);
@ -43,26 +43,26 @@ async function migrate() {
try {
log.info("Attempting migration to version " + mig.dbVersion);
await sql.transactional(async () => {
sql.transactional(() => {
if (mig.type === 'sql') {
const migrationSql = fs.readFileSync(resourceDir.MIGRATIONS_DIR + "/" + mig.file).toString('utf8');
console.log("Migration with SQL script: " + migrationSql);
await sql.executeScript(migrationSql);
sql.executeScript(migrationSql);
}
else if (mig.type === 'js') {
console.log("Migration with JS module");
const migrationModule = require(resourceDir.MIGRATIONS_DIR + "/" + mig.file);
await migrationModule();
migrationModule();
}
else {
throw new Error("Unknown migration type " + mig.type);
}
// not using repository because of changed utcDateModified column in migration 129
await sql.execute(`UPDATE options SET value = ? WHERE name = ?`, [mig.dbVersion, "dbVersion"]);
sql.execute(`UPDATE options SET value = ? WHERE name = ?`, [mig.dbVersion, "dbVersion"]);
});
log.info("Migration to version " + mig.dbVersion + " has been successful.");
@ -75,8 +75,8 @@ async function migrate() {
}
}
if (await sqlInit.isDbUpToDate()) {
await sqlInit.initDbConnection();
if (sqlInit.isDbUpToDate()) {
sqlInit.initDbConnection();
}
}

View File

@ -3,19 +3,19 @@
const optionService = require('./options');
const crypto = require('crypto');
async function getVerificationHash(password) {
const salt = await optionService.getOption('passwordVerificationSalt');
function getVerificationHash(password) {
const salt = optionService.getOption('passwordVerificationSalt');
return getScryptHash(password, salt);
}
async function getPasswordDerivedKey(password) {
const salt = await optionService.getOption('passwordDerivedKeySalt');
function getPasswordDerivedKey(password) {
const salt = optionService.getOption('passwordDerivedKeySalt');
return getScryptHash(password, salt);
}
async function getScryptHash(password, salt) {
function getScryptHash(password, salt) {
const hashed = crypto.scryptSync(password, salt, 32,
{N: 16384, r:8, p:1});

View File

@ -1,31 +1,28 @@
"use strict";
const sql = require('../sql.js');
const sqlInit = require('../sql_init.js');
const eventService = require('../events.js');
const noteCache = require('./note_cache');
const Note = require('./entities/note');
const Branch = require('./entities/branch');
const Attribute = require('./entities/attribute');
async function load() {
await sqlInit.dbReady;
function load() {
noteCache.reset();
(await sql.getRows(`SELECT noteId, title, type, mime, isProtected, dateCreated, dateModified, utcDateCreated, utcDateModified, contentLength FROM notes WHERE isDeleted = 0`, []))
sql.getRows(`SELECT noteId, title, type, mime, isProtected, dateCreated, dateModified, utcDateCreated, utcDateModified, contentLength FROM notes WHERE isDeleted = 0`, [])
.map(row => new Note(noteCache, row));
(await sql.getRows(`SELECT branchId, noteId, parentNoteId, prefix FROM branches WHERE isDeleted = 0`, []))
sql.getRows(`SELECT branchId, noteId, parentNoteId, prefix FROM branches WHERE isDeleted = 0`, [])
.map(row => new Branch(noteCache, row));
(await sql.getRows(`SELECT attributeId, noteId, type, name, value, isInheritable FROM attributes WHERE isDeleted = 0`, [])).map(row => new Attribute(noteCache, row));
sql.getRows(`SELECT attributeId, noteId, type, name, value, isInheritable FROM attributes WHERE isDeleted = 0`, []).map(row => new Attribute(noteCache, row));
noteCache.loaded = true;
noteCache.loadedResolve();
}
eventService.subscribe([eventService.ENTITY_CHANGED, eventService.ENTITY_DELETED, eventService.ENTITY_SYNCED], async ({entityName, entity}) => {
eventService.subscribe([eventService.ENTITY_CHANGED, eventService.ENTITY_DELETED, eventService.ENTITY_SYNCED], ({entityName, entity}) => {
// note that entity can also be just POJO without methods if coming from sync
if (!noteCache.loaded) {
@ -150,4 +147,5 @@ eventService.subscribe(eventService.ENTER_PROTECTED_SESSION, () => {
noteCache.loadedPromise.then(() => noteCache.decryptProtectedNotes());
});
load();
// FIXME
// load();

View File

@ -191,7 +191,7 @@ function setImmediatePromise() {
});
}
async function findSimilarNotes(noteId) {
function findSimilarNotes(noteId) {
const results = [];
let i = 0;
@ -211,7 +211,7 @@ async function findSimilarNotes(noteId) {
i++;
if (i % 200 === 0) {
await setImmediatePromise();
setImmediatePromise();
}
}

View File

@ -6,17 +6,17 @@ const dateUtils = require('../services/date_utils');
/**
* @param {Note} note
*/
async function protectNoteRevisions(note) {
for (const revision of await note.getRevisions()) {
function protectNoteRevisions(note) {
for (const revision of note.getRevisions()) {
if (note.isProtected !== revision.isProtected) {
const content = await revision.getContent();
const content = revision.getContent();
revision.isProtected = note.isProtected;
// this will force de/encryption
await revision.setContent(content);
revision.setContent(content);
await revision.save();
revision.save();
}
}
}
@ -25,12 +25,12 @@ async function protectNoteRevisions(note) {
* @param {Note} note
* @return {NoteRevision}
*/
async function createNoteRevision(note) {
if (await note.hasLabel("disableVersioning")) {
function createNoteRevision(note) {
if (note.hasLabel("disableVersioning")) {
return;
}
const noteRevision = await new NoteRevision({
const noteRevision = new NoteRevision({
noteId: note.noteId,
// title and text should be decrypted now
title: note.title,
@ -45,7 +45,7 @@ async function createNoteRevision(note) {
dateCreated: dateUtils.localNowDateTime()
}).save();
await noteRevision.setContent(await note.getContent());
noteRevision.setContent(note.getContent());
return noteRevision;
}

View File

@ -20,8 +20,8 @@ const request = require('./request');
const path = require('path');
const url = require('url');
async function getNewNotePosition(parentNoteId) {
const maxNotePos = await sql.getValue(`
function getNewNotePosition(parentNoteId) {
const maxNotePos = sql.getValue(`
SELECT MAX(notePosition)
FROM branches
WHERE parentNoteId = ?
@ -30,12 +30,12 @@ async function getNewNotePosition(parentNoteId) {
return maxNotePos === null ? 0 : maxNotePos + 10;
}
async function triggerChildNoteCreated(childNote, parentNote) {
await eventService.emit(eventService.CHILD_NOTE_CREATED, { childNote, parentNote });
function triggerChildNoteCreated(childNote, parentNote) {
eventService.emit(eventService.CHILD_NOTE_CREATED, { childNote, parentNote });
}
async function triggerNoteTitleChanged(note) {
await eventService.emit(eventService.NOTE_TITLE_CHANGED, note);
function triggerNoteTitleChanged(note) {
eventService.emit(eventService.NOTE_TITLE_CHANGED, note);
}
function deriveMime(type, mime) {
@ -60,10 +60,10 @@ function deriveMime(type, mime) {
return mime;
}
async function copyChildAttributes(parentNote, childNote) {
for (const attr of await parentNote.getOwnedAttributes()) {
function copyChildAttributes(parentNote, childNote) {
for (const attr of parentNote.getOwnedAttributes()) {
if (attr.name.startsWith("child:")) {
await new Attribute({
new Attribute({
noteId: childNote.noteId,
type: attr.type,
name: attr.name.substr(6),
@ -94,8 +94,8 @@ async function copyChildAttributes(parentNote, childNote) {
* @param params
* @return {Promise<{note: Note, branch: Branch}>}
*/
async function createNewNote(params) {
const parentNote = await repository.getNote(params.parentNoteId);
function createNewNote(params) {
const parentNote = repository.getNote(params.parentNoteId);
if (!parentNote) {
throw new Error(`Parent note "${params.parentNoteId}" not found.`);
@ -105,7 +105,7 @@ async function createNewNote(params) {
throw new Error(`Note title must not be empty`);
}
const note = await new Note({
const note = new Note({
noteId: params.noteId, // optionally can force specific noteId
title: params.title,
isProtected: !!params.isProtected,
@ -113,22 +113,22 @@ async function createNewNote(params) {
mime: deriveMime(params.type, params.mime)
}).save();
await note.setContent(params.content);
note.setContent(params.content);
const branch = await new Branch({
const branch = new Branch({
noteId: note.noteId,
parentNoteId: params.parentNoteId,
notePosition: params.notePosition !== undefined ? params.notePosition : await getNewNotePosition(params.parentNoteId),
notePosition: params.notePosition !== undefined ? params.notePosition : getNewNotePosition(params.parentNoteId),
prefix: params.prefix,
isExpanded: !!params.isExpanded
}).save();
await scanForLinks(note);
scanForLinks(note);
await copyChildAttributes(parentNote, note);
copyChildAttributes(parentNote, note);
await triggerNoteTitleChanged(note);
await triggerChildNoteCreated(note, parentNote);
triggerNoteTitleChanged(note);
triggerChildNoteCreated(note, parentNote);
return {
note,
@ -136,9 +136,9 @@ async function createNewNote(params) {
};
}
async function createNewNoteWithTarget(target, targetBranchId, params) {
function createNewNoteWithTarget(target, targetBranchId, params) {
if (!params.type) {
const parentNote = await repository.getNote(params.parentNoteId);
const parentNote = repository.getNote(params.parentNoteId);
// code note type can be inherited, otherwise text is default
params.type = parentNote.type === 'code' ? 'code' : 'text';
@ -146,20 +146,20 @@ async function createNewNoteWithTarget(target, targetBranchId, params) {
}
if (target === 'into') {
return await createNewNote(params);
return createNewNote(params);
}
else if (target === 'after') {
const afterNote = await sql.getRow('SELECT notePosition FROM branches WHERE branchId = ?', [targetBranchId]);
const afterNote = sql.getRow('SELECT notePosition FROM branches WHERE branchId = ?', [targetBranchId]);
// not updating utcDateModified to avoig having to sync whole rows
await sql.execute('UPDATE branches SET notePosition = notePosition + 10 WHERE parentNoteId = ? AND notePosition > ? AND isDeleted = 0',
sql.execute('UPDATE branches SET notePosition = notePosition + 10 WHERE parentNoteId = ? AND notePosition > ? AND isDeleted = 0',
[params.parentNoteId, afterNote.notePosition]);
params.notePosition = afterNote.notePosition + 10;
const retObject = await createNewNote(params);
const retObject = createNewNote(params);
await syncTableService.addNoteReorderingSync(params.parentNoteId);
syncTableService.addNoteReorderingSync(params.parentNoteId);
return retObject;
}
@ -168,31 +168,31 @@ async function createNewNoteWithTarget(target, targetBranchId, params) {
}
}
async function protectNoteRecursively(note, protect, includingSubTree, taskContext) {
await protectNote(note, protect);
function protectNoteRecursively(note, protect, includingSubTree, taskContext) {
protectNote(note, protect);
taskContext.increaseProgressCount();
if (includingSubTree) {
for (const child of await note.getChildNotes()) {
await protectNoteRecursively(child, protect, includingSubTree, taskContext);
for (const child of note.getChildNotes()) {
protectNoteRecursively(child, protect, includingSubTree, taskContext);
}
}
}
async function protectNote(note, protect) {
function protectNote(note, protect) {
if (protect !== note.isProtected) {
const content = await note.getContent();
const content = note.getContent();
note.isProtected = protect;
// this will force de/encryption
await note.setContent(content);
note.setContent(content);
await note.save();
note.save();
}
await noteRevisionService.protectNoteRevisions(note);
noteRevisionService.protectNoteRevisions(note);
}
function findImageLinks(content, foundLinks) {
@ -260,9 +260,9 @@ async function downloadImage(noteId, imageUrl) {
const title = path.basename(parsedUrl.pathname);
const imageService = require('../services/image');
const {note} = await imageService.saveImage(noteId, imageBuffer, title, true);
const {note} = imageService.saveImage(noteId, imageBuffer, title, true);
await note.addLabel('imageUrl', imageUrl);
note.addLabel('imageUrl', imageUrl);
imageUrlToNoteIdMapping[imageUrl] = note.noteId;
@ -282,7 +282,7 @@ function replaceUrl(content, url, imageNote) {
return content.replace(new RegExp(`\\s+src=[\"']${quotedUrl}[\"']`, "g"), ` src="api/images/${imageNote.noteId}/${imageNote.title}"`);
}
async function downloadImages(noteId, content) {
function downloadImages(noteId, content) {
const re = /<img[^>]*?\ssrc=['"]([^'">]+)['"]/ig;
let match;
@ -296,7 +296,7 @@ async function downloadImages(noteId, content) {
&& (url.length !== 20 || url.toLowerCase().startsWith('http'))) {
if (url in imageUrlToNoteIdMapping) {
const imageNote = await repository.getNote(imageUrlToNoteIdMapping[url]);
const imageNote = repository.getNote(imageUrlToNoteIdMapping[url]);
if (!imageNote || imageNote.isDeleted) {
delete imageUrlToNoteIdMapping[url];
@ -308,7 +308,7 @@ async function downloadImages(noteId, content) {
}
}
const existingImage = (await attributeService.getNotesWithLabel('imageUrl', url))
const existingImage = (attributeService.getNotesWithLabel('imageUrl', url))
.find(note => note.type === 'image');
if (existingImage) {
@ -331,7 +331,7 @@ async function downloadImages(noteId, content) {
}
Promise.all(Object.values(downloadImagePromises)).then(() => {
setTimeout(async () => {
setTimeout(() => {
// the normal expected flow of the offline image saving is that users will paste the image(s)
// which will get asynchronously downloaded, during that time they keep editing the note
// once the download is finished, the image note representing downloaded image will be used
@ -340,11 +340,11 @@ async function downloadImages(noteId, content) {
// are downloaded and the IMG references are not updated. For this occassion we have this code
// which upon the download of all the images will update the note if the links have not been fixed before
await sql.transactional(async () => {
const imageNotes = await repository.getNotes(Object.values(imageUrlToNoteIdMapping));
sql.transactional(() => {
const imageNotes = repository.getNotes(Object.values(imageUrlToNoteIdMapping));
const origNote = await repository.getNote(noteId);
const origContent = await origNote.getContent();
const origNote = repository.getNote(noteId);
const origContent = origNote.getContent();
let updatedContent = origContent;
for (const url in imageUrlToNoteIdMapping) {
@ -357,9 +357,9 @@ async function downloadImages(noteId, content) {
// update only if the links have not been already fixed.
if (updatedContent !== origContent) {
await origNote.setContent(updatedContent);
origNote.setContent(updatedContent);
await scanForLinks(origNote);
scanForLinks(origNote);
console.log(`Fixed the image links for note ${noteId} to the offline saved.`);
}
@ -370,7 +370,7 @@ async function downloadImages(noteId, content) {
return content;
}
async function saveLinks(note, content) {
function saveLinks(note, content) {
if (note.type !== 'text' && note.type !== 'relation-map') {
return content;
}
@ -382,7 +382,7 @@ async function saveLinks(note, content) {
const foundLinks = [];
if (note.type === 'text') {
content = await downloadImages(note.noteId, content);
content = downloadImages(note.noteId, content);
content = findImageLinks(content, foundLinks);
content = findInternalLinks(content, foundLinks);
@ -395,10 +395,10 @@ async function saveLinks(note, content) {
throw new Error("Unrecognized type " + note.type);
}
const existingLinks = await note.getLinks();
const existingLinks = note.getLinks();
for (const foundLink of foundLinks) {
const targetNote = await repository.getNote(foundLink.value);
const targetNote = repository.getNote(foundLink.value);
if (!targetNote || targetNote.isDeleted) {
continue;
}
@ -408,7 +408,7 @@ async function saveLinks(note, content) {
&& existingLink.name === foundLink.name);
if (!existingLink) {
const newLink = await new Attribute({
const newLink = new Attribute({
noteId: note.noteId,
type: 'relation',
name: foundLink.name,
@ -419,7 +419,7 @@ async function saveLinks(note, content) {
}
else if (existingLink.isDeleted) {
existingLink.isDeleted = false;
await existingLink.save();
existingLink.save();
}
// else the link exists so we don't need to do anything
}
@ -431,30 +431,30 @@ async function saveLinks(note, content) {
for (const unusedLink of unusedLinks) {
unusedLink.isDeleted = true;
await unusedLink.save();
unusedLink.save();
}
return content;
}
async function saveNoteRevision(note) {
function saveNoteRevision(note) {
// files and images are versioned separately
if (note.type === 'file' || note.type === 'image' || await note.hasLabel('disableVersioning')) {
if (note.type === 'file' || note.type === 'image' || note.hasLabel('disableVersioning')) {
return;
}
const now = new Date();
const noteRevisionSnapshotTimeInterval = parseInt(await optionService.getOption('noteRevisionSnapshotTimeInterval'));
const noteRevisionSnapshotTimeInterval = parseInt(optionService.getOption('noteRevisionSnapshotTimeInterval'));
const revisionCutoff = dateUtils.utcDateStr(new Date(now.getTime() - noteRevisionSnapshotTimeInterval * 1000));
const existingNoteRevisionId = await sql.getValue(
const existingNoteRevisionId = sql.getValue(
"SELECT noteRevisionId FROM note_revisions WHERE noteId = ? AND utcDateCreated >= ?", [note.noteId, revisionCutoff]);
const msSinceDateCreated = now.getTime() - dateUtils.parseDateTime(note.utcDateCreated).getTime();
if (!existingNoteRevisionId && msSinceDateCreated >= noteRevisionSnapshotTimeInterval * 1000) {
const noteRevision = await new NoteRevision({
const noteRevision = new NoteRevision({
noteId: note.noteId,
// title and text should be decrypted now
title: note.title,
@ -469,41 +469,41 @@ async function saveNoteRevision(note) {
dateCreated: dateUtils.localNowDateTime()
}).save();
await noteRevision.setContent(await note.getContent());
noteRevision.setContent(note.getContent());
}
}
async function updateNote(noteId, noteUpdates) {
const note = await repository.getNote(noteId);
function updateNote(noteId, noteUpdates) {
const note = repository.getNote(noteId);
if (!note.isContentAvailable) {
throw new Error(`Note ${noteId} is not available for change!`);
}
await saveNoteRevision(note);
saveNoteRevision(note);
// if protected status changed, then we need to encrypt/decrypt the content anyway
if (['file', 'image'].includes(note.type) && note.isProtected !== noteUpdates.isProtected) {
noteUpdates.content = await note.getContent();
noteUpdates.content = note.getContent();
}
const noteTitleChanged = note.title !== noteUpdates.title;
note.title = noteUpdates.title;
note.isProtected = noteUpdates.isProtected;
await note.save();
note.save();
if (noteUpdates.content !== undefined && noteUpdates.content !== null) {
noteUpdates.content = await saveLinks(note, noteUpdates.content);
noteUpdates.content = saveLinks(note, noteUpdates.content);
await note.setContent(noteUpdates.content);
note.setContent(noteUpdates.content);
}
if (noteTitleChanged) {
await triggerNoteTitleChanged(note);
triggerNoteTitleChanged(note);
}
await noteRevisionService.protectNoteRevisions(note);
noteRevisionService.protectNoteRevisions(note);
return {
dateModified: note.dateModified,
@ -518,7 +518,7 @@ async function updateNote(noteId, noteUpdates) {
*
* @return {boolean} - true if note has been deleted, false otherwise
*/
async function deleteBranch(branch, deleteId, taskContext) {
function deleteBranch(branch, deleteId, taskContext) {
taskContext.increaseProgressCount();
if (!branch || branch.isDeleted) {
@ -527,41 +527,41 @@ async function deleteBranch(branch, deleteId, taskContext) {
if (branch.branchId === 'root'
|| branch.noteId === 'root'
|| branch.noteId === await hoistedNoteService.getHoistedNoteId()) {
|| branch.noteId === hoistedNoteService.getHoistedNoteId()) {
throw new Error("Can't delete root branch/note");
}
branch.isDeleted = true;
branch.deleteId = deleteId;
await branch.save();
branch.save();
const note = await branch.getNote();
const notDeletedBranches = await note.getBranches();
const note = branch.getNote();
const notDeletedBranches = note.getBranches();
if (notDeletedBranches.length === 0) {
for (const childBranch of await note.getChildBranches()) {
await deleteBranch(childBranch, deleteId, taskContext);
for (const childBranch of note.getChildBranches()) {
deleteBranch(childBranch, deleteId, taskContext);
}
// first delete children and then parent - this will show up better in recent changes
note.isDeleted = true;
note.deleteId = deleteId;
await note.save();
note.save();
log.info("Deleting note " + note.noteId);
for (const attribute of await note.getOwnedAttributes()) {
for (const attribute of note.getOwnedAttributes()) {
attribute.isDeleted = true;
attribute.deleteId = deleteId;
await attribute.save();
attribute.save();
}
for (const relation of await note.getTargetRelations()) {
for (const relation of note.getTargetRelations()) {
relation.isDeleted = true;
relation.deleteId = deleteId;
await relation.save();
relation.save();
}
return true;
@ -576,8 +576,8 @@ async function deleteBranch(branch, deleteId, taskContext) {
* @param {string} deleteId
* @param {TaskContext} taskContext
*/
async function undeleteNote(note, deleteId, taskContext) {
const undeletedParentBranches = await getUndeletedParentBranches(note.noteId, deleteId);
function undeleteNote(note, deleteId, taskContext) {
const undeletedParentBranches = getUndeletedParentBranches(note.noteId, deleteId);
if (undeletedParentBranches.length === 0) {
// cannot undelete if there's no undeleted parent
@ -585,7 +585,7 @@ async function undeleteNote(note, deleteId, taskContext) {
}
for (const parentBranch of undeletedParentBranches) {
await undeleteBranch(parentBranch, deleteId, taskContext);
undeleteBranch(parentBranch, deleteId, taskContext);
}
}
@ -594,27 +594,27 @@ async function undeleteNote(note, deleteId, taskContext) {
* @param {string} deleteId
* @param {TaskContext} taskContext
*/
async function undeleteBranch(branch, deleteId, taskContext) {
function undeleteBranch(branch, deleteId, taskContext) {
if (!branch.isDeleted) {
return;
}
const note = await branch.getNote();
const note = branch.getNote();
if (note.isDeleted && note.deleteId !== deleteId) {
return;
}
branch.isDeleted = false;
await branch.save();
branch.save();
taskContext.increaseProgressCount();
if (note.isDeleted && note.deleteId === deleteId) {
note.isDeleted = false;
await note.save();
note.save();
const attrs = await repository.getEntities(`
const attrs = repository.getEntities(`
SELECT * FROM attributes
WHERE isDeleted = 1
AND deleteId = ?
@ -623,10 +623,10 @@ async function undeleteBranch(branch, deleteId, taskContext) {
for (const attr of attrs) {
attr.isDeleted = false;
await attr.save();
attr.save();
}
const childBranches = await repository.getEntities(`
const childBranches = repository.getEntities(`
SELECT branches.*
FROM branches
WHERE branches.isDeleted = 1
@ -634,7 +634,7 @@ async function undeleteBranch(branch, deleteId, taskContext) {
AND branches.parentNoteId = ?`, [deleteId, note.noteId]);
for (const childBranch of childBranches) {
await undeleteBranch(childBranch, deleteId, taskContext);
undeleteBranch(childBranch, deleteId, taskContext);
}
}
}
@ -642,8 +642,8 @@ async function undeleteBranch(branch, deleteId, taskContext) {
/**
* @return return deleted branches of an undeleted parent note
*/
async function getUndeletedParentBranches(noteId, deleteId) {
return await repository.getEntities(`
function getUndeletedParentBranches(noteId, deleteId) {
return repository.getEntities(`
SELECT branches.*
FROM branches
JOIN notes AS parentNote ON parentNote.noteId = branches.parentNoteId
@ -653,17 +653,17 @@ async function getUndeletedParentBranches(noteId, deleteId) {
AND parentNote.isDeleted = 0`, [noteId, deleteId]);
}
async function scanForLinks(note) {
function scanForLinks(note) {
if (!note || !['text', 'relation-map'].includes(note.type)) {
return;
}
try {
const content = await note.getContent();
const newContent = await saveLinks(note, content);
const content = note.getContent();
const newContent = saveLinks(note, content);
if (content !== newContent) {
await note.setContent(newContent);
note.setContent(newContent);
}
}
catch (e) {
@ -671,12 +671,12 @@ async function scanForLinks(note) {
}
}
async function eraseDeletedNotes() {
const eraseNotesAfterTimeInSeconds = await optionService.getOptionInt('eraseNotesAfterTimeInSeconds');
function eraseDeletedNotes() {
const eraseNotesAfterTimeInSeconds = optionService.getOptionInt('eraseNotesAfterTimeInSeconds');
const cutoffDate = new Date(Date.now() - eraseNotesAfterTimeInSeconds * 1000);
const noteIdsToErase = await sql.getColumn("SELECT noteId FROM notes WHERE isDeleted = 1 AND isErased = 0 AND notes.utcDateModified <= ?", [dateUtils.utcDateStr(cutoffDate)]);
const noteIdsToErase = sql.getColumn("SELECT noteId FROM notes WHERE isDeleted = 1 AND isErased = 0 AND notes.utcDateModified <= ?", [dateUtils.utcDateStr(cutoffDate)]);
if (noteIdsToErase.length === 0) {
return;
@ -688,7 +688,7 @@ async function eraseDeletedNotes() {
// - we don't want change the hash since this erasing happens on each instance separately
// and changing the hash would fire up the sync errors temporarily
await sql.executeMany(`
sql.executeMany(`
UPDATE notes
SET title = '[deleted]',
contentLength = 0,
@ -696,26 +696,26 @@ async function eraseDeletedNotes() {
isErased = 1
WHERE noteId IN (???)`, noteIdsToErase);
await sql.executeMany(`
sql.executeMany(`
UPDATE note_contents
SET content = NULL
WHERE noteId IN (???)`, noteIdsToErase);
// deleting first contents since the WHERE relies on isErased = 0
await sql.executeMany(`
sql.executeMany(`
UPDATE note_revision_contents
SET content = NULL
WHERE noteRevisionId IN
(SELECT noteRevisionId FROM note_revisions WHERE isErased = 0 AND noteId IN (???))`, noteIdsToErase);
await sql.executeMany(`
sql.executeMany(`
UPDATE note_revisions
SET isErased = 1,
title = NULL,
contentLength = 0
WHERE isErased = 0 AND noteId IN (???)`, noteIdsToErase);
await sql.executeMany(`
sql.executeMany(`
UPDATE attributes
SET name = 'deleted',
value = ''
@ -724,36 +724,36 @@ async function eraseDeletedNotes() {
log.info(`Erased notes: ${JSON.stringify(noteIdsToErase)}`);
}
async function duplicateNote(noteId, parentNoteId) {
const origNote = await repository.getNote(noteId);
function duplicateNote(noteId, parentNoteId) {
const origNote = repository.getNote(noteId);
if (origNote.isProtected && !protectedSessionService.isProtectedSessionAvailable()) {
throw new Error(`Cannot duplicate note=${origNote.noteId} because it is protected and protected session is not available`);
}
// might be null if orig note is not in the target parentNoteId
const origBranch = (await origNote.getBranches()).find(branch => branch.parentNoteId === parentNoteId);
const origBranch = (origNote.getBranches()).find(branch => branch.parentNoteId === parentNoteId);
const newNote = new Note(origNote);
newNote.noteId = undefined; // force creation of new note
newNote.title += " (dup)";
await newNote.save();
await newNote.setContent(await origNote.getContent());
newNote.save();
newNote.setContent(origNote.getContent());
const newBranch = await new Branch({
const newBranch = new Branch({
noteId: newNote.noteId,
parentNoteId: parentNoteId,
// here increasing just by 1 to make sure it's directly after original
notePosition: origBranch ? origBranch.notePosition + 1 : null
}).save();
for (const attribute of await origNote.getOwnedAttributes()) {
for (const attribute of origNote.getOwnedAttributes()) {
const attr = new Attribute(attribute);
attr.attributeId = undefined; // force creation of new attribute
attr.noteId = newNote.noteId;
await attr.save();
attr.save();
}
return {
@ -762,12 +762,10 @@ async function duplicateNote(noteId, parentNoteId) {
};
}
sqlInit.dbReady.then(() => {
// first cleanup kickoff 5 minutes after startup
setTimeout(cls.wrap(eraseDeletedNotes), 5 * 60 * 1000);
// first cleanup kickoff 5 minutes after startup
setTimeout(cls.wrap(eraseDeletedNotes), 5 * 60 * 1000);
setInterval(cls.wrap(eraseDeletedNotes), 4 * 3600 * 1000);
});
setInterval(cls.wrap(eraseDeletedNotes), 4 * 3600 * 1000);
module.exports = {
createNewNote,

View File

@ -1,7 +1,7 @@
const utils = require('./utils');
async function getOption(name) {
const option = await require('./repository').getOption(name);
function getOption(name) {
const option = require('./repository').getOption(name);
if (!option) {
throw new Error(`Option ${name} doesn't exist`);
@ -13,8 +13,8 @@ async function getOption(name) {
/**
* @return {Promise<number>}
*/
async function getOptionInt(name) {
const val = await getOption(name);
function getOptionInt(name) {
const val = getOption(name);
const intVal = parseInt(val);
@ -28,8 +28,8 @@ async function getOptionInt(name) {
/**
* @return {Promise<boolean>}
*/
async function getOptionBool(name) {
const val = await getOption(name);
function getOptionBool(name) {
const val = getOption(name);
if (!['true', 'false'].includes(val)) {
throw new Error(`Could not parse "${val}" into boolean for option "${name}"`);
@ -38,36 +38,36 @@ async function getOptionBool(name) {
return val === 'true';
}
async function setOption(name, value) {
const option = await require('./repository').getOption(name);
function setOption(name, value) {
const option = require('./repository').getOption(name);
if (option) {
option.value = value;
await option.save();
option.save();
}
else {
await createOption(name, value, false);
createOption(name, value, false);
}
}
async function createOption(name, value, isSynced) {
function createOption(name, value, isSynced) {
// to avoid circular dependency, need to find better solution
const Option = require('../entities/option');
await new Option({
new Option({
name: name,
value: value,
isSynced: isSynced
}).save();
}
async function getOptions() {
return await require('./repository').getEntities("SELECT * FROM options ORDER BY name");
function getOptions() {
return require('./repository').getEntities("SELECT * FROM options ORDER BY name");
}
async function getOptionsMap() {
const options = await getOptions();
function getOptionsMap() {
const options = getOptions();
return utils.toObject(options, opt => [opt.name, opt.value]);
}

View File

@ -7,28 +7,28 @@ const log = require('./log');
const dateUtils = require('./date_utils');
const keyboardActions = require('./keyboard_actions');
async function initDocumentOptions() {
await optionService.createOption('documentId', utils.randomSecureToken(16), false);
await optionService.createOption('documentSecret', utils.randomSecureToken(16), false);
function initDocumentOptions() {
optionService.createOption('documentId', utils.randomSecureToken(16), false);
optionService.createOption('documentSecret', utils.randomSecureToken(16), false);
}
async function initSyncedOptions(username, password) {
await optionService.createOption('username', username, true);
function initSyncedOptions(username, password) {
optionService.createOption('username', username, true);
await optionService.createOption('passwordVerificationSalt', utils.randomSecureToken(32), true);
await optionService.createOption('passwordDerivedKeySalt', utils.randomSecureToken(32), true);
optionService.createOption('passwordVerificationSalt', utils.randomSecureToken(32), true);
optionService.createOption('passwordDerivedKeySalt', utils.randomSecureToken(32), true);
const passwordVerificationKey = utils.toBase64(await myScryptService.getVerificationHash(password), true);
await optionService.createOption('passwordVerificationHash', passwordVerificationKey, true);
const passwordVerificationKey = utils.toBase64(myScryptService.getVerificationHash(password), true);
optionService.createOption('passwordVerificationHash', passwordVerificationKey, true);
// passwordEncryptionService expects these options to already exist
await optionService.createOption('encryptedDataKey', '', true);
optionService.createOption('encryptedDataKey', '', true);
await passwordEncryptionService.setDataKey(password, utils.randomSecureToken(16), true);
passwordEncryptionService.setDataKey(password, utils.randomSecureToken(16), true);
}
async function initNotSyncedOptions(initialized, startNotePath = 'root', opts = {}) {
await optionService.createOption('openTabs', JSON.stringify([
function initNotSyncedOptions(initialized, startNotePath = 'root', opts = {}) {
optionService.createOption('openTabs', JSON.stringify([
{
notePath: startNotePath,
active: true,
@ -38,21 +38,21 @@ async function initNotSyncedOptions(initialized, startNotePath = 'root', opts =
}
]), false);
await optionService.createOption('lastDailyBackupDate', dateUtils.utcNowDateTime(), false);
await optionService.createOption('lastWeeklyBackupDate', dateUtils.utcNowDateTime(), false);
await optionService.createOption('lastMonthlyBackupDate', dateUtils.utcNowDateTime(), false);
await optionService.createOption('dbVersion', appInfo.dbVersion, false);
optionService.createOption('lastDailyBackupDate', dateUtils.utcNowDateTime(), false);
optionService.createOption('lastWeeklyBackupDate', dateUtils.utcNowDateTime(), false);
optionService.createOption('lastMonthlyBackupDate', dateUtils.utcNowDateTime(), false);
optionService.createOption('dbVersion', appInfo.dbVersion, false);
await optionService.createOption('initialized', initialized ? 'true' : 'false', false);
optionService.createOption('initialized', initialized ? 'true' : 'false', false);
await optionService.createOption('lastSyncedPull', '0', false);
await optionService.createOption('lastSyncedPush', '0', false);
optionService.createOption('lastSyncedPull', '0', false);
optionService.createOption('lastSyncedPush', '0', false);
await optionService.createOption('theme', opts.theme || 'white', false);
optionService.createOption('theme', opts.theme || 'white', false);
await optionService.createOption('syncServerHost', opts.syncServerHost || '', false);
await optionService.createOption('syncServerTimeout', '5000', false);
await optionService.createOption('syncProxy', opts.syncProxy || '', false);
optionService.createOption('syncServerHost', opts.syncServerHost || '', false);
optionService.createOption('syncServerTimeout', '5000', false);
optionService.createOption('syncProxy', opts.syncProxy || '', false);
}
const defaultOptions = [
@ -87,14 +87,14 @@ const defaultOptions = [
{ name: 'hideIncludedImages_main', value: 'true', isSynced: false }
];
async function initStartupOptions() {
const optionsMap = await optionService.getOptionsMap();
function initStartupOptions() {
const optionsMap = optionService.getOptionsMap();
const allDefaultOptions = defaultOptions.concat(getKeyboardDefaultOptions());
for (const {name, value, isSynced} of allDefaultOptions) {
if (!(name in optionsMap)) {
await optionService.createOption(name, value, isSynced);
optionService.createOption(name, value, isSynced);
log.info(`Created missing option "${name}" with default value "${value}"`);
}

View File

@ -3,26 +3,26 @@ const myScryptService = require('./my_scrypt');
const utils = require('./utils');
const dataEncryptionService = require('./data_encryption');
async function verifyPassword(password) {
const givenPasswordHash = utils.toBase64(await myScryptService.getVerificationHash(password));
function verifyPassword(password) {
const givenPasswordHash = utils.toBase64(myScryptService.getVerificationHash(password));
const dbPasswordHash = await optionService.getOption('passwordVerificationHash');
const dbPasswordHash = optionService.getOption('passwordVerificationHash');
return givenPasswordHash === dbPasswordHash;
}
async function setDataKey(password, plainTextDataKey) {
const passwordDerivedKey = await myScryptService.getPasswordDerivedKey(password);
function setDataKey(password, plainTextDataKey) {
const passwordDerivedKey = myScryptService.getPasswordDerivedKey(password);
const newEncryptedDataKey = dataEncryptionService.encrypt(passwordDerivedKey, plainTextDataKey, 16);
await optionService.setOption('encryptedDataKey', newEncryptedDataKey);
optionService.setOption('encryptedDataKey', newEncryptedDataKey);
}
async function getDataKey(password) {
const passwordDerivedKey = await myScryptService.getPasswordDerivedKey(password);
function getDataKey(password) {
const passwordDerivedKey = myScryptService.getPasswordDerivedKey(password);
const encryptedDataKey = await optionService.getOption('encryptedDataKey');
const encryptedDataKey = optionService.getOption('encryptedDataKey');
const decryptedDataKey = dataEncryptionService.decrypt(passwordDerivedKey, encryptedDataKey, 16);

View File

@ -4,31 +4,26 @@ const sql = require('./sql');
const syncTableService = require('../services/sync_table');
const eventService = require('./events');
const cls = require('./cls');
const entityConstructor = require('../entities/entity_constructor');
let entityConstructor;
async function setEntityConstructor(constructor) {
entityConstructor = constructor;
}
async function getEntityFromName(entityName, entityId) {
function getEntityFromName(entityName, entityId) {
if (!entityName || !entityId) {
return null;
}
const constructor = entityConstructor.getEntityFromEntityName(entityName);
return await getEntity(`SELECT * FROM ${constructor.entityName} WHERE ${constructor.primaryKeyName} = ?`, [entityId]);
return getEntity(`SELECT * FROM ${constructor.entityName} WHERE ${constructor.primaryKeyName} = ?`, [entityId]);
}
async function getEntities(query, params = []) {
const rows = await sql.getRows(query, params);
function getEntities(query, params = []) {
const rows = sql.getRows(query, params);
return rows.map(entityConstructor.createEntityFromRow);
}
async function getEntity(query, params = []) {
const row = await sql.getRowOrNull(query, params);
function getEntity(query, params = []) {
const row = sql.getRowOrNull(query, params);
if (!row) {
return null;
@ -37,11 +32,11 @@ async function getEntity(query, params = []) {
return entityConstructor.createEntityFromRow(row);
}
async function getCachedEntity(entityName, entityId, query) {
function getCachedEntity(entityName, entityId, query) {
let entity = cls.getEntityFromCache(entityName, entityId);
if (!entity) {
entity = await getEntity(query, [entityId]);
entity = getEntity(query, [entityId]);
cls.setEntityToCache(entityName, entityId, entity);
}
@ -50,18 +45,18 @@ async function getCachedEntity(entityName, entityId, query) {
}
/** @returns {Promise<Note|null>} */
async function getNote(noteId) {
return await getCachedEntity('notes', noteId, "SELECT * FROM notes WHERE noteId = ?");
function getNote(noteId) {
return getCachedEntity('notes', noteId, "SELECT * FROM notes WHERE noteId = ?");
}
/** @returns {Promise<Note[]>} */
async function getNotes(noteIds) {
function getNotes(noteIds) {
// this note might be optimised, but remember that it must keep the existing order of noteIds
// (important e.g. for @orderBy in search)
const notes = [];
for (const noteId of noteIds) {
const note = await getNote(noteId);
const note = getNote(noteId);
notes.push(note);
}
@ -70,40 +65,40 @@ async function getNotes(noteIds) {
}
/** @returns {Promise<NoteRevision|null>} */
async function getNoteRevision(noteRevisionId) {
return await getCachedEntity('note_revisions', noteRevisionId, "SELECT * FROM note_revisions WHERE noteRevisionId = ?");
function getNoteRevision(noteRevisionId) {
return getCachedEntity('note_revisions', noteRevisionId, "SELECT * FROM note_revisions WHERE noteRevisionId = ?");
}
/** @returns {Promise<Branch|null>} */
async function getBranch(branchId) {
return await getCachedEntity('branches', branchId, "SELECT * FROM branches WHERE branchId = ?", [branchId]);
function getBranch(branchId) {
return getCachedEntity('branches', branchId, "SELECT * FROM branches WHERE branchId = ?", [branchId]);
}
/** @returns {Promise<Attribute|null>} */
async function getAttribute(attributeId) {
return await getCachedEntity('attributes', attributeId, "SELECT * FROM attributes WHERE attributeId = ?");
function getAttribute(attributeId) {
return getCachedEntity('attributes', attributeId, "SELECT * FROM attributes WHERE attributeId = ?");
}
/** @returns {Promise<Option|null>} */
async function getOption(name) {
return await getEntity("SELECT * FROM options WHERE name = ?", [name]);
function getOption(name) {
return getEntity("SELECT * FROM options WHERE name = ?", [name]);
}
async function updateEntity(entity) {
function updateEntity(entity) {
const entityName = entity.constructor.entityName;
const primaryKeyName = entity.constructor.primaryKeyName;
const isNewEntity = !entity[primaryKeyName];
if (entity.beforeSaving) {
await entity.beforeSaving();
entity.beforeSaving();
}
const clone = Object.assign({}, entity);
// this check requires that updatePojo is not static
if (entity.updatePojo) {
await entity.updatePojo(clone);
entity.updatePojo(clone);
}
// indicates whether entity actually changed
@ -116,15 +111,15 @@ async function updateEntity(entity) {
}
}
await sql.transactional(async () => {
await sql.upsert(entityName, primaryKeyName, clone);
sql.transactional(() => {
sql.upsert(entityName, primaryKeyName, clone);
const primaryKey = entity[primaryKeyName];
if (entity.isChanged) {
const isSynced = entityName !== 'options' || entity.isSynced;
await syncTableService.addEntitySync(entityName, primaryKey, null, isSynced);
syncTableService.addEntitySync(entityName, primaryKey, null, isSynced);
if (!cls.isEntityEventsDisabled()) {
const eventPayload = {
@ -133,11 +128,11 @@ async function updateEntity(entity) {
};
if (isNewEntity && !entity.isDeleted) {
await eventService.emit(eventService.ENTITY_CREATED, eventPayload);
eventService.emit(eventService.ENTITY_CREATED, eventPayload);
}
// it seems to be better to handle deletion and update separately
await eventService.emit(entity.isDeleted ? eventService.ENTITY_DELETED : eventService.ENTITY_CHANGED, eventPayload);
eventService.emit(entity.isDeleted ? eventService.ENTITY_DELETED : eventService.ENTITY_CHANGED, eventPayload);
}
}
});
@ -153,6 +148,5 @@ module.exports = {
getBranch,
getAttribute,
getOption,
updateEntity,
setEntityConstructor
updateEntity
};

View File

@ -19,7 +19,7 @@ function exec(opts) {
const proxyAgent = getProxyAgent(opts);
const parsedTargetUrl = url.parse(opts.url);
return new Promise(async (resolve, reject) => {
return new Promise((resolve, reject) => {
try {
const headers = {
Cookie: (opts.cookieJar && opts.cookieJar.header) || "",
@ -83,18 +83,18 @@ function exec(opts) {
});
}
async function getImage(imageUrl) {
function getImage(imageUrl) {
const opts = {
method: 'GET',
url: imageUrl,
proxy: await syncOptions.getSyncProxy()
proxy: syncOptions.getSyncProxy()
};
const client = getClient(opts);
const proxyAgent = getProxyAgent(opts);
const parsedTargetUrl = url.parse(opts.url);
return await new Promise(async (resolve, reject) => {
return new Promise((resolve, reject) => {
try {
const request = client.request({
method: opts.method,

View File

@ -3,8 +3,8 @@ const repository = require('./repository');
const cls = require('./cls');
const sqlInit = require('./sql_init');
async function runNotesWithLabel(runAttrValue) {
const notes = await repository.getEntities(`
function runNotesWithLabel(runAttrValue) {
const notes = repository.getEntities(`
SELECT notes.*
FROM notes
JOIN attributes ON attributes.noteId = notes.noteId
@ -21,10 +21,8 @@ async function runNotesWithLabel(runAttrValue) {
}
}
sqlInit.dbReady.then(() => {
setTimeout(cls.wrap(() => runNotesWithLabel('backendStartup')), 10 * 1000);
setTimeout(cls.wrap(() => runNotesWithLabel('backendStartup')), 10 * 1000);
setInterval(cls.wrap(() => runNotesWithLabel('hourly')), 3600 * 1000);
setInterval(cls.wrap(() => runNotesWithLabel('hourly')), 3600 * 1000);
setInterval(cls.wrap(() => runNotesWithLabel('daily')), 24 * 3600 * 1000);
});
setInterval(cls.wrap(() => runNotesWithLabel('daily')), 24 * 3600 * 1000);

View File

@ -4,28 +4,28 @@ const repository = require('./repository');
const cls = require('./cls');
const log = require('./log');
async function executeNote(note, apiParams) {
function executeNote(note, apiParams) {
if (!note.isJavaScript() || note.getScriptEnv() !== 'backend' || !note.isContentAvailable) {
log.info(`Cannot execute note ${note.noteId}`);
return;
}
const bundle = await getScriptBundle(note);
const bundle = getScriptBundle(note);
return await executeBundle(bundle, apiParams);
return executeBundle(bundle, apiParams);
}
async function executeNoteNoException(note, apiParams) {
function executeNoteNoException(note, apiParams) {
try {
await executeNote(note, apiParams);
executeNote(note, apiParams);
}
catch (e) {
// just swallow, exception is logged already in executeNote
}
}
async function executeBundle(bundle, apiParams = {}) {
function executeBundle(bundle, apiParams = {}) {
if (!apiParams.startNote) {
// this is the default case, the only exception is when we want to preserve frontend startNote
apiParams.startNote = bundle.note;
@ -34,16 +34,16 @@ async function executeBundle(bundle, apiParams = {}) {
cls.set('sourceId', 'script');
// 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 = "function() {\r\n" + bundle.script + "\r\n}";
const ctx = new ScriptContext(bundle.allNotes, apiParams);
try {
if (await bundle.note.hasOwnedLabel('manualTransactionHandling')) {
return await execute(ctx, script);
if (bundle.note.hasOwnedLabel('manualTransactionHandling')) {
return execute(ctx, script);
}
else {
return await sql.transactional(async () => await execute(ctx, script));
return sql.transactional(() => execute(ctx, script));
}
}
catch (e) {
@ -57,22 +57,22 @@ async function executeBundle(bundle, apiParams = {}) {
* This method preserves frontend startNode - that's why we start execution from currentNote and override
* bundle's startNote.
*/
async function executeScript(script, params, startNoteId, currentNoteId, originEntityName, originEntityId) {
const startNote = await repository.getNote(startNoteId);
const currentNote = await repository.getNote(currentNoteId);
const originEntity = await repository.getEntityFromName(originEntityName, originEntityId);
function executeScript(script, params, startNoteId, currentNoteId, originEntityName, originEntityId) {
const startNote = repository.getNote(startNoteId);
const currentNote = repository.getNote(currentNoteId);
const originEntity = repository.getEntityFromName(originEntityName, originEntityId);
currentNote.content = `return await (${script}\r\n)(${getParams(params)})`;
currentNote.content = `return (${script}\r\n)(${getParams(params)})`;
currentNote.type = 'code';
currentNote.mime = 'application/javascript;env=backend';
const bundle = await getScriptBundle(currentNote);
const bundle = getScriptBundle(currentNote);
return await executeBundle(bundle, { startNote, originEntity });
return executeBundle(bundle, { startNote, originEntity });
}
async function execute(ctx, script) {
return await (function() { return eval(`const apiContext = this;\r\n(${script}\r\n)()`); }.call(ctx));
function execute(ctx, script) {
return (function() { return eval(`const apiContext = this;\r\n(${script}\r\n)()`); }.call(ctx));
}
function getParams(params) {
@ -90,8 +90,8 @@ function getParams(params) {
}).join(",");
}
async function getScriptBundleForFrontend(note) {
const bundle = await getScriptBundle(note);
function getScriptBundleForFrontend(note) {
const bundle = getScriptBundle(note);
if (!bundle) {
return;
@ -107,7 +107,7 @@ async function getScriptBundleForFrontend(note) {
return bundle;
}
async function getScriptBundle(note, root = true, scriptEnv = null, includedNoteIds = []) {
function getScriptBundle(note, root = true, scriptEnv = null, includedNoteIds = []) {
if (!note.isContentAvailable) {
return;
}
@ -116,7 +116,7 @@ async function getScriptBundle(note, root = true, scriptEnv = null, includedNote
return;
}
if (!root && await note.hasOwnedLabel('disableInclusion')) {
if (!root && note.hasOwnedLabel('disableInclusion')) {
return;
}
@ -143,8 +143,8 @@ async function getScriptBundle(note, root = true, scriptEnv = null, includedNote
const modules = [];
for (const child of await note.getChildNotes()) {
const childBundle = await getScriptBundle(child, false, scriptEnv, includedNoteIds);
for (const child of note.getChildNotes()) {
const childBundle = getScriptBundle(child, false, scriptEnv, includedNoteIds);
if (childBundle) {
modules.push(childBundle.note);
@ -159,10 +159,10 @@ async function getScriptBundle(note, root = true, scriptEnv = null, includedNote
if (note.isJavaScript()) {
bundle.script += `
apiContext.modules['${note.noteId}'] = {};
${root ? 'return ' : ''}await ((async function(exports, module, require, api` + (modules.length > 0 ? ', ' : '') +
${root ? 'return ' : ''}((function(exports, module, require, api` + (modules.length > 0 ? ', ' : '') +
modules.map(child => sanitizeVariableName(child.title)).join(', ') + `) {
try {
${await note.getContent()};
${note.getContent()};
} catch (e) { throw new Error("Load of script note \\"${note.title}\\" (${note.noteId}) failed with: " + e.message); }
if (!module.exports) module.exports = {};
for (const exportKey in exports) module.exports[exportKey] = exports[exportKey];
@ -172,7 +172,7 @@ return module.exports;
`;
}
else if (note.isHtml()) {
bundle.html += await note.getContent();
bundle.html += note.getContent();
}
return bundle;

View File

@ -20,19 +20,19 @@ const parseFilters = require('./search/parse_filters.js');
const buildSearchQuery = require('./build_search_query');
const noteCacheService = require('./note_cache/note_cache.js');
async function searchForNotes(searchString) {
const noteIds = await searchForNoteIds(searchString);
function searchForNotes(searchString) {
const noteIds = searchForNoteIds(searchString);
return await repository.getNotes(noteIds);
return repository.getNotes(noteIds);
}
async function searchForNoteIds(searchString) {
function searchForNoteIds(searchString) {
const filters = parseFilters(searchString);
const {query, params} = buildSearchQuery(filters, 'notes.noteId');
try {
let noteIds = await sql.getColumn(query, params);
let noteIds = sql.getColumn(query, params);
noteIds = noteIds.filter(noteCacheService.isAvailable);

View File

@ -18,9 +18,9 @@ class AndExp extends Expression {
this.subExpressions = subExpressions;
}
async execute(inputNoteSet, searchContext) {
execute(inputNoteSet, searchContext) {
for (const subExpression of this.subExpressions) {
inputNoteSet = await subExpression.execute(inputNoteSet, searchContext);
inputNoteSet = subExpression.execute(inputNoteSet, searchContext);
}
return inputNoteSet;

View File

@ -15,13 +15,13 @@ class NoteContentFulltextExp extends Expression {
this.tokens = tokens;
}
async execute(inputNoteSet) {
execute(inputNoteSet) {
const resultNoteSet = new NoteSet();
const wheres = this.tokens.map(token => "note_contents.content LIKE " + utils.prepareSqlForLike(this.likePrefix, token, this.likeSuffix));
const sql = require('../../sql');
const noteIds = await sql.getColumn(`
const noteIds = sql.getColumn(`
SELECT notes.noteId
FROM notes
JOIN note_contents ON notes.noteId = note_contents.noteId

View File

@ -21,11 +21,11 @@ class OrExp extends Expression {
this.subExpressions = subExpressions;
}
async execute(inputNoteSet, searchContext) {
execute(inputNoteSet, searchContext) {
const resultNoteSet = new NoteSet();
for (const subExpression of this.subExpressions) {
resultNoteSet.mergeIn(await subExpression.execute(inputNoteSet, searchContext));
resultNoteSet.mergeIn(subExpression.execute(inputNoteSet, searchContext));
}
return resultNoteSet;

View File

@ -15,7 +15,7 @@ const utils = require('../utils');
* @param {Expression} expression
* @return {Promise<SearchResult[]>}
*/
async function findNotesWithExpression(expression) {
function findNotesWithExpression(expression) {
const hoistedNote = noteCache.notes[hoistedNoteService.getHoistedNoteId()];
const allNotes = (hoistedNote && hoistedNote.noteId !== 'root')
? hoistedNote.subtreeNotes
@ -27,7 +27,7 @@ async function findNotesWithExpression(expression) {
noteIdToNotePath: {}
};
const noteSet = await expression.execute(allNoteSet, searchContext);
const noteSet = expression.execute(allNoteSet, searchContext);
let searchResults = noteSet.notes
.map(note => searchContext.noteIdToNotePath[note.noteId] || noteCacheService.getSomePath(note))
@ -67,17 +67,17 @@ function parseQueryToExpression(query, parsingContext) {
* @param {ParsingContext} parsingContext
* @return {Promise<SearchResult[]>}
*/
async function findNotesWithQuery(query, parsingContext) {
function findNotesWithQuery(query, parsingContext) {
const expression = parseQueryToExpression(query, parsingContext);
if (!expression) {
return [];
}
return await findNotesWithExpression(expression);
return findNotesWithExpression(expression);
}
async function searchNotes(query) {
function searchNotes(query) {
if (!query.trim().length) {
return [];
}
@ -87,7 +87,7 @@ async function searchNotes(query) {
fuzzyAttributeSearch: false
});
const allSearchResults = await findNotesWithQuery(query, parsingContext);
const allSearchResults = findNotesWithQuery(query, parsingContext);
const trimmedSearchResults = allSearchResults.slice(0, 200);
return {
@ -96,7 +96,7 @@ async function searchNotes(query) {
};
}
async function searchNotesForAutocomplete(query) {
function searchNotesForAutocomplete(query) {
if (!query.trim().length) {
return [];
}
@ -106,7 +106,7 @@ async function searchNotesForAutocomplete(query) {
fuzzyAttributeSearch: true
});
let searchResults = await findNotesWithQuery(query, parsingContext);
let searchResults = findNotesWithQuery(query, parsingContext);
searchResults = searchResults.slice(0, 200);

View File

@ -22,9 +22,9 @@ function triggerSync() {
log.info("Triggering sync.");
// it's ok to not wait for it here
syncService.sync().then(async res => {
syncService.sync().then(res => {
if (res.success) {
await sqlInit.dbInitialized();
sqlInit.dbInitialized();
}
});
}
@ -33,30 +33,30 @@ async function sendSeedToSyncServer() {
log.info("Initiating sync to server");
await requestToSyncServer('POST', '/api/setup/sync-seed', {
options: await getSyncSeedOptions(),
options: getSyncSeedOptions(),
syncVersion: appInfo.syncVersion
});
// this is completely new sync, need to reset counters. If this would not be new sync,
// the previous request would have failed.
await optionService.setOption('lastSyncedPush', 0);
await optionService.setOption('lastSyncedPull', 0);
optionService.setOption('lastSyncedPush', 0);
optionService.setOption('lastSyncedPull', 0);
}
async function requestToSyncServer(method, path, body = null) {
const timeout = await syncOptions.getSyncTimeout();
const timeout = syncOptions.getSyncTimeout();
return utils.timeLimit(request.exec({
return await utils.timeLimit(request.exec({
method,
url: await syncOptions.getSyncServerHost() + path,
url: syncOptions.getSyncServerHost() + path,
body,
proxy: await syncOptions.getSyncProxy(),
proxy: syncOptions.getSyncProxy(),
timeout: timeout
}), timeout);
}
async function setupSyncFromSyncServer(syncServerHost, syncProxy, username, password) {
if (await sqlInit.isDbInitialized()) {
if (sqlInit.isDbInitialized()) {
return {
result: 'failure',
error: 'DB is already initialized.'
@ -89,7 +89,7 @@ async function setupSyncFromSyncServer(syncServerHost, syncProxy, username, pass
}
}
await sqlInit.createDatabaseForSync(resp.options, syncServerHost, syncProxy);
sqlInit.createDatabaseForSync(resp.options, syncServerHost, syncProxy);
triggerSync();
@ -105,10 +105,10 @@ async function setupSyncFromSyncServer(syncServerHost, syncProxy, username, pass
}
}
async function getSyncSeedOptions() {
function getSyncSeedOptions() {
return [
await repository.getOption('documentId'),
await repository.getOption('documentSecret')
repository.getOption('documentId'),
repository.getOption('documentSecret')
];
}

View File

@ -5,13 +5,13 @@ const sql = require('./sql');
const sqlInit = require('./sql_init');
const cls = require('./cls');
async function saveSourceId(sourceId) {
await sql.insert("source_ids", {
function saveSourceId(sourceId) {
sql.insert("source_ids", {
sourceId: sourceId,
utcDateCreated: dateUtils.utcNowDateTime()
});
await refreshSourceIds();
refreshSourceIds();
}
function createSourceId() {
@ -21,16 +21,16 @@ function createSourceId() {
return sourceId;
}
async function generateSourceId() {
function generateSourceId() {
const sourceId = createSourceId();
await saveSourceId(sourceId);
saveSourceId(sourceId);
return sourceId;
}
async function refreshSourceIds() {
const sourceIdsArr = await sql.getColumn("SELECT sourceId FROM source_ids ORDER BY utcDateCreated DESC");
function refreshSourceIds() {
const sourceIdsArr = sql.getColumn("SELECT sourceId FROM source_ids ORDER BY utcDateCreated DESC");
allSourceIds = {};
@ -48,7 +48,7 @@ function isLocalSourceId(srcId) {
const currentSourceId = createSourceId();
// this will also refresh source IDs
sqlInit.dbReady.then(cls.wrap(() => saveSourceId(currentSourceId)));
cls.wrap(() => saveSourceId(currentSourceId));
function getCurrentSourceId() {
return currentSourceId;

View File

@ -19,7 +19,7 @@ function setDbConnection(connection) {
});
});
async function insert(tableName, rec, replace = false) {
function insert(tableName, rec, replace = false) {
const keys = Object.keys(rec);
if (keys.length === 0) {
log.error("Can't insert empty object into table " + tableName);
@ -31,16 +31,16 @@ async function insert(tableName, rec, replace = false) {
const query = "INSERT " + (replace ? "OR REPLACE" : "") + " INTO " + tableName + "(" + columns + ") VALUES (" + questionMarks + ")";
const res = await execute(query, Object.values(rec));
const res = execute(query, Object.values(rec));
return res.lastInsertRowid;
}
async function replace(tableName, rec) {
return await insert(tableName, rec, true);
function replace(tableName, rec) {
return insert(tableName, rec, true);
}
async function upsert(tableName, primaryKey, rec) {
function upsert(tableName, primaryKey, rec) {
const keys = Object.keys(rec);
if (keys.length === 0) {
log.error("Can't upsert empty object into table " + tableName);
@ -62,7 +62,7 @@ async function upsert(tableName, primaryKey, rec) {
}
}
await execute(query, rec);
execute(query, rec);
}
const statementCache = {};
@ -87,18 +87,18 @@ function rollback() {
return stmt("ROLLBACK").run();
}
async function getRow(query, params = []) {
function getRow(query, params = []) {
return wrap(() => stmt(query).get(params), query);
}
async function getRowOrNull(query, params = []) {
const all = await getRows(query, params);
function getRowOrNull(query, params = []) {
const all = getRows(query, params);
return all.length > 0 ? all[0] : null;
}
async function getValue(query, params = []) {
const row = await getRowOrNull(query, params);
function getValue(query, params = []) {
const row = getRowOrNull(query, params);
if (!row) {
return null;
@ -110,7 +110,7 @@ async function getValue(query, params = []) {
const PARAM_LIMIT = 900; // actual limit is 999
// this is to overcome 999 limit of number of query parameters
async function getManyRows(query, params) {
function getManyRows(query, params) {
let results = [];
while (params.length > 0) {
@ -128,19 +128,19 @@ async function getManyRows(query, params) {
const questionMarks = curParams.map(() => ":param" + i++).join(",");
const curQuery = query.replace(/\?\?\?/g, questionMarks);
results = results.concat(await getRows(curQuery, curParamsObj));
results = results.concat(getRows(curQuery, curParamsObj));
}
return results;
}
async function getRows(query, params = []) {
function getRows(query, params = []) {
return wrap(() => stmt(query).all(params), query);
}
async function getMap(query, params = []) {
function getMap(query, params = []) {
const map = {};
const results = await getRows(query, params);
const results = getRows(query, params);
for (const row of results) {
const keys = Object.keys(row);
@ -151,9 +151,9 @@ async function getMap(query, params = []) {
return map;
}
async function getColumn(query, params = []) {
function getColumn(query, params = []) {
const list = [];
const result = await getRows(query, params);
const result = getRows(query, params);
if (result.length === 0) {
return list;
@ -168,25 +168,25 @@ async function getColumn(query, params = []) {
return list;
}
async function execute(query, params = []) {
await startTransactionIfNecessary();
function execute(query, params = []) {
startTransactionIfNecessary();
return wrap(() => stmt(query).run(params), query);
}
async function executeWithoutTransaction(query, params = []) {
await dbConnection.run(query, params);
function executeWithoutTransaction(query, params = []) {
dbConnection.run(query, params);
}
async function executeMany(query, params) {
await startTransactionIfNecessary();
function executeMany(query, params) {
startTransactionIfNecessary();
// essentially just alias
await getManyRows(query, params);
getManyRows(query, params);
}
async function executeScript(query) {
await startTransactionIfNecessary();
function executeScript(query) {
startTransactionIfNecessary();
return wrap(() => stmt.run(query), query);
}
@ -231,37 +231,33 @@ let transactionActive = false;
let transactionPromise = null;
let transactionPromiseResolve = null;
async function startTransactionIfNecessary() {
function startTransactionIfNecessary() {
if (!cls.get('isTransactional')
|| cls.get('isInTransaction')) {
return;
}
while (transactionActive) {
await transactionPromise;
}
// first set semaphore (atomic operation and only then start transaction
transactionActive = true;
transactionPromise = new Promise(res => transactionPromiseResolve = res);
cls.set('isInTransaction', true);
await beginTransaction();
beginTransaction();
}
async function transactional(func) {
function transactional(func) {
// if the CLS is already transactional then the whole transaction is handled by higher level transactional() call
if (cls.get('isTransactional')) {
return await func();
return func();
}
cls.set('isTransactional', true); // this signals that transaction will be needed if there's a write operation
try {
const ret = await func();
const ret = func();
if (cls.get('isInTransaction')) {
await commit();
commit();
// note that sync rows sent from this action will be sent again by scheduled periodic ping
require('./ws.js').sendPingToAllClients();
@ -271,7 +267,7 @@ async function transactional(func) {
}
catch (e) {
if (cls.get('isInTransaction')) {
await rollback();
rollback();
}
throw e;

View File

@ -19,32 +19,32 @@ sql.setDbConnection(dbConnection);
const dbReady = initDbConnection();
async function schemaExists() {
const tableResults = await sql.getRows("SELECT name FROM sqlite_master WHERE type='table' AND name='options'");
function schemaExists() {
const tableResults = sql.getRows("SELECT name FROM sqlite_master WHERE type='table' AND name='options'");
return tableResults.length === 1;
}
async function isDbInitialized() {
if (!await schemaExists()) {
function isDbInitialized() {
if (!schemaExists()) {
return false;
}
const initialized = await sql.getValue("SELECT value FROM options WHERE name = 'initialized'");
const initialized = sql.getValue("SELECT value FROM options WHERE name = 'initialized'");
// !initialized may be removed in the future, required only for migration
return !initialized || initialized === 'true';
}
async function initDbConnection() {
await cls.init(async () => {
if (!await isDbInitialized()) {
log.info(`DB not initialized, please visit setup page` + (utils.isElectron() ? '' : ` - http://[your-server-host]:${await port} to see instructions on how to initialize Trilium.`));
function initDbConnection() {
cls.init(() => {
if (!isDbInitialized()) {
log.info(`DB not initialized, please visit setup page` + (utils.isElectron() ? '' : ` - http://[your-server-host]:${port} to see instructions on how to initialize Trilium.`));
return;
}
const currentDbVersion = await getDbVersion();
const currentDbVersion = getDbVersion();
if (currentDbVersion > appInfo.dbVersion) {
log.error(`Current DB version ${currentDbVersion} is newer than app db version ${appInfo.dbVersion} which means that it was created by newer and incompatible version of Trilium. Upgrade to latest version of Trilium to resolve this issue.`);
@ -52,45 +52,43 @@ async function initDbConnection() {
utils.crash();
}
if (!await isDbUpToDate()) {
if (!isDbUpToDate()) {
// avoiding circular dependency
const migrationService = require('./migration');
await migrationService.migrate();
migrationService.migrate();
}
await require('./options_init').initStartupOptions();
log.info("DB ready.");
require('./options_init').initStartupOptions();
});
}
async function createInitialDatabase(username, password, theme) {
function createInitialDatabase(username, password, theme) {
log.info("Creating initial database ...");
if (await isDbInitialized()) {
if (isDbInitialized()) {
throw new Error("DB is already initialized");
}
const schema = fs.readFileSync(resourceDir.DB_INIT_DIR + '/schema.sql', 'UTF-8');
const demoFile = fs.readFileSync(resourceDir.DB_INIT_DIR + '/demo.zip');
await sql.transactional(async () => {
await sql.executeScript(schema);
sql.transactional(() => {
sql.executeScript(schema);
const Note = require("../entities/note");
const Branch = require("../entities/branch");
const rootNote = await new Note({
const rootNote = new Note({
noteId: 'root',
title: 'root',
type: 'text',
mime: 'text/html'
}).save();
await rootNote.setContent('');
rootNote.setContent('');
await new Branch({
new Branch({
branchId: 'root',
noteId: 'root',
parentNoteId: 'none',
@ -101,53 +99,53 @@ async function createInitialDatabase(username, password, theme) {
const dummyTaskContext = new TaskContext("1", 'import', false);
const zipImportService = require("./import/zip");
await zipImportService.importZip(dummyTaskContext, demoFile, rootNote);
zipImportService.importZip(dummyTaskContext, demoFile, rootNote);
const startNoteId = await sql.getValue("SELECT noteId FROM branches WHERE parentNoteId = 'root' AND isDeleted = 0 ORDER BY notePosition");
const startNoteId = sql.getValue("SELECT noteId FROM branches WHERE parentNoteId = 'root' AND isDeleted = 0 ORDER BY notePosition");
const optionsInitService = require('./options_init');
await optionsInitService.initDocumentOptions();
await optionsInitService.initSyncedOptions(username, password);
await optionsInitService.initNotSyncedOptions(true, startNoteId, { theme });
optionsInitService.initDocumentOptions();
optionsInitService.initSyncedOptions(username, password);
optionsInitService.initNotSyncedOptions(true, startNoteId, { theme });
await require('./sync_table').fillAllSyncRows();
require('./sync_table').fillAllSyncRows();
});
log.info("Schema and initial content generated.");
await initDbConnection();
initDbConnection();
}
async function createDatabaseForSync(options, syncServerHost = '', syncProxy = '') {
function createDatabaseForSync(options, syncServerHost = '', syncProxy = '') {
log.info("Creating database for sync");
if (await isDbInitialized()) {
if (isDbInitialized()) {
throw new Error("DB is already initialized");
}
const schema = fs.readFileSync(resourceDir.DB_INIT_DIR + '/schema.sql', 'UTF-8');
await sql.transactional(async () => {
await sql.executeScript(schema);
sql.transactional(() => {
sql.executeScript(schema);
await require('./options_init').initNotSyncedOptions(false, 'root', { syncServerHost, syncProxy });
require('./options_init').initNotSyncedOptions(false, 'root', { syncServerHost, syncProxy });
// document options required for sync to kick off
for (const opt of options) {
await new Option(opt).save();
new Option(opt).save();
}
});
log.info("Schema and not synced options generated.");
}
async function getDbVersion() {
return parseInt(await sql.getValue("SELECT value FROM options WHERE name = 'dbVersion'"));
function getDbVersion() {
return parseInt(sql.getValue("SELECT value FROM options WHERE name = 'dbVersion'"));
}
async function isDbUpToDate() {
const dbVersion = await getDbVersion();
function isDbUpToDate() {
const dbVersion = getDbVersion();
const upToDate = dbVersion >= appInfo.dbVersion;
@ -158,17 +156,15 @@ async function isDbUpToDate() {
return upToDate;
}
async function dbInitialized() {
if (!await isDbInitialized()) {
await optionService.setOption('initialized', 'true');
function dbInitialized() {
if (!isDbInitialized()) {
optionService.setOption('initialized', 'true');
await initDbConnection();
initDbConnection();
}
}
dbReady.then(async () => {
log.info("DB size: " + await sql.getValue("SELECT page_count * page_size / 1000 as size FROM pragma_page_count(), pragma_page_size()") + " KB");
});
log.info("DB size: " + sql.getValue("SELECT page_count * page_size / 1000 as size FROM pragma_page_count(), pragma_page_size()") + " KB");
module.exports = {
dbReady,

View File

@ -28,7 +28,7 @@ const stats = {
async function sync() {
try {
return await syncMutexService.doExclusively(async () => {
if (!await syncOptions.isSyncSetup()) {
if (!syncOptions.isSyncSetup()) {
return { success: false, message: 'Sync not configured' };
}
@ -87,13 +87,13 @@ async function login() {
await setupService.sendSeedToSyncServer();
}
return await doLogin();
return doLogin();
}
async function doLogin() {
const timestamp = dateUtils.utcNowDateTime();
const documentSecret = await optionService.getOption('documentSecret');
const documentSecret = optionService.getOption('documentSecret');
const hash = utils.hmac(documentSecret, timestamp);
const syncContext = { cookieJar: {} };
@ -109,14 +109,14 @@ async function doLogin() {
syncContext.sourceId = resp.sourceId;
const lastSyncedPull = await getLastSyncedPull();
const lastSyncedPull = getLastSyncedPull();
// this is important in a scenario where we setup the sync by manually copying the document
// lastSyncedPull then could be pretty off for the newly cloned client
if (lastSyncedPull > resp.maxSyncId) {
log.info(`Lowering last synced pull from ${lastSyncedPull} to ${resp.maxSyncId}`);
await setLastSyncedPull(resp.maxSyncId);
setLastSyncedPull(resp.maxSyncId);
}
return syncContext;
@ -126,7 +126,7 @@ async function pullSync(syncContext) {
let appliedPulls = 0;
while (true) {
const lastSyncedPull = await getLastSyncedPull();
const lastSyncedPull = getLastSyncedPull();
const changesUri = '/api/sync/changed?lastSyncId=' + lastSyncedPull;
const startDate = Date.now();
@ -144,7 +144,7 @@ async function pullSync(syncContext) {
break;
}
await sql.transactional(async () => {
sql.transactional(() => {
for (const {sync, entity} of rows) {
if (!sourceIdService.isLocalSourceId(sync.sourceId)) {
if (appliedPulls === 0 && sync.entity !== 'recent_notes') { // send only for first
@ -153,13 +153,13 @@ async function pullSync(syncContext) {
appliedPulls++;
}
await syncUpdateService.updateEntity(sync, entity, syncContext.sourceId);
syncUpdateService.updateEntity(sync, entity, syncContext.sourceId);
}
stats.outstandingPulls = resp.maxSyncId - sync.id;
}
await setLastSyncedPull(rows[rows.length - 1].sync.id);
setLastSyncedPull(rows[rows.length - 1].sync.id);
});
log.info(`Pulled and updated ${rows.length} changes from ${changesUri} in ${Date.now() - startDate}ms`);
@ -173,10 +173,10 @@ async function pullSync(syncContext) {
}
async function pushSync(syncContext) {
let lastSyncedPush = await getLastSyncedPush();
let lastSyncedPush = getLastSyncedPush();
while (true) {
const syncs = await sql.getRows('SELECT * FROM sync WHERE isSynced = 1 AND id > ? LIMIT 1000', [lastSyncedPush]);
const syncs = sql.getRows('SELECT * FROM sync WHERE isSynced = 1 AND id > ? LIMIT 1000', [lastSyncedPush]);
if (syncs.length === 0) {
log.info("Nothing to push");
@ -201,12 +201,12 @@ async function pushSync(syncContext) {
if (filteredSyncs.length === 0) {
// there still might be more syncs (because of batch limit), just all from current batch
// has been filtered out
await setLastSyncedPush(lastSyncedPush);
setLastSyncedPush(lastSyncedPush);
continue;
}
const syncRecords = await getSyncRecords(filteredSyncs);
const syncRecords = getSyncRecords(filteredSyncs);
const startDate = new Date();
await syncRequest(syncContext, 'PUT', '/api/sync/update', {
@ -218,7 +218,7 @@ async function pushSync(syncContext) {
lastSyncedPush = syncRecords[syncRecords.length - 1].sync.id;
await setLastSyncedPush(lastSyncedPush);
setLastSyncedPush(lastSyncedPush);
}
}
@ -228,7 +228,7 @@ async function syncFinished(syncContext) {
async function checkContentHash(syncContext) {
const resp = await syncRequest(syncContext, 'GET', '/api/sync/check');
const lastSyncedPullId = await getLastSyncedPull();
const lastSyncedPullId = getLastSyncedPull();
if (lastSyncedPullId < resp.maxSyncId) {
log.info(`There are some outstanding pulls (${lastSyncedPullId} vs. ${resp.maxSyncId}), skipping content check.`);
@ -236,7 +236,7 @@ async function checkContentHash(syncContext) {
return true;
}
const notPushedSyncs = await sql.getValue("SELECT EXISTS(SELECT 1 FROM sync WHERE isSynced = 1 AND id > ?)", [await getLastSyncedPush()]);
const notPushedSyncs = sql.getValue("SELECT EXISTS(SELECT 1 FROM sync WHERE isSynced = 1 AND id > ?)", [getLastSyncedPush()]);
if (notPushedSyncs) {
log.info(`There's ${notPushedSyncs} outstanding pushes, skipping content check.`);
@ -244,12 +244,12 @@ async function checkContentHash(syncContext) {
return true;
}
const failedChecks = await contentHashService.checkContentHashes(resp.entityHashes);
const failedChecks = contentHashService.checkContentHashes(resp.entityHashes);
for (const {entityName, sector} of failedChecks) {
const entityPrimaryKey = entityConstructor.getEntityFromEntityName(entityName).primaryKeyName;
await syncTableService.addEntitySyncsForSector(entityName, entityPrimaryKey, sector);
syncTableService.addEntitySyncsForSector(entityName, entityPrimaryKey, sector);
await syncRequest(syncContext, 'POST', `/api/sync/queue-sector/${entityName}/${sector}`);
}
@ -257,19 +257,19 @@ async function checkContentHash(syncContext) {
return failedChecks.length > 0;
}
async function syncRequest(syncContext, method, requestPath, body) {
const timeout = await syncOptions.getSyncTimeout();
function syncRequest(syncContext, method, requestPath, body) {
const timeout = syncOptions.getSyncTimeout();
const opts = {
method,
url: await syncOptions.getSyncServerHost() + requestPath,
url: syncOptions.getSyncServerHost() + requestPath,
cookieJar: syncContext.cookieJar,
timeout: timeout,
body,
proxy: proxyToggle ? await syncOptions.getSyncProxy() : null
proxy: proxyToggle ? syncOptions.getSyncProxy() : null
};
return await utils.timeLimit(request.exec(opts), timeout);
return utils.timeLimit(request.exec(opts), timeout);
}
const primaryKeys = {
@ -284,9 +284,9 @@ const primaryKeys = {
"attributes": "attributeId"
};
async function getEntityRow(entityName, entityId) {
function getEntityRow(entityName, entityId) {
if (entityName === 'note_reordering') {
return await sql.getMap("SELECT branchId, notePosition FROM branches WHERE parentNoteId = ? AND isDeleted = 0", [entityId]);
return sql.getMap("SELECT branchId, notePosition FROM branches WHERE parentNoteId = ? AND isDeleted = 0", [entityId]);
}
else {
const primaryKey = primaryKeys[entityName];
@ -295,7 +295,7 @@ async function getEntityRow(entityName, entityId) {
throw new Error("Unknown entity " + entityName);
}
const entity = await sql.getRow(`SELECT * FROM ${entityName} WHERE ${primaryKey} = ?`, [entityId]);
const entity = sql.getRow(`SELECT * FROM ${entityName} WHERE ${primaryKey} = ?`, [entityId]);
if (!entity) {
throw new Error(`Entity ${entityName} ${entityId} not found.`);
@ -313,12 +313,12 @@ async function getEntityRow(entityName, entityId) {
}
}
async function getSyncRecords(syncs) {
function getSyncRecords(syncs) {
const records = [];
let length = 0;
for (const sync of syncs) {
const entity = await getEntityRow(sync.entityName, sync.entityId);
const entity = getEntityRow(sync.entityName, sync.entityId);
if (sync.entityName === 'options' && !entity.isSynced) {
records.push({sync});
@ -340,42 +340,40 @@ async function getSyncRecords(syncs) {
return records;
}
async function getLastSyncedPull() {
return parseInt(await optionService.getOption('lastSyncedPull'));
function getLastSyncedPull() {
return parseInt(optionService.getOption('lastSyncedPull'));
}
async function setLastSyncedPull(syncId) {
await optionService.setOption('lastSyncedPull', syncId);
function setLastSyncedPull(syncId) {
optionService.setOption('lastSyncedPull', syncId);
}
async function getLastSyncedPush() {
return parseInt(await optionService.getOption('lastSyncedPush'));
function getLastSyncedPush() {
return parseInt(optionService.getOption('lastSyncedPush'));
}
async function setLastSyncedPush(lastSyncedPush) {
await optionService.setOption('lastSyncedPush', lastSyncedPush);
function setLastSyncedPush(lastSyncedPush) {
optionService.setOption('lastSyncedPush', lastSyncedPush);
}
async function updatePushStats() {
if (await syncOptions.isSyncSetup()) {
const lastSyncedPush = await optionService.getOption('lastSyncedPush');
function updatePushStats() {
if (syncOptions.isSyncSetup()) {
const lastSyncedPush = optionService.getOption('lastSyncedPush');
stats.outstandingPushes = await sql.getValue("SELECT COUNT(1) FROM sync WHERE isSynced = 1 AND id > ?", [lastSyncedPush]);
stats.outstandingPushes = sql.getValue("SELECT COUNT(1) FROM sync WHERE isSynced = 1 AND id > ?", [lastSyncedPush]);
}
}
async function getMaxSyncId() {
return await sql.getValue('SELECT MAX(id) FROM sync');
function getMaxSyncId() {
return sql.getValue('SELECT MAX(id) FROM sync');
}
sqlInit.dbReady.then(async () => {
setInterval(cls.wrap(sync), 60000);
setInterval(cls.wrap(sync), 60000);
// kickoff initial sync immediately
setTimeout(cls.wrap(sync), 3000);
// kickoff initial sync immediately
setTimeout(cls.wrap(sync), 3000);
setInterval(cls.wrap(updatePushStats), 1000);
});
setInterval(cls.wrap(updatePushStats), 1000);
module.exports = {
sync,

View File

@ -10,7 +10,7 @@ async function doExclusively(func) {
const releaseMutex = await instance.acquire();
try {
return await func();
return func();
}
finally {
releaseMutex();

View File

@ -10,19 +10,19 @@ const config = require('./config');
* to live sync server.
*/
async function get(name) {
return (config['Sync'] && config['Sync'][name]) || await optionService.getOption(name);
function get(name) {
return (config['Sync'] && config['Sync'][name]) || optionService.getOption(name);
}
module.exports = {
getSyncServerHost: async () => await get('syncServerHost'),
isSyncSetup: async () => {
const syncServerHost = await get('syncServerHost');
getSyncServerHost: () => get('syncServerHost'),
isSyncSetup: () => {
const syncServerHost = get('syncServerHost');
// special value "disabled" is here to support use case where document is configured with sync server
// and we need to override it with config from config.ini
return !!syncServerHost && syncServerHost !== 'disabled';
},
getSyncTimeout: async () => parseInt(await get('syncServerTimeout')) || 60000,
getSyncProxy: async () => await get('syncProxy')
getSyncTimeout: () => parseInt(get('syncServerTimeout')) || 60000,
getSyncProxy: () => get('syncProxy')
};

View File

@ -10,7 +10,7 @@ const cls = require('./cls');
let maxSyncId = 0;
async function insertEntitySync(entityName, entityId, sourceId = null, isSynced = true) {
function insertEntitySync(entityName, entityId, sourceId = null, isSynced = true) {
const sync = {
entityName: entityName,
entityId: entityId,
@ -19,66 +19,66 @@ async function insertEntitySync(entityName, entityId, sourceId = null, isSynced
isSynced: isSynced ? 1 : 0
};
sync.id = await sql.replace("sync", sync);
sync.id = sql.replace("sync", sync);
maxSyncId = Math.max(maxSyncId, sync.id);
return sync;
}
async function addEntitySync(entityName, entityId, sourceId, isSynced) {
const sync = await insertEntitySync(entityName, entityId, sourceId, isSynced);
function addEntitySync(entityName, entityId, sourceId, isSynced) {
const sync = insertEntitySync(entityName, entityId, sourceId, isSynced);
cls.addSyncRow(sync);
}
async function addEntitySyncsForSector(entityName, entityPrimaryKey, sector) {
function addEntitySyncsForSector(entityName, entityPrimaryKey, sector) {
const startTime = Date.now();
await sql.transactional(async () => {
const entityIds = await sql.getColumn(`SELECT ${entityPrimaryKey} FROM ${entityName} WHERE SUBSTR(${entityPrimaryKey}, 1, 1) = ?`, [sector]);
sql.transactional(() => {
const entityIds = sql.getColumn(`SELECT ${entityPrimaryKey} FROM ${entityName} WHERE SUBSTR(${entityPrimaryKey}, 1, 1) = ?`, [sector]);
for (const entityId of entityIds) {
if (entityName === 'options') {
const isSynced = await sql.getValue(`SELECT isSynced FROM options WHERE name = ?`, [entityId]);
const isSynced = sql.getValue(`SELECT isSynced FROM options WHERE name = ?`, [entityId]);
if (!isSynced) {
continue;
}
}
await insertEntitySync(entityName, entityId, 'content-check', true);
insertEntitySync(entityName, entityId, 'content-check', true);
}
});
log.info(`Added sector ${sector} of ${entityName} to sync queue in ${Date.now() - startTime}ms.`);
}
async function cleanupSyncRowsForMissingEntities(entityName, entityPrimaryKey) {
await sql.execute(`
function cleanupSyncRowsForMissingEntities(entityName, entityPrimaryKey) {
sql.execute(`
DELETE
FROM sync
WHERE sync.entityName = '${entityName}'
AND sync.entityId NOT IN (SELECT ${entityPrimaryKey} FROM ${entityName})`);
}
async function fillSyncRows(entityName, entityPrimaryKey, condition = '') {
function fillSyncRows(entityName, entityPrimaryKey, condition = '') {
try {
await cleanupSyncRowsForMissingEntities(entityName, entityPrimaryKey);
cleanupSyncRowsForMissingEntities(entityName, entityPrimaryKey);
const entityIds = await sql.getColumn(`SELECT ${entityPrimaryKey} FROM ${entityName}`
const entityIds = sql.getColumn(`SELECT ${entityPrimaryKey} FROM ${entityName}`
+ (condition ? ` WHERE ${condition}` : ''));
let createdCount = 0;
for (const entityId of entityIds) {
const existingRows = await sql.getValue("SELECT COUNT(1) FROM sync WHERE entityName = ? AND entityId = ?", [entityName, entityId]);
const existingRows = sql.getValue("SELECT COUNT(1) FROM sync WHERE entityName = ? AND entityId = ?", [entityName, entityId]);
// we don't want to replace existing entities (which would effectively cause full resync)
if (existingRows === 0) {
createdCount++;
await sql.insert("sync", {
sql.insert("sync", {
entityName: entityName,
entityId: entityId,
sourceId: "SYNC_FILL",
@ -98,31 +98,31 @@ async function fillSyncRows(entityName, entityPrimaryKey, condition = '') {
}
}
async function fillAllSyncRows() {
await sql.execute("DELETE FROM sync");
function fillAllSyncRows() {
sql.execute("DELETE FROM sync");
await fillSyncRows("notes", "noteId");
await fillSyncRows("note_contents", "noteId");
await fillSyncRows("branches", "branchId");
await fillSyncRows("note_revisions", "noteRevisionId");
await fillSyncRows("note_revision_contents", "noteRevisionId");
await fillSyncRows("recent_notes", "noteId");
await fillSyncRows("attributes", "attributeId");
await fillSyncRows("api_tokens", "apiTokenId");
await fillSyncRows("options", "name", 'isSynced = 1');
fillSyncRows("notes", "noteId");
fillSyncRows("note_contents", "noteId");
fillSyncRows("branches", "branchId");
fillSyncRows("note_revisions", "noteRevisionId");
fillSyncRows("note_revision_contents", "noteRevisionId");
fillSyncRows("recent_notes", "noteId");
fillSyncRows("attributes", "attributeId");
fillSyncRows("api_tokens", "apiTokenId");
fillSyncRows("options", "name", 'isSynced = 1');
}
module.exports = {
addNoteSync: async (noteId, sourceId) => await addEntitySync("notes", noteId, sourceId),
addNoteContentSync: async (noteId, sourceId) => await addEntitySync("note_contents", noteId, sourceId),
addBranchSync: async (branchId, sourceId) => await addEntitySync("branches", branchId, sourceId),
addNoteReorderingSync: async (parentNoteId, sourceId) => await addEntitySync("note_reordering", parentNoteId, sourceId),
addNoteRevisionSync: async (noteRevisionId, sourceId) => await addEntitySync("note_revisions", noteRevisionId, sourceId),
addNoteRevisionContentSync: async (noteRevisionId, sourceId) => await addEntitySync("note_revision_contents", noteRevisionId, sourceId),
addOptionsSync: async (name, sourceId, isSynced) => await addEntitySync("options", name, sourceId, isSynced),
addRecentNoteSync: async (noteId, sourceId) => await addEntitySync("recent_notes", noteId, sourceId),
addAttributeSync: async (attributeId, sourceId) => await addEntitySync("attributes", attributeId, sourceId),
addApiTokenSync: async (apiTokenId, sourceId) => await addEntitySync("api_tokens", apiTokenId, sourceId),
addNoteSync: (noteId, sourceId) => addEntitySync("notes", noteId, sourceId),
addNoteContentSync: (noteId, sourceId) => addEntitySync("note_contents", noteId, sourceId),
addBranchSync: (branchId, sourceId) => addEntitySync("branches", branchId, sourceId),
addNoteReorderingSync: (parentNoteId, sourceId) => addEntitySync("note_reordering", parentNoteId, sourceId),
addNoteRevisionSync: (noteRevisionId, sourceId) => addEntitySync("note_revisions", noteRevisionId, sourceId),
addNoteRevisionContentSync: (noteRevisionId, sourceId) => addEntitySync("note_revision_contents", noteRevisionId, sourceId),
addOptionsSync: (name, sourceId, isSynced) => addEntitySync("options", name, sourceId, isSynced),
addRecentNoteSync: (noteId, sourceId) => addEntitySync("recent_notes", noteId, sourceId),
addAttributeSync: (attributeId, sourceId) => addEntitySync("attributes", attributeId, sourceId),
addApiTokenSync: (apiTokenId, sourceId) => addEntitySync("api_tokens", apiTokenId, sourceId),
addEntitySync,
fillAllSyncRows,
addEntitySyncsForSector,

View File

@ -3,7 +3,7 @@ const log = require('./log');
const syncTableService = require('./sync_table');
const eventService = require('./events');
async function updateEntity(sync, entity, sourceId) {
function updateEntity(sync, entity, sourceId) {
// can be undefined for options with isSynced=false
if (!entity) {
return false;
@ -13,34 +13,34 @@ async function updateEntity(sync, entity, sourceId) {
let updated;
if (entityName === 'notes') {
updated = await updateNote(entity, sourceId);
updated = updateNote(entity, sourceId);
}
else if (entityName === 'note_contents') {
updated = await updateNoteContent(entity, sourceId);
updated = updateNoteContent(entity, sourceId);
}
else if (entityName === 'branches') {
updated = await updateBranch(entity, sourceId);
updated = updateBranch(entity, sourceId);
}
else if (entityName === 'note_revisions') {
updated = await updateNoteRevision(entity, sourceId);
updated = updateNoteRevision(entity, sourceId);
}
else if (entityName === 'note_revision_contents') {
updated = await updateNoteRevisionContent(entity, sourceId);
updated = updateNoteRevisionContent(entity, sourceId);
}
else if (entityName === 'note_reordering') {
updated = await updateNoteReordering(sync.entityId, entity, sourceId);
updated = updateNoteReordering(sync.entityId, entity, sourceId);
}
else if (entityName === 'options') {
updated = await updateOptions(entity, sourceId);
updated = updateOptions(entity, sourceId);
}
else if (entityName === 'recent_notes') {
updated = await updateRecentNotes(entity, sourceId);
updated = updateRecentNotes(entity, sourceId);
}
else if (entityName === 'attributes') {
updated = await updateAttribute(entity, sourceId);
updated = updateAttribute(entity, sourceId);
}
else if (entityName === 'api_tokens') {
updated = await updateApiToken(entity, sourceId);
updated = updateApiToken(entity, sourceId);
}
else {
throw new Error(`Unrecognized entity type ${entityName}`);
@ -50,7 +50,7 @@ async function updateEntity(sync, entity, sourceId) {
// the title and content are not available decrypted as listeners would expect
if (updated &&
(!['notes', 'note_contents', 'note_revisions', 'note_revision_contents'].includes(entityName) || !entity.isProtected)) {
await eventService.emit(eventService.ENTITY_SYNCED, {
eventService.emit(eventService.ENTITY_SYNCED, {
entityName,
entity
});
@ -79,14 +79,14 @@ function shouldWeUpdateEntity(localEntity, remoteEntity) {
return false;
}
async function updateNote(remoteEntity, sourceId) {
const localEntity = await sql.getRow("SELECT * FROM notes WHERE noteId = ?", [remoteEntity.noteId]);
function updateNote(remoteEntity, sourceId) {
const localEntity = sql.getRow("SELECT * FROM notes WHERE noteId = ?", [remoteEntity.noteId]);
if (shouldWeUpdateEntity(localEntity, remoteEntity)) {
await sql.transactional(async () => {
await sql.replace("notes", remoteEntity);
sql.transactional(() => {
sql.replace("notes", remoteEntity);
await syncTableService.addNoteSync(remoteEntity.noteId, sourceId);
syncTableService.addNoteSync(remoteEntity.noteId, sourceId);
});
return true;
@ -95,16 +95,16 @@ async function updateNote(remoteEntity, sourceId) {
return false;
}
async function updateNoteContent(remoteEntity, sourceId) {
const localEntity = await sql.getRow("SELECT * FROM note_contents WHERE noteId = ?", [remoteEntity.noteId]);
function updateNoteContent(remoteEntity, sourceId) {
const localEntity = sql.getRow("SELECT * FROM note_contents WHERE noteId = ?", [remoteEntity.noteId]);
if (shouldWeUpdateEntity(localEntity, remoteEntity)) {
remoteEntity.content = remoteEntity.content === null ? null : Buffer.from(remoteEntity.content, 'base64');
await sql.transactional(async () => {
await sql.replace("note_contents", remoteEntity);
sql.transactional(() => {
sql.replace("note_contents", remoteEntity);
await syncTableService.addNoteContentSync(remoteEntity.noteId, sourceId);
syncTableService.addNoteContentSync(remoteEntity.noteId, sourceId);
});
return true;
@ -113,20 +113,20 @@ async function updateNoteContent(remoteEntity, sourceId) {
return false;
}
async function updateBranch(remoteEntity, sourceId) {
const localEntity = await sql.getRowOrNull("SELECT * FROM branches WHERE branchId = ?", [remoteEntity.branchId]);
function updateBranch(remoteEntity, sourceId) {
const localEntity = sql.getRowOrNull("SELECT * FROM branches WHERE branchId = ?", [remoteEntity.branchId]);
if (shouldWeUpdateEntity(localEntity, remoteEntity)) {
await sql.transactional(async () => {
sql.transactional(() => {
// isExpanded is not synced unless it's a new branch instance
// otherwise in case of full new sync we'll get all branches (even root) collapsed.
if (localEntity) {
delete remoteEntity.isExpanded;
}
await sql.replace('branches', remoteEntity);
sql.replace('branches', remoteEntity);
await syncTableService.addBranchSync(remoteEntity.branchId, sourceId);
syncTableService.addBranchSync(remoteEntity.branchId, sourceId);
});
return true;
@ -135,30 +135,30 @@ async function updateBranch(remoteEntity, sourceId) {
return false;
}
async function updateNoteRevision(remoteEntity, sourceId) {
const localEntity = await sql.getRowOrNull("SELECT * FROM note_revisions WHERE noteRevisionId = ?", [remoteEntity.noteRevisionId]);
function updateNoteRevision(remoteEntity, sourceId) {
const localEntity = sql.getRowOrNull("SELECT * FROM note_revisions WHERE noteRevisionId = ?", [remoteEntity.noteRevisionId]);
await sql.transactional(async () => {
sql.transactional(() => {
if (shouldWeUpdateEntity(localEntity, remoteEntity)) {
await sql.replace('note_revisions', remoteEntity);
sql.replace('note_revisions', remoteEntity);
await syncTableService.addNoteRevisionSync(remoteEntity.noteRevisionId, sourceId);
syncTableService.addNoteRevisionSync(remoteEntity.noteRevisionId, sourceId);
log.info("Update/sync note revision " + remoteEntity.noteRevisionId);
}
});
}
async function updateNoteRevisionContent(remoteEntity, sourceId) {
const localEntity = await sql.getRowOrNull("SELECT * FROM note_revision_contents WHERE noteRevisionId = ?", [remoteEntity.noteRevisionId]);
function updateNoteRevisionContent(remoteEntity, sourceId) {
const localEntity = sql.getRowOrNull("SELECT * FROM note_revision_contents WHERE noteRevisionId = ?", [remoteEntity.noteRevisionId]);
if (shouldWeUpdateEntity(localEntity, remoteEntity)) {
await sql.transactional(async () => {
sql.transactional(() => {
remoteEntity.content = remoteEntity.content === null ? null : Buffer.from(remoteEntity.content, 'base64');
await sql.replace('note_revision_contents', remoteEntity);
sql.replace('note_revision_contents', remoteEntity);
await syncTableService.addNoteRevisionContentSync(remoteEntity.noteRevisionId, sourceId);
syncTableService.addNoteRevisionContentSync(remoteEntity.noteRevisionId, sourceId);
});
return true;
@ -167,30 +167,30 @@ async function updateNoteRevisionContent(remoteEntity, sourceId) {
return false;
}
async function updateNoteReordering(entityId, remote, sourceId) {
await sql.transactional(async () => {
function updateNoteReordering(entityId, remote, sourceId) {
sql.transactional(() => {
for (const key in remote) {
await sql.execute("UPDATE branches SET notePosition = ? WHERE branchId = ?", [remote[key], key]);
sql.execute("UPDATE branches SET notePosition = ? WHERE branchId = ?", [remote[key], key]);
}
await syncTableService.addNoteReorderingSync(entityId, sourceId);
syncTableService.addNoteReorderingSync(entityId, sourceId);
});
return true;
}
async function updateOptions(remoteEntity, sourceId) {
const localEntity = await sql.getRowOrNull("SELECT * FROM options WHERE name = ?", [remoteEntity.name]);
function updateOptions(remoteEntity, sourceId) {
const localEntity = sql.getRowOrNull("SELECT * FROM options WHERE name = ?", [remoteEntity.name]);
if (localEntity && !localEntity.isSynced) {
return;
}
if (shouldWeUpdateEntity(localEntity, remoteEntity)) {
await sql.transactional(async () => {
await sql.replace('options', remoteEntity);
sql.transactional(() => {
sql.replace('options', remoteEntity);
await syncTableService.addOptionsSync(remoteEntity.name, sourceId, true);
syncTableService.addOptionsSync(remoteEntity.name, sourceId, true);
});
return true;
@ -199,14 +199,14 @@ async function updateOptions(remoteEntity, sourceId) {
return false;
}
async function updateRecentNotes(remoteEntity, sourceId) {
const localEntity = await sql.getRowOrNull("SELECT * FROM recent_notes WHERE noteId = ?", [remoteEntity.noteId]);
function updateRecentNotes(remoteEntity, sourceId) {
const localEntity = sql.getRowOrNull("SELECT * FROM recent_notes WHERE noteId = ?", [remoteEntity.noteId]);
if (shouldWeUpdateEntity(localEntity, remoteEntity)) {
await sql.transactional(async () => {
await sql.replace('recent_notes', remoteEntity);
sql.transactional(() => {
sql.replace('recent_notes', remoteEntity);
await syncTableService.addRecentNoteSync(remoteEntity.noteId, sourceId);
syncTableService.addRecentNoteSync(remoteEntity.noteId, sourceId);
});
return true;
@ -215,14 +215,14 @@ async function updateRecentNotes(remoteEntity, sourceId) {
return false;
}
async function updateAttribute(remoteEntity, sourceId) {
const localEntity = await sql.getRow("SELECT * FROM attributes WHERE attributeId = ?", [remoteEntity.attributeId]);
function updateAttribute(remoteEntity, sourceId) {
const localEntity = sql.getRow("SELECT * FROM attributes WHERE attributeId = ?", [remoteEntity.attributeId]);
if (shouldWeUpdateEntity(localEntity, remoteEntity)) {
await sql.transactional(async () => {
await sql.replace("attributes", remoteEntity);
sql.transactional(() => {
sql.replace("attributes", remoteEntity);
await syncTableService.addAttributeSync(remoteEntity.attributeId, sourceId);
syncTableService.addAttributeSync(remoteEntity.attributeId, sourceId);
});
return true;
@ -231,14 +231,14 @@ async function updateAttribute(remoteEntity, sourceId) {
return false;
}
async function updateApiToken(entity, sourceId) {
const apiTokenId = await sql.getRow("SELECT * FROM api_tokens WHERE apiTokenId = ?", [entity.apiTokenId]);
function updateApiToken(entity, sourceId) {
const apiTokenId = sql.getRow("SELECT * FROM api_tokens WHERE apiTokenId = ?", [entity.apiTokenId]);
if (!apiTokenId) {
await sql.transactional(async () => {
await sql.replace("api_tokens", entity);
sql.transactional(() => {
sql.replace("api_tokens", entity);
await syncTableService.addApiTokenSync(entity.apiTokenId, sourceId);
syncTableService.addApiTokenSync(entity.apiTokenId, sourceId);
});
return true;

View File

@ -7,9 +7,9 @@ const syncTableService = require('./sync_table');
const protectedSessionService = require('./protected_session');
const noteCacheService = require('./note_cache/note_cache.js');
async function getNotes(noteIds) {
function getNotes(noteIds) {
// we return also deleted notes which have been specifically asked for
const notes = await sql.getManyRows(`
const notes = sql.getManyRows(`
SELECT
noteId,
title,
@ -23,7 +23,7 @@ async function getNotes(noteIds) {
protectedSessionService.decryptNotes(notes);
await noteCacheService.loadedPromise;
noteCacheService.loadedPromise;
notes.forEach(note => {
note.isProtected = !!note.isProtected
@ -32,7 +32,7 @@ async function getNotes(noteIds) {
return notes;
}
async function validateParentChild(parentNoteId, childNoteId, branchId = null) {
function validateParentChild(parentNoteId, childNoteId, branchId = null) {
if (childNoteId === 'root') {
return { success: false, message: 'Cannot move root note.'};
}
@ -42,7 +42,7 @@ async function validateParentChild(parentNoteId, childNoteId, branchId = null) {
return { success: false, message: 'Cannot move anything into root parent.' };
}
const existing = await getExistingBranch(parentNoteId, childNoteId);
const existing = getExistingBranch(parentNoteId, childNoteId);
if (existing && (branchId === null || existing.branchId !== branchId)) {
return {
@ -51,7 +51,7 @@ async function validateParentChild(parentNoteId, childNoteId, branchId = null) {
};
}
if (!await checkTreeCycle(parentNoteId, childNoteId)) {
if (!checkTreeCycle(parentNoteId, childNoteId)) {
return {
success: false,
message: 'Moving/cloning note here would create cycle.'
@ -61,20 +61,20 @@ async function validateParentChild(parentNoteId, childNoteId, branchId = null) {
return { success: true };
}
async function getExistingBranch(parentNoteId, childNoteId) {
return await repository.getEntity('SELECT * FROM branches WHERE noteId = ? AND parentNoteId = ? AND isDeleted = 0', [childNoteId, parentNoteId]);
function getExistingBranch(parentNoteId, childNoteId) {
return repository.getEntity('SELECT * FROM branches WHERE noteId = ? AND parentNoteId = ? AND isDeleted = 0', [childNoteId, parentNoteId]);
}
/**
* Tree cycle can be created when cloning or when moving existing clone. This method should detect both cases.
*/
async function checkTreeCycle(parentNoteId, childNoteId) {
function checkTreeCycle(parentNoteId, childNoteId) {
const subtreeNoteIds = [];
// we'll load the whole sub tree - because the cycle can start in one of the notes in the sub tree
await loadSubtreeNoteIds(childNoteId, subtreeNoteIds);
loadSubtreeNoteIds(childNoteId, subtreeNoteIds);
async function checkTreeCycleInner(parentNoteId) {
function checkTreeCycleInner(parentNoteId) {
if (parentNoteId === 'root') {
return true;
}
@ -85,10 +85,10 @@ async function checkTreeCycle(parentNoteId, childNoteId) {
return false;
}
const parentNoteIds = await sql.getColumn("SELECT DISTINCT parentNoteId FROM branches WHERE noteId = ? AND isDeleted = 0", [parentNoteId]);
const parentNoteIds = sql.getColumn("SELECT DISTINCT parentNoteId FROM branches WHERE noteId = ? AND isDeleted = 0", [parentNoteId]);
for (const pid of parentNoteIds) {
if (!await checkTreeCycleInner(pid)) {
if (!checkTreeCycleInner(pid)) {
return false;
}
}
@ -96,22 +96,22 @@ async function checkTreeCycle(parentNoteId, childNoteId) {
return true;
}
return await checkTreeCycleInner(parentNoteId);
return checkTreeCycleInner(parentNoteId);
}
async function loadSubtreeNoteIds(parentNoteId, subtreeNoteIds) {
function loadSubtreeNoteIds(parentNoteId, subtreeNoteIds) {
subtreeNoteIds.push(parentNoteId);
const children = await sql.getColumn("SELECT noteId FROM branches WHERE parentNoteId = ? AND isDeleted = 0", [parentNoteId]);
const children = sql.getColumn("SELECT noteId FROM branches WHERE parentNoteId = ? AND isDeleted = 0", [parentNoteId]);
for (const childNoteId of children) {
await loadSubtreeNoteIds(childNoteId, subtreeNoteIds);
loadSubtreeNoteIds(childNoteId, subtreeNoteIds);
}
}
async function sortNotesAlphabetically(parentNoteId, directoriesFirst = false) {
await sql.transactional(async () => {
const notes = await sql.getRows(
function sortNotesAlphabetically(parentNoteId, directoriesFirst = false) {
sql.transactional(() => {
const notes = sql.getRows(
`SELECT branches.branchId, notes.noteId, title, isProtected,
CASE WHEN COUNT(childBranches.noteId) > 0 THEN 1 ELSE 0 END AS hasChildren
FROM notes
@ -135,28 +135,28 @@ async function sortNotesAlphabetically(parentNoteId, directoriesFirst = false) {
let position = 10;
for (const note of notes) {
await sql.execute("UPDATE branches SET notePosition = ? WHERE branchId = ?",
sql.execute("UPDATE branches SET notePosition = ? WHERE branchId = ?",
[position, note.branchId]);
position += 10;
}
await syncTableService.addNoteReorderingSync(parentNoteId);
syncTableService.addNoteReorderingSync(parentNoteId);
});
}
/**
* @deprecated - this will be removed in the future
*/
async function setNoteToParent(noteId, prefix, parentNoteId) {
const parentNote = await repository.getNote(parentNoteId);
function setNoteToParent(noteId, prefix, parentNoteId) {
const parentNote = repository.getNote(parentNoteId);
if (parentNote && parentNote.isDeleted) {
throw new Error(`Cannot move note to deleted parent note ${parentNoteId}`);
}
// case where there might be more such branches is ignored. It's expected there should be just one
const branch = await repository.getEntity("SELECT * FROM branches WHERE isDeleted = 0 AND noteId = ? AND prefix = ?", [noteId, prefix]);
const branch = repository.getEntity("SELECT * FROM branches WHERE isDeleted = 0 AND noteId = ? AND prefix = ?", [noteId, prefix]);
if (branch) {
if (!parentNoteId) {
@ -167,23 +167,23 @@ async function setNoteToParent(noteId, prefix, parentNoteId) {
branch.prefix = prefix;
}
await branch.save();
branch.save();
}
else if (parentNoteId) {
const note = await repository.getNote(noteId);
const note = repository.getNote(noteId);
if (note.isDeleted) {
throw new Error(`Cannot create a branch for ${noteId} which is deleted.`);
}
const branch = await repository.getEntity('SELECT * FROM branches WHERE isDeleted = 0 AND noteId = ? AND parentNoteId = ?', [noteId, parentNoteId]);
const branch = repository.getEntity('SELECT * FROM branches WHERE isDeleted = 0 AND noteId = ? AND parentNoteId = ?', [noteId, parentNoteId]);
if (branch) {
branch.prefix = prefix;
await branch.save();
branch.save();
}
else {
await new Branch({
new Branch({
noteId: noteId,
parentNoteId: parentNoteId,
prefix: prefix

View File

@ -69,10 +69,10 @@ function prepareSqlForLike(prefix, str, suffix) {
return `'${prefix}${value}${suffix}' ESCAPE '\\'`;
}
async function stopWatch(what, func) {
function stopWatch(what, func) {
const start = new Date();
const ret = await func();
const ret = func();
const tookMs = Date.now() - start.getTime();

View File

@ -24,9 +24,9 @@ async function createExtraWindow(notePath) {
webPreferences: {
enableRemoteModule: true,
nodeIntegration: true,
spellcheck: await optionService.getOptionBool('spellCheckEnabled')
spellcheck: optionService.getOptionBool('spellCheckEnabled')
},
frame: await optionService.getOptionBool('nativeTitleBarVisible'),
frame: optionService.getOptionBool('nativeTitleBarVisible'),
icon: getIcon()
});
@ -47,7 +47,7 @@ async function createMainWindow() {
defaultHeight: 800
});
const spellcheckEnabled = await optionService.getOptionBool('spellCheckEnabled');
const spellcheckEnabled = optionService.getOptionBool('spellCheckEnabled');
const {BrowserWindow} = require('electron'); // should not be statically imported
mainWindow = new BrowserWindow({
@ -61,7 +61,7 @@ async function createMainWindow() {
nodeIntegration: true,
spellcheck: spellcheckEnabled
},
frame: await optionService.getOptionBool('nativeTitleBarVisible'),
frame: optionService.getOptionBool('nativeTitleBarVisible'),
icon: getIcon()
});
@ -91,7 +91,7 @@ async function createMainWindow() {
});
if (spellcheckEnabled) {
const languageCodes = (await optionService.getOption('spellCheckLanguageCode'))
const languageCodes = (optionService.getOption('spellCheckLanguageCode'))
.split(',')
.map(code => code.trim());
@ -127,12 +127,12 @@ function closeSetupWindow() {
}
}
async function registerGlobalShortcuts() {
function registerGlobalShortcuts() {
const {globalShortcut} = require('electron');
await sqlInit.dbReady;
sqlInit.dbReady;
const allActions = await keyboardActionsService.getKeyboardActions();
const allActions = keyboardActionsService.getKeyboardActions();
for (const action of allActions) {
if (!action.effectiveShortcuts) {
@ -143,7 +143,7 @@ async function registerGlobalShortcuts() {
if (shortcut.startsWith('global:')) {
const translatedShortcut = shortcut.substr(7);
const result = globalShortcut.register(translatedShortcut, cls.wrap(async () => {
const result = globalShortcut.register(translatedShortcut, cls.wrap(() => {
// window may be hidden / not in focus
mainWindow.focus();

View File

@ -32,7 +32,7 @@ function init(httpServer, sessionParser) {
console.log(`websocket client connected`);
ws.on('message', messageJson => {
ws.on('message', async messageJson => {
const message = JSON.parse(messageJson);
if (message.type === 'log-error') {
@ -41,7 +41,7 @@ function init(httpServer, sessionParser) {
else if (message.type === 'ping') {
lastAcceptedSyncIds[ws.id] = message.lastSyncId;
syncMutexService.doExclusively(async () => await sendPing(ws));
await syncMutexService.doExclusively(() => sendPing(ws));
}
else {
log.error('Unrecognized message: ');
@ -73,36 +73,36 @@ function sendMessageToAllClients(message) {
}
}
async function fillInAdditionalProperties(sync) {
function fillInAdditionalProperties(sync) {
// fill in some extra data needed by the frontend
if (sync.entityName === 'attributes') {
sync.entity = await sql.getRow(`SELECT * FROM attributes WHERE attributeId = ?`, [sync.entityId]);
sync.entity = sql.getRow(`SELECT * FROM attributes WHERE attributeId = ?`, [sync.entityId]);
} else if (sync.entityName === 'branches') {
sync.entity = await sql.getRow(`SELECT * FROM branches WHERE branchId = ?`, [sync.entityId]);
sync.entity = sql.getRow(`SELECT * FROM branches WHERE branchId = ?`, [sync.entityId]);
} else if (sync.entityName === 'notes') {
sync.entity = await sql.getRow(`SELECT * FROM notes WHERE noteId = ?`, [sync.entityId]);
sync.entity = sql.getRow(`SELECT * FROM notes WHERE noteId = ?`, [sync.entityId]);
if (sync.entity.isProtected) {
sync.entity.title = protectedSessionService.decryptString(sync.entity.title);
}
} else if (sync.entityName === 'note_revisions') {
sync.noteId = await sql.getValue(`SELECT noteId
sync.noteId = sql.getValue(`SELECT noteId
FROM note_revisions
WHERE noteRevisionId = ?`, [sync.entityId]);
} else if (sync.entityName === 'note_reordering') {
sync.positions = await sql.getMap(`SELECT branchId, notePosition FROM branches WHERE isDeleted = 0 AND parentNoteId = ?`, [sync.entityId]);
sync.positions = sql.getMap(`SELECT branchId, notePosition FROM branches WHERE isDeleted = 0 AND parentNoteId = ?`, [sync.entityId]);
}
else if (sync.entityName === 'options') {
sync.entity = await sql.getRow(`SELECT * FROM options WHERE name = ?`, [sync.entityId]);
sync.entity = sql.getRow(`SELECT * FROM options WHERE name = ?`, [sync.entityId]);
}
}
async function sendPing(client) {
function sendPing(client) {
const syncRows = cls.getSyncRows();
for (const sync of syncRows) {
try {
await fillInAdditionalProperties(sync);
fillInAdditionalProperties(sync);
}
catch (e) {
log.error("Could not fill additional properties for sync " + JSON.stringify(sync)

View File

@ -37,7 +37,7 @@ let httpServer;
async function startTrilium() {
const usedPort = await port;
const usedHost = await host;
const usedHost = host;
app.set('port', usedPort);
app.set('host', usedHost);
@ -98,10 +98,11 @@ async function startTrilium() {
throw error;
}
}
);
)
httpServer.on('listening', () => debug('Listening on port' + httpServer.address().port));
sqlInit.dbReady.then(() => ws.init(httpServer, sessionParser));
ws.init(httpServer, sessionParser);
if (utils.isElectron()) {
const electronRouting = require('./routes/electron');