becca_entities_battachment.js.html 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="utf-8">
  5. <title>JSDoc: Source: becca/entities/battachment.js</title>
  6. <script src="scripts/prettify/prettify.js"> </script>
  7. <script src="scripts/prettify/lang-css.js"> </script>
  8. <!--[if lt IE 9]>
  9. <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
  10. <![endif]-->
  11. <link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
  12. <link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
  13. </head>
  14. <body>
  15. <div id="main">
  16. <h1 class="page-title">Source: becca/entities/battachment.js</h1>
  17. <section>
  18. <article>
  19. <pre class="prettyprint source linenums"><code>"use strict";
  20. const utils = require('../../services/utils');
  21. const dateUtils = require('../../services/date_utils');
  22. const AbstractBeccaEntity = require("./abstract_becca_entity");
  23. const sql = require("../../services/sql");
  24. const protectedSessionService = require("../../services/protected_session");
  25. const log = require("../../services/log");
  26. const attachmentRoleToNoteTypeMapping = {
  27. 'image': 'image'
  28. };
  29. /**
  30. * Attachment represent data related/attached to the note. Conceptually similar to attributes, but intended for
  31. * larger amounts of data and generally not accessible to the user.
  32. *
  33. * @extends AbstractBeccaEntity
  34. */
  35. class BAttachment extends AbstractBeccaEntity {
  36. static get entityName() { return "attachments"; }
  37. static get primaryKeyName() { return "attachmentId"; }
  38. static get hashedProperties() { return ["attachmentId", "ownerId", "role", "mime", "title", "blobId", "utcDateScheduledForErasureSince"]; }
  39. constructor(row) {
  40. super();
  41. if (!row.ownerId?.trim()) {
  42. throw new Error("'ownerId' must be given to initialize a Attachment entity");
  43. } else if (!row.role?.trim()) {
  44. throw new Error("'role' must be given to initialize a Attachment entity");
  45. } else if (!row.mime?.trim()) {
  46. throw new Error("'mime' must be given to initialize a Attachment entity");
  47. } else if (!row.title?.trim()) {
  48. throw new Error("'title' must be given to initialize a Attachment entity");
  49. }
  50. /** @type {string} */
  51. this.attachmentId = row.attachmentId;
  52. /**
  53. * either noteId or revisionId to which this attachment belongs
  54. * @type {string}
  55. */
  56. this.ownerId = row.ownerId;
  57. /** @type {string} */
  58. this.role = row.role;
  59. /** @type {string} */
  60. this.mime = row.mime;
  61. /** @type {string} */
  62. this.title = row.title;
  63. /** @type {int} */
  64. this.position = row.position;
  65. /** @type {string} */
  66. this.blobId = row.blobId;
  67. /** @type {boolean} */
  68. this.isProtected = !!row.isProtected;
  69. /** @type {string} */
  70. this.dateModified = row.dateModified;
  71. /** @type {string} */
  72. this.utcDateModified = row.utcDateModified;
  73. /** @type {string} */
  74. this.utcDateScheduledForErasureSince = row.utcDateScheduledForErasureSince;
  75. /**
  76. * optionally added to the entity
  77. * @type {int}
  78. */
  79. this.contentLength = row.contentLength;
  80. this.decrypt();
  81. }
  82. /** @returns {BAttachment} */
  83. copy() {
  84. return new BAttachment({
  85. ownerId: this.ownerId,
  86. role: this.role,
  87. mime: this.mime,
  88. title: this.title,
  89. blobId: this.blobId,
  90. isProtected: this.isProtected
  91. });
  92. }
  93. /** @returns {BNote} */
  94. getNote() {
  95. return this.becca.notes[this.ownerId];
  96. }
  97. /** @returns {boolean} true if the note has string content (not binary) */
  98. hasStringContent() {
  99. return utils.isStringNote(this.type, this.mime);
  100. }
  101. isContentAvailable() {
  102. return !this.attachmentId // new attachment which was not encrypted yet
  103. || !this.isProtected
  104. || protectedSessionService.isProtectedSessionAvailable()
  105. }
  106. getTitleOrProtected() {
  107. return this.isContentAvailable() ? this.title : '[protected]';
  108. }
  109. decrypt() {
  110. if (!this.isProtected || !this.attachmentId) {
  111. this.isDecrypted = true;
  112. return;
  113. }
  114. if (!this.isDecrypted &amp;&amp; protectedSessionService.isProtectedSessionAvailable()) {
  115. try {
  116. this.title = protectedSessionService.decryptString(this.title);
  117. this.isDecrypted = true;
  118. }
  119. catch (e) {
  120. log.error(`Could not decrypt attachment ${this.attachmentId}: ${e.message} ${e.stack}`);
  121. }
  122. }
  123. }
  124. /** @returns {string|Buffer} */
  125. getContent() {
  126. return this._getContent();
  127. }
  128. /**
  129. * @param content
  130. * @param {object} [opts]
  131. * @param {object} [opts.forceSave=false] - will also save this BAttachment entity
  132. * @param {object} [opts.forceFrontendReload=false] - override frontend heuristics on when to reload, instruct to reload
  133. */
  134. setContent(content, opts) {
  135. this._setContent(content, opts);
  136. }
  137. /** @returns {{note: BNote, branch: BBranch}} */
  138. convertToNote() {
  139. if (this.type === 'search') {
  140. throw new Error(`Note of type search cannot have child notes`);
  141. }
  142. if (!this.getNote()) {
  143. throw new Error("Cannot find note of this attachment. It is possible that this is note revision's attachment. " +
  144. "Converting note revision's attachments to note is not (yet) supported.");
  145. }
  146. if (!(this.role in attachmentRoleToNoteTypeMapping)) {
  147. throw new Error(`Mapping from attachment role '${this.role}' to note's type is not defined`);
  148. }
  149. if (!this.isContentAvailable()) { // isProtected is the same for attachment
  150. throw new Error(`Cannot convert protected attachment outside of protected session`);
  151. }
  152. const noteService = require('../../services/notes');
  153. const { note, branch } = noteService.createNewNote({
  154. parentNoteId: this.ownerId,
  155. title: this.title,
  156. type: attachmentRoleToNoteTypeMapping[this.role],
  157. mime: this.mime,
  158. content: this.getContent(),
  159. isProtected: this.isProtected
  160. });
  161. this.markAsDeleted();
  162. const parentNote = this.getNote();
  163. if (this.role === 'image' &amp;&amp; parentNote.type === 'text') {
  164. const origContent = parentNote.getContent();
  165. const oldAttachmentUrl = `api/attachments/${this.attachmentId}/image/`;
  166. const newNoteUrl = `api/images/${note.noteId}/`;
  167. const fixedContent = utils.replaceAll(origContent, oldAttachmentUrl, newNoteUrl);
  168. if (fixedContent !== origContent) {
  169. parentNote.setContent(fixedContent);
  170. }
  171. noteService.asyncPostProcessContent(note, fixedContent);
  172. }
  173. return { note, branch };
  174. }
  175. getFileName() {
  176. const type = this.role === 'image' ? 'image' : 'file';
  177. return utils.formatDownloadTitle(this.title, type, this.mime);
  178. }
  179. beforeSaving() {
  180. super.beforeSaving();
  181. if (this.position === undefined || this.position === null) {
  182. this.position = 10 + sql.getValue(`SELECT COALESCE(MAX(position), 0)
  183. FROM attachments
  184. WHERE ownerId = ?`, [this.noteId]);
  185. }
  186. this.dateModified = dateUtils.localNowDateTime();
  187. this.utcDateModified = dateUtils.utcNowDateTime();
  188. }
  189. getPojo() {
  190. return {
  191. attachmentId: this.attachmentId,
  192. ownerId: this.ownerId,
  193. role: this.role,
  194. mime: this.mime,
  195. title: this.title,
  196. position: this.position,
  197. blobId: this.blobId,
  198. isProtected: !!this.isProtected,
  199. isDeleted: false,
  200. dateModified: this.dateModified,
  201. utcDateModified: this.utcDateModified,
  202. utcDateScheduledForErasureSince: this.utcDateScheduledForErasureSince,
  203. contentLength: this.contentLength
  204. };
  205. }
  206. getPojoToSave() {
  207. const pojo = this.getPojo();
  208. delete pojo.contentLength;
  209. if (pojo.isProtected) {
  210. if (this.isDecrypted) {
  211. pojo.title = protectedSessionService.encrypt(pojo.title);
  212. }
  213. else {
  214. // updating protected note outside of protected session means we will keep original ciphertexts
  215. delete pojo.title;
  216. }
  217. }
  218. return pojo;
  219. }
  220. }
  221. module.exports = BAttachment;
  222. </code></pre>
  223. </article>
  224. </section>
  225. </div>
  226. <nav>
  227. <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>
  228. </nav>
  229. <br class="clear">
  230. <footer>
  231. Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.2</a>
  232. </footer>
  233. <script> prettyPrint(); </script>
  234. <script src="scripts/linenumber.js"> </script>
  235. </body>
  236. </html>