mirror of
https://github.com/zadam/trilium.git
synced 2024-12-25 08:43:03 +08:00
attachment ETAPI support WIP
This commit is contained in:
parent
49241ab318
commit
3b3f6082a7
19 changed files with 229 additions and 83 deletions
|
@ -29,12 +29,12 @@ function dumpDocument(documentPath, targetPath, options) {
|
|||
function dumpNote(targetPath, noteId) {
|
||||
console.log(`Reading note '${noteId}'`);
|
||||
|
||||
let childTargetPath, note, fileNameWithPath;
|
||||
let childTargetPath, noteRow, fileNameWithPath;
|
||||
|
||||
try {
|
||||
note = sql.getRow("SELECT * FROM notes WHERE noteId = ?", [noteId]);
|
||||
noteRow = sql.getRow("SELECT * FROM notes WHERE noteId = ?", [noteId]);
|
||||
|
||||
if (note.isDeleted) {
|
||||
if (noteRow.isDeleted) {
|
||||
stats.deleted++;
|
||||
|
||||
if (!options.includeDeleted) {
|
||||
|
@ -44,13 +44,13 @@ function dumpDocument(documentPath, targetPath, options) {
|
|||
}
|
||||
}
|
||||
|
||||
if (note.isProtected) {
|
||||
if (noteRow.isProtected) {
|
||||
stats.protected++;
|
||||
|
||||
note.title = decryptService.decryptString(dataKey, note.title);
|
||||
noteRow.title = decryptService.decryptString(dataKey, noteRow.title);
|
||||
}
|
||||
|
||||
let safeTitle = sanitize(note.title);
|
||||
let safeTitle = sanitize(noteRow.title);
|
||||
|
||||
if (safeTitle.length > 20) {
|
||||
safeTitle = safeTitle.substring(0, 20);
|
||||
|
@ -64,8 +64,8 @@ function dumpDocument(documentPath, targetPath, options) {
|
|||
|
||||
existingPaths[childTargetPath] = true;
|
||||
|
||||
if (note.noteId in noteIdToPath) {
|
||||
const message = `Note '${noteId}' has been already dumped to ${noteIdToPath[note.noteId]}`;
|
||||
if (noteRow.noteId in noteIdToPath) {
|
||||
const message = `Note '${noteId}' has been already dumped to ${noteIdToPath[noteRow.noteId]}`;
|
||||
|
||||
console.log(message);
|
||||
|
||||
|
@ -74,16 +74,16 @@ function dumpDocument(documentPath, targetPath, options) {
|
|||
return;
|
||||
}
|
||||
|
||||
let {content} = sql.getRow("SELECT content FROM blobs WHERE blobId = ?", [note.blobId]);
|
||||
let {content} = sql.getRow("SELECT content FROM blobs WHERE blobId = ?", [noteRow.blobId]);
|
||||
|
||||
if (content !== null && note.isProtected && dataKey) {
|
||||
if (content !== null && noteRow.isProtected && dataKey) {
|
||||
content = decryptService.decrypt(dataKey, content);
|
||||
}
|
||||
|
||||
if (isContentEmpty(content)) {
|
||||
console.log(`Note '${noteId}' is empty, skipping.`);
|
||||
} else {
|
||||
fileNameWithPath = extensionService.getFileName(note, childTargetPath, safeTitle);
|
||||
fileNameWithPath = extensionService.getFileName(noteRow, childTargetPath, safeTitle);
|
||||
|
||||
fs.writeFileSync(fileNameWithPath, content);
|
||||
|
||||
|
|
|
@ -187,7 +187,7 @@ class BBranch extends AbstractBeccaEntity {
|
|||
|
||||
// first delete children and then parent - this will show up better in recent changes
|
||||
|
||||
log.info(`Deleting note ${note.noteId}`);
|
||||
log.info(`Deleting note '${note.noteId}'`);
|
||||
|
||||
this.becca.notes[note.noteId].isBeingDeleted = true;
|
||||
|
||||
|
|
|
@ -1549,6 +1549,8 @@ class BNote extends AbstractBeccaEntity {
|
|||
}
|
||||
|
||||
get isDeleted() {
|
||||
// isBeingDeleted is relevant only in the transition period when the deletion process have begun, but not yet
|
||||
// finished (note is still in becca)
|
||||
return !(this.noteId in this.becca.notes) || this.isBeingDeleted;
|
||||
}
|
||||
|
||||
|
@ -1602,7 +1604,7 @@ class BNote extends AbstractBeccaEntity {
|
|||
/**
|
||||
* @returns {BAttachment}
|
||||
*/
|
||||
saveAttachment({attachmentId, role, mime, title, content}) {
|
||||
saveAttachment({attachmentId, role, mime, title, content, position}) {
|
||||
let attachment;
|
||||
|
||||
if (attachmentId) {
|
||||
|
@ -1613,15 +1615,13 @@ class BNote extends AbstractBeccaEntity {
|
|||
title,
|
||||
role,
|
||||
mime,
|
||||
isProtected: this.isProtected
|
||||
isProtected: this.isProtected,
|
||||
position
|
||||
});
|
||||
}
|
||||
|
||||
if (content !== undefined && content !== null) {
|
||||
content = content || "";
|
||||
attachment.setContent(content, {forceSave: true});
|
||||
} else {
|
||||
attachment.save();
|
||||
}
|
||||
|
||||
return attachment;
|
||||
}
|
||||
|
|
104
src/etapi/attachments.js
Normal file
104
src/etapi/attachments.js
Normal file
|
@ -0,0 +1,104 @@
|
|||
const becca = require("../becca/becca");
|
||||
const eu = require("./etapi_utils");
|
||||
const mappers = require("./mappers");
|
||||
const v = require("./validators");
|
||||
const utils = require("../services/utils.js");
|
||||
const noteService = require("../services/notes.js");
|
||||
|
||||
function register(router) {
|
||||
const ALLOWED_PROPERTIES_FOR_CREATE_ATTACHMENT = {
|
||||
'parentId': [v.notNull, v.isNoteId],
|
||||
'role': [v.notNull, v.isString],
|
||||
'mime': [v.notNull, v.isString],
|
||||
'title': [v.notNull, v.isString],
|
||||
'position': [v.notNull, v.isInteger],
|
||||
'content': [v.isString],
|
||||
};
|
||||
|
||||
eu.route(router, 'post' ,'/etapi/attachments', (req, res, next) => {
|
||||
const params = {};
|
||||
|
||||
eu.validateAndPatch(params, req.body, ALLOWED_PROPERTIES_FOR_CREATE_ATTACHMENT);
|
||||
|
||||
try {
|
||||
const note = becca.getNoteOrThrow(params.parentId);
|
||||
const attachment = note.saveAttachment(params);
|
||||
|
||||
res.status(201).json(mappers.mapAttachmentToPojo(attachment));
|
||||
}
|
||||
catch (e) {
|
||||
throw new eu.EtapiError(500, eu.GENERIC_CODE, e.message);
|
||||
}
|
||||
});
|
||||
|
||||
eu.route(router, 'get', '/etapi/attachments/:attachmentId', (req, res, next) => {
|
||||
const attachment = eu.getAndCheckAttachment(req.params.attachmentId);
|
||||
|
||||
res.json(mappers.mapAttachmentToPojo(attachment));
|
||||
});
|
||||
|
||||
const ALLOWED_PROPERTIES_FOR_PATCH = {
|
||||
'role': [v.notNull, v.isString],
|
||||
'mime': [v.notNull, v.isString],
|
||||
'title': [v.notNull, v.isString],
|
||||
'position': [v.notNull, v.isInteger],
|
||||
};
|
||||
|
||||
eu.route(router, 'patch' ,'/etapi/attachments/:attachmentId', (req, res, next) => {
|
||||
const attachment = eu.getAndCheckAttachment(req.params.attachmentId);
|
||||
|
||||
if (attachment.isProtected) {
|
||||
throw new eu.EtapiError(400, "ATTACHMENT_IS_PROTECTED", `Attachment '${req.params.attachmentId}' is protected and cannot be modified through ETAPI.`);
|
||||
}
|
||||
|
||||
eu.validateAndPatch(attachment, req.body, ALLOWED_PROPERTIES_FOR_PATCH);
|
||||
attachment.save();
|
||||
|
||||
res.json(mappers.mapAttachmentToPojo(attachment));
|
||||
});
|
||||
|
||||
eu.route(router, 'get', '/etapi/attachments/:attachmentId/content', (req, res, next) => {
|
||||
const attachment = eu.getAndCheckAttachment(req.params.attachmentId);
|
||||
|
||||
if (attachment.isProtected) {
|
||||
throw new eu.EtapiError(400, "ATTACHMENT_IS_PROTECTED", `Attachment '${req.params.attachmentId}' is protected and content cannot be read through ETAPI.`);
|
||||
}
|
||||
|
||||
const filename = utils.formatDownloadTitle(attachment.title, attachment.type, attachment.mime);
|
||||
|
||||
res.setHeader('Content-Disposition', utils.getContentDisposition(filename));
|
||||
|
||||
res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||
res.setHeader('Content-Type', attachment.mime);
|
||||
|
||||
res.send(attachment.getContent());
|
||||
});
|
||||
|
||||
eu.route(router, 'put', '/etapi/attachments/:attachmentId/content', (req, res, next) => {
|
||||
const attachment = eu.getAndCheckAttachment(req.params.attachmentId);
|
||||
|
||||
if (attachment.isProtected) {
|
||||
throw new eu.EtapiError(400, "ATTACHMENT_IS_PROTECTED", `Attachment '${req.params.attachmentId}' is protected and cannot be modified through ETAPI.`);
|
||||
}
|
||||
|
||||
attachment.setContent(req.body);
|
||||
|
||||
return res.sendStatus(204);
|
||||
});
|
||||
|
||||
eu.route(router, 'delete' ,'/etapi/attachments/:attachmentId', (req, res, next) => {
|
||||
const attachment = becca.getAttachment(req.params.attachmentId);
|
||||
|
||||
if (!attachment) {
|
||||
return res.sendStatus(204);
|
||||
}
|
||||
|
||||
attachment.markAsDeleted();
|
||||
|
||||
res.sendStatus(204);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
register
|
||||
};
|
|
@ -68,7 +68,7 @@ function register(router) {
|
|||
eu.route(router, 'delete' ,'/etapi/attributes/:attributeId', (req, res, next) => {
|
||||
const attribute = becca.getAttribute(req.params.attributeId);
|
||||
|
||||
if (!attribute || attribute.isDeleted) {
|
||||
if (!attribute) {
|
||||
return res.sendStatus(204);
|
||||
}
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@ function register(router) {
|
|||
eu.route(router, 'delete' ,'/etapi/branches/:branchId', (req, res, next) => {
|
||||
const branch = becca.getBranch(req.params.branchId);
|
||||
|
||||
if (!branch || branch.isDeleted) {
|
||||
if (!branch) {
|
||||
return res.sendStatus(204);
|
||||
}
|
||||
|
||||
|
|
|
@ -77,7 +77,18 @@ function getAndCheckNote(noteId) {
|
|||
return note;
|
||||
}
|
||||
else {
|
||||
throw new EtapiError(404, "NOTE_NOT_FOUND", `Note '${noteId}' not found`);
|
||||
throw new EtapiError(404, "NOTE_NOT_FOUND", `Note '${noteId}' not found.`);
|
||||
}
|
||||
}
|
||||
|
||||
function getAndCheckAttachment(attachmentId) {
|
||||
const attachment = becca.getAttachment(attachmentId, {includeContentLength: true});
|
||||
|
||||
if (attachment) {
|
||||
return attachment;
|
||||
}
|
||||
else {
|
||||
throw new EtapiError(404, "ATTACHMENT_NOT_FOUND", `Attachment '${attachmentId}' not found.`);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -88,7 +99,7 @@ function getAndCheckBranch(branchId) {
|
|||
return branch;
|
||||
}
|
||||
else {
|
||||
throw new EtapiError(404, "BRANCH_NOT_FOUND", `Branch '${branchId}' not found`);
|
||||
throw new EtapiError(404, "BRANCH_NOT_FOUND", `Branch '${branchId}' not found.`);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -99,7 +110,7 @@ function getAndCheckAttribute(attributeId) {
|
|||
return attribute;
|
||||
}
|
||||
else {
|
||||
throw new EtapiError(404, "ATTRIBUTE_NOT_FOUND", `Attribute '${attributeId}' not found`);
|
||||
throw new EtapiError(404, "ATTRIBUTE_NOT_FOUND", `Attribute '${attributeId}' not found.`);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -113,7 +124,7 @@ function validateAndPatch(target, source, allowedProperties) {
|
|||
const validationResult = validator(source[key]);
|
||||
|
||||
if (validationResult) {
|
||||
throw new EtapiError(400, "PROPERTY_VALIDATION_ERROR", `Validation failed on property '${key}': ${validationResult}`);
|
||||
throw new EtapiError(400, "PROPERTY_VALIDATION_ERROR", `Validation failed on property '${key}': ${validationResult}.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -134,5 +145,6 @@ module.exports = {
|
|||
validateAndPatch,
|
||||
getAndCheckNote,
|
||||
getAndCheckBranch,
|
||||
getAndCheckAttribute
|
||||
getAndCheckAttribute,
|
||||
getAndCheckAttachment
|
||||
}
|
||||
|
|
|
@ -46,8 +46,26 @@ function mapAttributeToPojo(attr) {
|
|||
};
|
||||
}
|
||||
|
||||
/** @param {BAttachment} attachment */
|
||||
function mapAttachmentToPojo(attachment) {
|
||||
return {
|
||||
attachmentId: attachment.attachmentId,
|
||||
parentId: attachment.parentId,
|
||||
role: attachment.role,
|
||||
mime: attachment.mime,
|
||||
title: attachment.title,
|
||||
position: attachment.position,
|
||||
blobId: attachment.blobId,
|
||||
dateModified: attachment.dateModified,
|
||||
utcDateModified: attachment.utcDateModified,
|
||||
utcDateScheduledForErasureSince: attachment.utcDateScheduledForErasureSince,
|
||||
contentLength: attachment.contentLength
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
mapNoteToPojo,
|
||||
mapBranchToPojo,
|
||||
mapAttributeToPojo
|
||||
mapAttributeToPojo,
|
||||
mapAttachmentToPojo
|
||||
};
|
||||
|
|
|
@ -14,7 +14,7 @@ function register(router) {
|
|||
const {search} = req.query;
|
||||
|
||||
if (!search?.trim()) {
|
||||
throw new eu.EtapiError(400, 'SEARCH_QUERY_PARAM_MANDATORY', "'search' query parameter is mandatory");
|
||||
throw new eu.EtapiError(400, 'SEARCH_QUERY_PARAM_MANDATORY', "'search' query parameter is mandatory.");
|
||||
}
|
||||
|
||||
const searchParams = parseSearchParams(req);
|
||||
|
@ -78,10 +78,10 @@ function register(router) {
|
|||
};
|
||||
|
||||
eu.route(router, 'patch' ,'/etapi/notes/:noteId', (req, res, next) => {
|
||||
const note = eu.getAndCheckNote(req.params.noteId)
|
||||
const note = eu.getAndCheckNote(req.params.noteId);
|
||||
|
||||
if (note.isProtected) {
|
||||
throw new eu.EtapiError(400, "NOTE_IS_PROTECTED", `Note '${req.params.noteId}' is protected and cannot be modified through ETAPI`);
|
||||
throw new eu.EtapiError(400, "NOTE_IS_PROTECTED", `Note '${req.params.noteId}' is protected and cannot be modified through ETAPI.`);
|
||||
}
|
||||
|
||||
eu.validateAndPatch(note, req.body, ALLOWED_PROPERTIES_FOR_PATCH);
|
||||
|
@ -95,7 +95,7 @@ function register(router) {
|
|||
|
||||
const note = becca.getNote(noteId);
|
||||
|
||||
if (!note || note.isDeleted) {
|
||||
if (!note) {
|
||||
return res.sendStatus(204);
|
||||
}
|
||||
|
||||
|
@ -107,6 +107,10 @@ function register(router) {
|
|||
eu.route(router, 'get', '/etapi/notes/:noteId/content', (req, res, next) => {
|
||||
const note = eu.getAndCheckNote(req.params.noteId);
|
||||
|
||||
if (note.isProtected) {
|
||||
throw new eu.EtapiError(400, "NOTE_IS_PROTECTED", `Note '${req.params.noteId}' is protected and content cannot be read through ETAPI.`);
|
||||
}
|
||||
|
||||
const filename = utils.formatDownloadTitle(note.title, note.type, note.mime);
|
||||
|
||||
res.setHeader('Content-Disposition', utils.getContentDisposition(filename));
|
||||
|
@ -120,6 +124,10 @@ function register(router) {
|
|||
eu.route(router, 'put', '/etapi/notes/:noteId/content', (req, res, next) => {
|
||||
const note = eu.getAndCheckNote(req.params.noteId);
|
||||
|
||||
if (note.isProtected) {
|
||||
throw new eu.EtapiError(400, "NOTE_IS_PROTECTED", `Note '${req.params.noteId}' is protected and cannot be modified through ETAPI.`);
|
||||
}
|
||||
|
||||
note.setContent(req.body);
|
||||
|
||||
noteService.asyncPostProcessContent(note, req.body);
|
||||
|
@ -132,7 +140,7 @@ function register(router) {
|
|||
const format = req.query.format || "html";
|
||||
|
||||
if (!["html", "markdown"].includes(format)) {
|
||||
throw new eu.EtapiError(400, "UNRECOGNIZED_EXPORT_FORMAT", `Unrecognized export format '${format}', supported values are 'html' (default) or 'markdown'`);
|
||||
throw new eu.EtapiError(400, "UNRECOGNIZED_EXPORT_FORMAT", `Unrecognized export format '${format}', supported values are 'html' (default) or 'markdown'.`);
|
||||
}
|
||||
|
||||
const taskContext = new TaskContext('no-progress-reporting');
|
||||
|
@ -153,6 +161,15 @@ function register(router) {
|
|||
|
||||
return res.sendStatus(204);
|
||||
});
|
||||
|
||||
eu.route(router, 'get', '/etapi/notes/:noteId/attachments', (req, res, next) => {
|
||||
const note = eu.getAndCheckNote(req.params.noteId);
|
||||
const attachments = note.getAttachments({includeContentLength: true})
|
||||
|
||||
res.json(
|
||||
attachments.map(attachment => mappers.mapAttachmentToPojo(attachment))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function parseSearchParams(req) {
|
||||
|
@ -186,7 +203,7 @@ function parseBoolean(obj, name) {
|
|||
}
|
||||
|
||||
if (!['true', 'false'].includes(obj[name])) {
|
||||
throw new eu.EtapiError(400, SEARCH_PARAM_ERROR, `Cannot parse boolean '${name}' value '${obj[name]}, allowed values are 'true' and 'false'`);
|
||||
throw new eu.EtapiError(400, SEARCH_PARAM_ERROR, `Cannot parse boolean '${name}' value '${obj[name]}, allowed values are 'true' and 'false'.`);
|
||||
}
|
||||
|
||||
return obj[name] === 'true';
|
||||
|
@ -200,7 +217,7 @@ function parseOrderDirection(obj, name) {
|
|||
const integer = parseInt(obj[name]);
|
||||
|
||||
if (!['asc', 'desc'].includes(obj[name])) {
|
||||
throw new eu.EtapiError(400, SEARCH_PARAM_ERROR, `Cannot parse order direction value '${obj[name]}, allowed values are 'asc' and 'desc'`);
|
||||
throw new eu.EtapiError(400, SEARCH_PARAM_ERROR, `Cannot parse order direction value '${obj[name]}, allowed values are 'asc' and 'desc'.`);
|
||||
}
|
||||
|
||||
return integer;
|
||||
|
@ -214,7 +231,7 @@ function parseInteger(obj, name) {
|
|||
const integer = parseInt(obj[name]);
|
||||
|
||||
if (Number.isNaN(integer)) {
|
||||
throw new eu.EtapiError(400, SEARCH_PARAM_ERROR, `Cannot parse integer '${name}' value '${obj[name]}`);
|
||||
throw new eu.EtapiError(400, SEARCH_PARAM_ERROR, `Cannot parse integer '${name}' value '${obj[name]}'.`);
|
||||
}
|
||||
|
||||
return integer;
|
||||
|
|
|
@ -149,7 +149,7 @@ function getEditedNotesOnDate(req) {
|
|||
}
|
||||
|
||||
return notes.map(note => {
|
||||
const notePath = note.isDeleted ? null : getNotePathData(note);
|
||||
const notePath = getNotePathData(note);
|
||||
|
||||
const notePojo = note.getPojo();
|
||||
notePojo.notePath = notePath ? notePath.notePath : null;
|
||||
|
|
|
@ -11,8 +11,8 @@ const ValidationError = require("../../errors/validation_error");
|
|||
function searchFromNote(req) {
|
||||
const note = becca.getNoteOrThrow(req.params.noteId);
|
||||
|
||||
if (note.isDeleted) {
|
||||
// this can be triggered from recent changes, and it's harmless to return empty list rather than fail
|
||||
if (!note) {
|
||||
// this can be triggered from recent changes, and it's harmless to return an empty list rather than fail
|
||||
return [];
|
||||
}
|
||||
|
||||
|
@ -26,8 +26,8 @@ function searchFromNote(req) {
|
|||
function searchAndExecute(req) {
|
||||
const note = becca.getNoteOrThrow(req.params.noteId);
|
||||
|
||||
if (note.isDeleted) {
|
||||
// this can be triggered from recent changes, and it's harmless to return empty list rather than fail
|
||||
if (!note) {
|
||||
// this can be triggered from recent changes, and it's harmless to return an empty list rather than fail
|
||||
return [];
|
||||
}
|
||||
|
||||
|
|
|
@ -63,6 +63,7 @@ const shareRoutes = require('../share/routes');
|
|||
|
||||
const etapiAuthRoutes = require('../etapi/auth');
|
||||
const etapiAppInfoRoutes = require('../etapi/app_info');
|
||||
const etapiAttachmentRoutes = require('../etapi/attachments');
|
||||
const etapiAttributeRoutes = require('../etapi/attributes');
|
||||
const etapiBranchRoutes = require('../etapi/branches');
|
||||
const etapiNoteRoutes = require('../etapi/notes');
|
||||
|
@ -332,6 +333,7 @@ function register(app) {
|
|||
|
||||
etapiAuthRoutes.register(router, [loginRateLimiter]);
|
||||
etapiAppInfoRoutes.register(router);
|
||||
etapiAttachmentRoutes.register(router);
|
||||
etapiAttributeRoutes.register(router);
|
||||
etapiBranchRoutes.register(router);
|
||||
etapiNoteRoutes.register(router);
|
||||
|
|
|
@ -134,7 +134,7 @@ function executeActions(note, searchResultNoteIds) {
|
|||
for (const resultNoteId of searchResultNoteIds) {
|
||||
const resultNote = becca.getNote(resultNoteId);
|
||||
|
||||
if (!resultNote || resultNote.isDeleted) {
|
||||
if (!resultNote) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,10 @@ const beccaService = require("../becca/becca_service");
|
|||
const log = require("./log");
|
||||
|
||||
function cloneNoteToParentNote(noteId, parentNoteId, prefix) {
|
||||
if (!(noteId in becca.notes) || !(parentNoteId in becca.notes)) {
|
||||
return { success: false, message: 'Note cannot be cloned because either the cloned note or the intended parent is deleted.' };
|
||||
}
|
||||
|
||||
const parentNote = becca.getNote(parentNoteId);
|
||||
|
||||
if (parentNote.type === 'search') {
|
||||
|
@ -18,10 +22,6 @@ function cloneNoteToParentNote(noteId, parentNoteId, prefix) {
|
|||
};
|
||||
}
|
||||
|
||||
if (isNoteDeleted(noteId) || isNoteDeleted(parentNoteId)) {
|
||||
return { success: false, message: 'Note cannot be cloned because either the cloned note or the intended parent is deleted.' };
|
||||
}
|
||||
|
||||
const validationResult = treeService.validateParentChild(parentNoteId, noteId);
|
||||
|
||||
if (!validationResult.success) {
|
||||
|
@ -174,12 +174,6 @@ function cloneNoteAfter(noteId, afterBranchId) {
|
|||
return { success: true, branchId: branch.branchId };
|
||||
}
|
||||
|
||||
function isNoteDeleted(noteId) {
|
||||
const note = becca.getNote(noteId);
|
||||
|
||||
return !note || note.isDeleted;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
cloneNoteToBranch,
|
||||
cloneNoteToParentNote,
|
||||
|
|
|
@ -607,16 +607,16 @@ class ConsistencyChecks {
|
|||
WHERE
|
||||
entity_changes.id IS NULL`,
|
||||
({entityId}) => {
|
||||
const entity = sql.getRow(`SELECT * FROM ${entityName} WHERE ${key} = ?`, [entityId]);
|
||||
const entityRow = sql.getRow(`SELECT * FROM ${entityName} WHERE ${key} = ?`, [entityId]);
|
||||
|
||||
if (this.autoFix) {
|
||||
entityChangesService.addEntityChange({
|
||||
entityName,
|
||||
entityId,
|
||||
hash: utils.randomString(10), // doesn't matter, will force sync, but that's OK
|
||||
isErased: !!entity.isErased,
|
||||
utcDateChanged: entity.utcDateModified || entity.utcDateCreated,
|
||||
isSynced: entityName !== 'options' || entity.isSynced
|
||||
isErased: !!entityRow.isErased,
|
||||
utcDateChanged: entityRow.utcDateModified || entityRow.utcDateCreated,
|
||||
isSynced: entityName !== 'options' || entityRow.isSynced
|
||||
});
|
||||
|
||||
logFix(`Created missing entity change for entityName '${entityName}', entityId '${entityId}'`);
|
||||
|
|
|
@ -570,7 +570,7 @@ function downloadImages(noteId, content) {
|
|||
for (const url in imageUrlToAttachmentIdMapping) {
|
||||
const imageNote = imageNotes.find(note => note.noteId === imageUrlToAttachmentIdMapping[url]);
|
||||
|
||||
if (imageNote && !imageNote.isDeleted) {
|
||||
if (imageNote) {
|
||||
updatedContent = replaceUrl(updatedContent, url, imageNote);
|
||||
}
|
||||
}
|
||||
|
@ -697,14 +697,14 @@ function updateNoteData(noteId, content) {
|
|||
* @param {TaskContext} taskContext
|
||||
*/
|
||||
function undeleteNote(noteId, taskContext) {
|
||||
const note = sql.getRow("SELECT * FROM notes WHERE noteId = ?", [noteId]);
|
||||
const noteRow = sql.getRow("SELECT * FROM notes WHERE noteId = ?", [noteId]);
|
||||
|
||||
if (!note.isDeleted) {
|
||||
if (!noteRow.isDeleted) {
|
||||
log.error(`Note '${noteId}' is not deleted and thus cannot be undeleted.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const undeletedParentBranchIds = getUndeletedParentBranchIds(noteId, note.deleteId);
|
||||
const undeletedParentBranchIds = getUndeletedParentBranchIds(noteId, noteRow.deleteId);
|
||||
|
||||
if (undeletedParentBranchIds.length === 0) {
|
||||
// cannot undelete if there's no undeleted parent
|
||||
|
@ -712,7 +712,7 @@ function undeleteNote(noteId, taskContext) {
|
|||
}
|
||||
|
||||
for (const parentBranchId of undeletedParentBranchIds) {
|
||||
undeleteBranch(parentBranchId, note.deleteId, taskContext);
|
||||
undeleteBranch(parentBranchId, noteRow.deleteId, taskContext);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -722,38 +722,38 @@ function undeleteNote(noteId, taskContext) {
|
|||
* @param {TaskContext} taskContext
|
||||
*/
|
||||
function undeleteBranch(branchId, deleteId, taskContext) {
|
||||
const branch = sql.getRow("SELECT * FROM branches WHERE branchId = ?", [branchId])
|
||||
const branchRow = sql.getRow("SELECT * FROM branches WHERE branchId = ?", [branchId])
|
||||
|
||||
if (!branch.isDeleted) {
|
||||
if (!branchRow.isDeleted) {
|
||||
return;
|
||||
}
|
||||
|
||||
const note = sql.getRow("SELECT * FROM notes WHERE noteId = ?", [branch.noteId]);
|
||||
const noteRow = sql.getRow("SELECT * FROM notes WHERE noteId = ?", [branchRow.noteId]);
|
||||
|
||||
if (note.isDeleted && note.deleteId !== deleteId) {
|
||||
if (noteRow.isDeleted && noteRow.deleteId !== deleteId) {
|
||||
return;
|
||||
}
|
||||
|
||||
new BBranch(branch).save();
|
||||
new BBranch(branchRow).save();
|
||||
|
||||
taskContext.increaseProgressCount();
|
||||
|
||||
if (note.isDeleted && note.deleteId === deleteId) {
|
||||
if (noteRow.isDeleted && noteRow.deleteId === deleteId) {
|
||||
// becca entity was already created as skeleton in "new Branch()" above
|
||||
const noteEntity = becca.getNote(note.noteId);
|
||||
noteEntity.updateFromRow(note);
|
||||
const noteEntity = becca.getNote(noteRow.noteId);
|
||||
noteEntity.updateFromRow(noteRow);
|
||||
noteEntity.save();
|
||||
|
||||
const attributes = sql.getRows(`
|
||||
const attributeRows = sql.getRows(`
|
||||
SELECT * FROM attributes
|
||||
WHERE isDeleted = 1
|
||||
AND deleteId = ?
|
||||
AND (noteId = ?
|
||||
OR (type = 'relation' AND value = ?))`, [deleteId, note.noteId, note.noteId]);
|
||||
OR (type = 'relation' AND value = ?))`, [deleteId, noteRow.noteId, noteRow.noteId]);
|
||||
|
||||
for (const attribute of attributes) {
|
||||
for (const attributeRow of attributeRows) {
|
||||
// relation might point to a note which hasn't been undeleted yet and would thus throw up
|
||||
new BAttribute(attribute).save({skipValidation: true});
|
||||
new BAttribute(attributeRow).save({skipValidation: true});
|
||||
}
|
||||
|
||||
const childBranchIds = sql.getColumn(`
|
||||
|
@ -761,7 +761,7 @@ function undeleteBranch(branchId, deleteId, taskContext) {
|
|||
FROM branches
|
||||
WHERE branches.isDeleted = 1
|
||||
AND branches.deleteId = ?
|
||||
AND branches.parentNoteId = ?`, [deleteId, note.noteId]);
|
||||
AND branches.parentNoteId = ?`, [deleteId, noteRow.noteId]);
|
||||
|
||||
for (const childBranchId of childBranchIds) {
|
||||
undeleteBranch(childBranchId, deleteId, taskContext);
|
||||
|
|
|
@ -155,7 +155,6 @@ function findResultsWithExpression(expression, searchContext) {
|
|||
const noteSet = expression.execute(allNoteSet, executionContext, searchContext);
|
||||
|
||||
const searchResults = noteSet.notes
|
||||
.filter(note => !note.isDeleted)
|
||||
.map(note => {
|
||||
const notePathArray = executionContext.noteIdToNotePath[note.noteId] || note.getBestNotePath();
|
||||
|
||||
|
|
|
@ -315,21 +315,21 @@ function getEntityChangeRow(entityName, entityId) {
|
|||
throw new Error(`Unknown entity '${entityName}'`);
|
||||
}
|
||||
|
||||
const entity = sql.getRow(`SELECT * FROM ${entityName} WHERE ${primaryKey} = ?`, [entityId]);
|
||||
const entityRow = sql.getRow(`SELECT * FROM ${entityName} WHERE ${primaryKey} = ?`, [entityId]);
|
||||
|
||||
if (!entity) {
|
||||
if (!entityRow) {
|
||||
throw new Error(`Entity ${entityName} '${entityId}' not found.`);
|
||||
}
|
||||
|
||||
if (entityName === 'blobs' && entity.content !== null) {
|
||||
if (typeof entity.content === 'string') {
|
||||
entity.content = Buffer.from(entity.content, 'utf-8');
|
||||
if (entityName === 'blobs' && entityRow.content !== null) {
|
||||
if (typeof entityRow.content === 'string') {
|
||||
entityRow.content = Buffer.from(entityRow.content, 'utf-8');
|
||||
}
|
||||
|
||||
entity.content = entity.content.toString("base64");
|
||||
entityRow.content = entityRow.content.toString("base64");
|
||||
}
|
||||
|
||||
return entity;
|
||||
return entityRow;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -199,7 +199,7 @@ function sortNotesIfNeeded(parentNoteId) {
|
|||
function setNoteToParent(noteId, prefix, parentNoteId) {
|
||||
const parentNote = becca.getNote(parentNoteId);
|
||||
|
||||
if (parentNote && parentNote.isDeleted) {
|
||||
if (!parentNote) {
|
||||
throw new Error(`Cannot move note to deleted parent note '${parentNoteId}'`);
|
||||
}
|
||||
|
||||
|
@ -209,7 +209,7 @@ function setNoteToParent(noteId, prefix, parentNoteId) {
|
|||
|
||||
if (branch) {
|
||||
if (!parentNoteId) {
|
||||
log.info(`Removing note ${noteId} from parent ${parentNoteId}`);
|
||||
log.info(`Removing note '${noteId}' from parent '${parentNoteId}'`);
|
||||
|
||||
branch.markAsDeleted();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue