mirror of
https://github.com/zadam/trilium.git
synced 2025-06-06 18:08:33 +02:00
syncification
This commit is contained in:
parent
30062d687f
commit
88348c560c
@ -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');
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
const appInfo = require('../../services/app_info');
|
||||
|
||||
async function getAppInfo() {
|
||||
function getAppInfo() {
|
||||
return appInfo;
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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');
|
||||
|
@ -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 = {
|
||||
|
@ -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
|
||||
|
@ -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 = {
|
||||
|
@ -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 = {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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];
|
||||
|
@ -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 = {
|
||||
|
@ -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 };
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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 (
|
||||
|
@ -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 = {
|
||||
|
@ -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, '-');
|
||||
|
@ -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 = {
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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 = {
|
||||
|
@ -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.`);
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
};
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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 {
|
||||
|
@ -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 = {
|
||||
|
@ -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 = {
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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>`);
|
||||
|
@ -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') {
|
||||
|
@ -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));
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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'));
|
||||
|
@ -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
|
||||
|
@ -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') {
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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));
|
||||
|
@ -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));
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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});
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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]);
|
||||
}
|
||||
|
@ -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}"`);
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
};
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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')
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -10,7 +10,7 @@ async function doExclusively(func) {
|
||||
const releaseMutex = await instance.acquire();
|
||||
|
||||
try {
|
||||
return await func();
|
||||
return func();
|
||||
}
|
||||
finally {
|
||||
releaseMutex();
|
||||
|
@ -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')
|
||||
};
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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)
|
||||
|
7
src/www
7
src/www
@ -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');
|
||||
|
Loading…
x
Reference in New Issue
Block a user