<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>JSDoc: Source: becca/entities/battachment.js</title> <script src="scripts/prettify/prettify.js"> </script> <script src="scripts/prettify/lang-css.js"> </script> <!--[if lt IE 9]> <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script> <![endif]--> <link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css"> <link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css"> </head> <body> <div id="main"> <h1 class="page-title">Source: becca/entities/battachment.js</h1> <section> <article> <pre class="prettyprint source linenums"><code>"use strict"; const utils = require('../../services/utils'); const dateUtils = require('../../services/date_utils'); const AbstractBeccaEntity = require("./abstract_becca_entity"); const sql = require("../../services/sql"); const protectedSessionService = require("../../services/protected_session"); const log = require("../../services/log"); const attachmentRoleToNoteTypeMapping = { 'image': 'image' }; /** * Attachment represent data related/attached to the note. Conceptually similar to attributes, but intended for * larger amounts of data and generally not accessible to the user. * * @extends AbstractBeccaEntity */ class BAttachment extends AbstractBeccaEntity { static get entityName() { return "attachments"; } static get primaryKeyName() { return "attachmentId"; } static get hashedProperties() { return ["attachmentId", "ownerId", "role", "mime", "title", "blobId", "utcDateScheduledForErasureSince"]; } constructor(row) { super(); if (!row.ownerId?.trim()) { throw new Error("'ownerId' must be given to initialize a Attachment entity"); } else if (!row.role?.trim()) { throw new Error("'role' must be given to initialize a Attachment entity"); } else if (!row.mime?.trim()) { throw new Error("'mime' must be given to initialize a Attachment entity"); } else if (!row.title?.trim()) { throw new Error("'title' must be given to initialize a Attachment entity"); } /** @type {string} */ this.attachmentId = row.attachmentId; /** * either noteId or revisionId to which this attachment belongs * @type {string} */ this.ownerId = row.ownerId; /** @type {string} */ this.role = row.role; /** @type {string} */ this.mime = row.mime; /** @type {string} */ this.title = row.title; /** @type {int} */ this.position = row.position; /** @type {string} */ this.blobId = row.blobId; /** @type {boolean} */ this.isProtected = !!row.isProtected; /** @type {string} */ this.dateModified = row.dateModified; /** @type {string} */ this.utcDateModified = row.utcDateModified; /** @type {string} */ this.utcDateScheduledForErasureSince = row.utcDateScheduledForErasureSince; /** * optionally added to the entity * @type {int} */ this.contentLength = row.contentLength; this.decrypt(); } /** @returns {BAttachment} */ copy() { return new BAttachment({ ownerId: this.ownerId, role: this.role, mime: this.mime, title: this.title, blobId: this.blobId, isProtected: this.isProtected }); } /** @returns {BNote} */ getNote() { return this.becca.notes[this.ownerId]; } /** @returns {boolean} true if the note has string content (not binary) */ hasStringContent() { return utils.isStringNote(this.type, this.mime); } isContentAvailable() { return !this.attachmentId // new attachment which was not encrypted yet || !this.isProtected || protectedSessionService.isProtectedSessionAvailable() } getTitleOrProtected() { return this.isContentAvailable() ? this.title : '[protected]'; } decrypt() { if (!this.isProtected || !this.attachmentId) { this.isDecrypted = true; return; } if (!this.isDecrypted && protectedSessionService.isProtectedSessionAvailable()) { try { this.title = protectedSessionService.decryptString(this.title); this.isDecrypted = true; } catch (e) { log.error(`Could not decrypt attachment ${this.attachmentId}: ${e.message} ${e.stack}`); } } } /** @returns {string|Buffer} */ getContent() { return this._getContent(); } /** * @param content * @param {object} [opts] * @param {object} [opts.forceSave=false] - will also save this BAttachment entity * @param {object} [opts.forceFrontendReload=false] - override frontend heuristics on when to reload, instruct to reload */ setContent(content, opts) { this._setContent(content, opts); } /** @returns {{note: BNote, branch: BBranch}} */ convertToNote() { if (this.type === 'search') { throw new Error(`Note of type search cannot have child notes`); } if (!this.getNote()) { throw new Error("Cannot find note of this attachment. It is possible that this is note revision's attachment. " + "Converting note revision's attachments to note is not (yet) supported."); } if (!(this.role in attachmentRoleToNoteTypeMapping)) { throw new Error(`Mapping from attachment role '${this.role}' to note's type is not defined`); } if (!this.isContentAvailable()) { // isProtected is the same for attachment throw new Error(`Cannot convert protected attachment outside of protected session`); } const noteService = require('../../services/notes'); const { note, branch } = noteService.createNewNote({ parentNoteId: this.ownerId, title: this.title, type: attachmentRoleToNoteTypeMapping[this.role], mime: this.mime, content: this.getContent(), isProtected: this.isProtected }); this.markAsDeleted(); const parentNote = this.getNote(); if (this.role === 'image' && parentNote.type === 'text') { const origContent = parentNote.getContent(); const oldAttachmentUrl = `api/attachments/${this.attachmentId}/image/`; const newNoteUrl = `api/images/${note.noteId}/`; const fixedContent = utils.replaceAll(origContent, oldAttachmentUrl, newNoteUrl); if (fixedContent !== origContent) { parentNote.setContent(fixedContent); } noteService.asyncPostProcessContent(note, fixedContent); } return { note, branch }; } getFileName() { const type = this.role === 'image' ? 'image' : 'file'; return utils.formatDownloadTitle(this.title, type, this.mime); } beforeSaving() { super.beforeSaving(); if (this.position === undefined || this.position === null) { this.position = 10 + sql.getValue(`SELECT COALESCE(MAX(position), 0) FROM attachments WHERE ownerId = ?`, [this.noteId]); } this.dateModified = dateUtils.localNowDateTime(); this.utcDateModified = dateUtils.utcNowDateTime(); } getPojo() { return { attachmentId: this.attachmentId, ownerId: this.ownerId, role: this.role, mime: this.mime, title: this.title, position: this.position, blobId: this.blobId, isProtected: !!this.isProtected, isDeleted: false, dateModified: this.dateModified, utcDateModified: this.utcDateModified, utcDateScheduledForErasureSince: this.utcDateScheduledForErasureSince, contentLength: this.contentLength }; } getPojoToSave() { const pojo = this.getPojo(); delete pojo.contentLength; if (pojo.isProtected) { if (this.isDecrypted) { pojo.title = protectedSessionService.encrypt(pojo.title); } else { // updating protected note outside of protected session means we will keep original ciphertexts delete pojo.title; } } return pojo; } } module.exports = BAttachment; </code></pre> </article> </section> </div> <nav> <h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-sql.html">sql</a></li></ul><h3>Classes</h3><ul><li><a href="AbstractBeccaEntity.html">AbstractBeccaEntity</a></li><li><a href="BAttachment.html">BAttachment</a></li><li><a href="BAttribute.html">BAttribute</a></li><li><a href="BBranch.html">BBranch</a></li><li><a href="BEtapiToken.html">BEtapiToken</a></li><li><a href="BNote.html">BNote</a></li><li><a href="BOption.html">BOption</a></li><li><a href="BRecentNote.html">BRecentNote</a></li><li><a href="BRevision.html">BRevision</a></li><li><a href="BackendScriptApi.html">BackendScriptApi</a></li></ul><h3>Global</h3><ul><li><a href="global.html#api">api</a></li></ul> </nav> <br class="clear"> <footer> Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.2</a> </footer> <script> prettyPrint(); </script> <script src="scripts/linenumber.js"> </script> </body> </html>