2017-10-21 21:10:33 -04:00
|
|
|
"use strict";
|
|
|
|
|
2024-07-18 21:35:17 +03:00
|
|
|
import beccaService from "../../becca/becca_service.js";
|
|
|
|
import utils from "../../services/utils.js";
|
|
|
|
import sql from "../../services/sql.js";
|
|
|
|
import cls from "../../services/cls.js";
|
2024-07-18 21:37:45 +03:00
|
|
|
import path from "path";
|
2024-07-18 21:35:17 +03:00
|
|
|
import becca from "../../becca/becca.js";
|
|
|
|
import blobService from "../../services/blob.js";
|
|
|
|
import eraseService from "../../services/erase.js";
|
2025-01-09 18:36:24 +02:00
|
|
|
import type { Request, Response } from "express";
|
2025-01-13 23:18:10 +02:00
|
|
|
import type BRevision from "../../becca/entities/brevision.js";
|
|
|
|
import type BNote from "../../becca/entities/bnote.js";
|
|
|
|
import type { NotePojo } from "../../becca/becca-interface.js";
|
2024-04-06 22:32:03 +03:00
|
|
|
|
|
|
|
interface NotePath {
|
|
|
|
noteId: string;
|
|
|
|
branchId?: string;
|
|
|
|
title: string;
|
|
|
|
notePath: string[];
|
|
|
|
path: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
interface NotePojoWithNotePath extends NotePojo {
|
|
|
|
notePath?: string[] | null;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getRevisionBlob(req: Request) {
|
2025-01-09 18:07:02 +02:00
|
|
|
const preview = req.query.preview === "true";
|
2023-05-05 16:37:39 +02:00
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
return blobService.getBlobPojo("revisions", req.params.revisionId, { preview });
|
2023-05-05 16:37:39 +02:00
|
|
|
}
|
2017-10-14 23:31:44 -04:00
|
|
|
|
2024-04-06 22:32:03 +03:00
|
|
|
function getRevisions(req: Request) {
|
2025-01-09 18:07:02 +02:00
|
|
|
return becca.getRevisionsFromQuery(
|
|
|
|
`
|
2023-06-04 23:01:40 +02:00
|
|
|
SELECT revisions.*,
|
2024-12-22 15:42:15 +02:00
|
|
|
LENGTH(blobs.content) AS contentLength
|
2023-06-04 23:01:40 +02:00
|
|
|
FROM revisions
|
2024-12-22 15:42:15 +02:00
|
|
|
JOIN blobs ON revisions.blobId = blobs.blobId
|
2023-08-09 23:12:31 +02:00
|
|
|
WHERE revisions.noteId = ?
|
2025-01-09 18:07:02 +02:00
|
|
|
ORDER BY revisions.utcDateCreated DESC`,
|
|
|
|
[req.params.noteId]
|
|
|
|
);
|
2019-09-08 09:17:16 +02:00
|
|
|
}
|
|
|
|
|
2024-04-06 22:32:03 +03:00
|
|
|
function getRevision(req: Request) {
|
|
|
|
const revision = becca.getRevisionOrThrow(req.params.revisionId);
|
2019-09-08 09:17:16 +02:00
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
if (revision.type === "file") {
|
2023-06-04 23:01:40 +02:00
|
|
|
if (revision.hasStringContent()) {
|
2024-04-06 22:32:03 +03:00
|
|
|
revision.content = (revision.getContent() as string).substr(0, 10000);
|
2019-11-09 13:01:05 +01:00
|
|
|
}
|
2025-01-09 18:07:02 +02:00
|
|
|
} else {
|
2023-06-04 23:01:40 +02:00
|
|
|
revision.content = revision.getContent();
|
2019-11-01 20:00:56 +01:00
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
if (revision.content && revision.type === "image") {
|
|
|
|
revision.content = revision.content.toString("base64");
|
2019-11-09 08:53:13 +01:00
|
|
|
}
|
2019-11-08 23:09:57 +01:00
|
|
|
}
|
|
|
|
|
2023-06-04 23:01:40 +02:00
|
|
|
return revision;
|
2018-03-30 13:56:46 -04:00
|
|
|
}
|
2017-10-14 23:31:44 -04:00
|
|
|
|
2024-04-06 22:32:03 +03:00
|
|
|
function getRevisionFilename(revision: BRevision) {
|
2023-06-04 23:01:40 +02:00
|
|
|
let filename = utils.formatDownloadTitle(revision.title, revision.type, revision.mime);
|
2019-11-09 08:53:13 +01:00
|
|
|
|
2024-07-24 20:39:50 +03:00
|
|
|
if (!revision.dateCreated) {
|
|
|
|
throw new Error("Missing creation date for revision.");
|
|
|
|
}
|
|
|
|
|
2019-11-09 08:53:13 +01:00
|
|
|
const extension = path.extname(filename);
|
2023-06-04 23:01:40 +02:00
|
|
|
const date = revision.dateCreated
|
2019-11-09 08:53:13 +01:00
|
|
|
.substr(0, 19)
|
2025-01-09 18:07:02 +02:00
|
|
|
.replace(" ", "_")
|
|
|
|
.replace(/[^0-9_]/g, "");
|
2019-11-09 08:53:13 +01:00
|
|
|
|
|
|
|
if (extension) {
|
2022-12-21 15:19:05 +01:00
|
|
|
filename = `${filename.substr(0, filename.length - extension.length)}-${date}${extension}`;
|
2025-01-09 18:07:02 +02:00
|
|
|
} else {
|
2022-12-21 15:19:05 +01:00
|
|
|
filename += `-${date}`;
|
2019-11-09 08:53:13 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return filename;
|
|
|
|
}
|
|
|
|
|
2024-04-06 22:32:03 +03:00
|
|
|
function downloadRevision(req: Request, res: Response) {
|
|
|
|
const revision = becca.getRevisionOrThrow(req.params.revisionId);
|
2019-11-09 08:53:13 +01:00
|
|
|
|
2023-06-04 23:01:40 +02:00
|
|
|
if (!revision.isContentAvailable()) {
|
2025-01-09 18:07:02 +02:00
|
|
|
return res.setHeader("Content-Type", "text/plain").status(401).send("Protected session not available");
|
2019-11-09 08:53:13 +01:00
|
|
|
}
|
|
|
|
|
2023-06-04 23:01:40 +02:00
|
|
|
const filename = getRevisionFilename(revision);
|
2019-11-09 08:53:13 +01:00
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
res.setHeader("Content-Disposition", utils.getContentDisposition(filename));
|
|
|
|
res.setHeader("Content-Type", revision.mime);
|
2019-11-09 08:53:13 +01:00
|
|
|
|
2023-06-04 23:01:40 +02:00
|
|
|
res.send(revision.getContent());
|
2019-11-09 08:53:13 +01:00
|
|
|
}
|
|
|
|
|
2024-04-06 22:32:03 +03:00
|
|
|
function eraseAllRevisions(req: Request) {
|
2025-01-09 18:07:02 +02:00
|
|
|
const revisionIdsToErase = sql.getColumn<string>("SELECT revisionId FROM revisions WHERE noteId = ?", [req.params.noteId]);
|
2019-11-09 15:21:14 +01:00
|
|
|
|
2023-12-04 00:11:24 +01:00
|
|
|
eraseService.eraseRevisions(revisionIdsToErase);
|
2019-11-09 15:21:14 +01:00
|
|
|
}
|
|
|
|
|
2024-04-06 22:32:03 +03:00
|
|
|
function eraseRevision(req: Request) {
|
2023-12-04 00:11:24 +01:00
|
|
|
eraseService.eraseRevisions([req.params.revisionId]);
|
2019-11-09 15:21:14 +01:00
|
|
|
}
|
|
|
|
|
2024-09-04 08:41:17 +00:00
|
|
|
function eraseAllExcessRevisions() {
|
2025-03-05 18:09:44 +01:00
|
|
|
const allNoteIds = sql.getRows("SELECT noteId FROM notes WHERE SUBSTRING(noteId, 1, 1) != '_'") as { noteId: string }[];
|
2025-01-09 18:07:02 +02:00
|
|
|
allNoteIds.forEach((row) => {
|
|
|
|
becca.getNote(row.noteId)?.eraseExcessRevisionSnapshots();
|
2024-09-04 08:41:17 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-04-06 22:32:03 +03:00
|
|
|
function restoreRevision(req: Request) {
|
2023-06-04 23:01:40 +02:00
|
|
|
const revision = becca.getRevision(req.params.revisionId);
|
2020-05-07 23:34:13 +02:00
|
|
|
|
2023-06-04 23:01:40 +02:00
|
|
|
if (revision) {
|
|
|
|
const note = revision.getNote();
|
2020-05-07 23:34:13 +02:00
|
|
|
|
2023-04-19 22:47:33 +02:00
|
|
|
sql.transactional(() => {
|
2023-06-04 23:01:40 +02:00
|
|
|
note.saveRevision();
|
2020-05-07 23:34:13 +02:00
|
|
|
|
2023-04-19 22:47:33 +02:00
|
|
|
for (const oldNoteAttachment of note.getAttachments()) {
|
|
|
|
oldNoteAttachment.markAsDeleted();
|
|
|
|
}
|
|
|
|
|
2023-06-04 23:01:40 +02:00
|
|
|
let revisionContent = revision.getContent();
|
2023-04-19 22:47:33 +02:00
|
|
|
|
2023-06-04 23:01:40 +02:00
|
|
|
for (const revisionAttachment of revision.getAttachments()) {
|
2023-04-19 22:47:33 +02:00
|
|
|
const noteAttachment = revisionAttachment.copy();
|
2023-07-14 17:01:56 +02:00
|
|
|
noteAttachment.ownerId = note.noteId;
|
2023-04-19 22:47:33 +02:00
|
|
|
noteAttachment.setContent(revisionAttachment.getContent(), { forceSave: true });
|
|
|
|
|
|
|
|
// content is rewritten to point to the restored revision attachments
|
2024-04-06 22:32:03 +03:00
|
|
|
if (typeof revisionContent === "string") {
|
|
|
|
revisionContent = revisionContent.replaceAll(`attachments/${revisionAttachment.attachmentId}`, `attachments/${noteAttachment.attachmentId}`);
|
|
|
|
}
|
2023-04-19 22:47:33 +02:00
|
|
|
}
|
|
|
|
|
2023-06-04 23:01:40 +02:00
|
|
|
note.title = revision.title;
|
2024-09-15 12:48:01 +08:00
|
|
|
note.mime = revision.mime;
|
2025-03-08 16:12:37 +01:00
|
|
|
note.type = revision.type;
|
2023-04-19 22:47:33 +02:00
|
|
|
note.setContent(revisionContent, { forceSave: true });
|
|
|
|
});
|
2020-05-07 23:34:13 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-06 22:32:03 +03:00
|
|
|
function getEditedNotesOnDate(req: Request) {
|
2025-01-09 18:07:02 +02:00
|
|
|
const noteIds = sql.getColumn<string>(
|
|
|
|
`
|
2019-12-01 14:30:59 +01:00
|
|
|
SELECT notes.*
|
|
|
|
FROM notes
|
|
|
|
WHERE noteId IN (
|
2024-12-22 15:42:15 +02:00
|
|
|
SELECT noteId FROM notes
|
2020-06-20 23:24:34 +02:00
|
|
|
WHERE notes.dateCreated LIKE :date
|
2024-12-22 15:42:15 +02:00
|
|
|
OR notes.dateModified LIKE :date
|
2019-12-01 14:30:59 +01:00
|
|
|
UNION ALL
|
2023-06-04 23:01:40 +02:00
|
|
|
SELECT noteId FROM revisions
|
|
|
|
WHERE revisions.dateLastEdited LIKE :date
|
2019-12-01 14:30:59 +01:00
|
|
|
)
|
2019-12-02 22:27:06 +01:00
|
|
|
ORDER BY isDeleted
|
2025-01-09 18:07:02 +02:00
|
|
|
LIMIT 50`,
|
|
|
|
{ date: `${req.params.date}%` }
|
|
|
|
);
|
2019-09-07 10:11:59 +02:00
|
|
|
|
2022-11-03 20:28:56 +02:00
|
|
|
let notes = becca.getNotes(noteIds, true);
|
|
|
|
|
|
|
|
// Narrow down the results if a note is hoisted, similar to "Jump to note".
|
|
|
|
const hoistedNoteId = cls.getHoistedNoteId();
|
2025-01-09 18:07:02 +02:00
|
|
|
if (hoistedNoteId !== "root") {
|
|
|
|
notes = notes.filter((note) => note.hasAncestor(hoistedNoteId));
|
2022-11-03 20:28:56 +02:00
|
|
|
}
|
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
return notes.map((note) => {
|
2023-06-05 09:23:42 +02:00
|
|
|
const notePath = getNotePathData(note);
|
2019-09-07 10:11:59 +02:00
|
|
|
|
2024-04-06 22:32:03 +03:00
|
|
|
const notePojo: NotePojoWithNotePath = note.getPojo();
|
2023-05-26 14:54:13 +08:00
|
|
|
notePojo.notePath = notePath ? notePath.notePath : null;
|
2023-05-26 14:36:21 +08:00
|
|
|
|
2023-05-26 14:54:13 +08:00
|
|
|
return notePojo;
|
|
|
|
});
|
2019-09-07 10:11:59 +02:00
|
|
|
}
|
|
|
|
|
2024-04-06 22:32:03 +03:00
|
|
|
function getNotePathData(note: BNote): NotePath | undefined {
|
2023-04-16 11:28:24 +02:00
|
|
|
const retPath = note.getBestNotePath();
|
|
|
|
|
|
|
|
if (retPath) {
|
|
|
|
const noteTitle = beccaService.getNoteTitleForPath(retPath);
|
|
|
|
|
|
|
|
let branchId;
|
|
|
|
|
|
|
|
if (note.isRoot()) {
|
2025-01-09 18:07:02 +02:00
|
|
|
branchId = "none_root";
|
|
|
|
} else {
|
2023-04-16 11:28:24 +02:00
|
|
|
const parentNote = note.parents[0];
|
2024-04-06 22:32:03 +03:00
|
|
|
branchId = becca.getBranchFromChildAndParent(note.noteId, parentNote.noteId)?.branchId;
|
2023-04-16 11:28:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
noteId: note.noteId,
|
|
|
|
branchId: branchId,
|
|
|
|
title: noteTitle,
|
|
|
|
notePath: retPath,
|
2025-01-09 18:07:02 +02:00
|
|
|
path: retPath.join("/")
|
2023-04-16 11:28:24 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-18 21:47:30 +03:00
|
|
|
export default {
|
2023-06-04 23:01:40 +02:00
|
|
|
getRevisionBlob,
|
|
|
|
getRevisions,
|
|
|
|
getRevision,
|
|
|
|
downloadRevision,
|
2019-11-09 15:21:14 +01:00
|
|
|
getEditedNotesOnDate,
|
2023-06-04 23:01:40 +02:00
|
|
|
eraseAllRevisions,
|
2024-09-04 08:41:17 +00:00
|
|
|
eraseAllExcessRevisions,
|
2023-06-04 23:01:40 +02:00
|
|
|
eraseRevision,
|
|
|
|
restoreRevision
|
2020-05-12 10:28:31 +02:00
|
|
|
};
|