2024-07-18 21:37:45 +03:00
|
|
|
import safeCompare from "safe-compare";
|
|
|
|
import ejs from "ejs";
|
2024-04-10 19:04:38 +03:00
|
|
|
|
2024-04-17 22:54:05 +03:00
|
|
|
import type { Request, Response, Router } from "express";
|
|
|
|
|
2024-07-18 21:35:17 +03:00
|
|
|
import shaca from "./shaca/shaca.js";
|
|
|
|
import shacaLoader from "./shaca/shaca_loader.js";
|
|
|
|
import shareRoot from "./share_root.js";
|
|
|
|
import contentRenderer from "./content_renderer.js";
|
|
|
|
import assetPath from "../services/asset_path.js";
|
|
|
|
import appPath from "../services/app_path.js";
|
|
|
|
import searchService from "../services/search/services/search.js";
|
|
|
|
import SearchContext from "../services/search/search_context.js";
|
|
|
|
import log from "../services/log.js";
|
2025-01-13 23:18:10 +02:00
|
|
|
import type SNote from "./shaca/entities/snote.js";
|
|
|
|
import type SBranch from "./shaca/entities/sbranch.js";
|
|
|
|
import type SAttachment from "./shaca/entities/sattachment.js";
|
2025-03-10 07:15:43 +01:00
|
|
|
import utils, { safeExtractMessageAndStackFromError } from "../services/utils.js";
|
2025-02-17 13:19:55 -07:00
|
|
|
import options from "../services/options.js";
|
2024-04-10 19:04:38 +03:00
|
|
|
|
|
|
|
function getSharedSubTreeRoot(note: SNote): { note?: SNote; branch?: SBranch } {
|
2021-10-19 22:48:38 +02:00
|
|
|
if (note.noteId === shareRoot.SHARE_ROOT_NOTE_ID) {
|
2021-12-23 20:54:48 +01:00
|
|
|
// share root itself is not shared
|
2023-07-17 22:19:03 +02:00
|
|
|
return {};
|
2021-10-19 22:48:38 +02:00
|
|
|
}
|
|
|
|
|
2021-12-23 20:54:48 +01:00
|
|
|
// every path leads to share root, but which one to choose?
|
2023-06-30 11:18:34 +02:00
|
|
|
// for the sake of simplicity, URLs are not note paths
|
2023-07-17 22:19:03 +02:00
|
|
|
const parentBranch = note.getParentBranches()[0];
|
2021-10-19 22:48:38 +02:00
|
|
|
|
2023-07-17 22:19:03 +02:00
|
|
|
if (parentBranch.parentNoteId === shareRoot.SHARE_ROOT_NOTE_ID) {
|
|
|
|
return {
|
|
|
|
note,
|
|
|
|
branch: parentBranch
|
|
|
|
};
|
2021-10-19 22:48:38 +02:00
|
|
|
}
|
|
|
|
|
2023-07-17 22:19:03 +02:00
|
|
|
return getSharedSubTreeRoot(parentBranch.getParentNote());
|
2021-10-19 22:48:38 +02:00
|
|
|
}
|
2021-10-17 14:44:59 +02:00
|
|
|
|
2024-04-17 22:54:05 +03:00
|
|
|
function addNoIndexHeader(note: SNote, res: Response) {
|
2025-01-09 18:07:02 +02:00
|
|
|
if (note.isLabelTruthy("shareDisallowRobotIndexing")) {
|
|
|
|
res.setHeader("X-Robots-Tag", "noindex");
|
2022-03-22 23:17:47 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-17 22:54:05 +03:00
|
|
|
function requestCredentials(res: Response) {
|
2025-01-09 18:07:02 +02:00
|
|
|
res.setHeader("WWW-Authenticate", 'Basic realm="User Visible Realm", charset="UTF-8"').sendStatus(401);
|
2022-07-31 21:45:32 +02:00
|
|
|
}
|
|
|
|
|
2024-04-17 22:54:05 +03:00
|
|
|
function checkAttachmentAccess(attachmentId: string, req: Request, res: Response) {
|
2023-06-05 23:05:05 +02:00
|
|
|
const attachment = shaca.getAttachment(attachmentId);
|
|
|
|
|
|
|
|
if (!attachment) {
|
2025-01-09 18:07:02 +02:00
|
|
|
res.status(404).json({ message: `Attachment '${attachmentId}' not found.` });
|
2023-06-05 23:05:05 +02:00
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2023-07-14 17:01:56 +02:00
|
|
|
const note = checkNoteAccess(attachment.ownerId, req, res);
|
2023-06-05 23:05:05 +02:00
|
|
|
|
2023-06-30 11:18:34 +02:00
|
|
|
// truthy note means the user has access, and we can return the attachment
|
2023-06-05 23:05:05 +02:00
|
|
|
return note ? attachment : false;
|
|
|
|
}
|
|
|
|
|
2024-04-17 22:54:05 +03:00
|
|
|
function checkNoteAccess(noteId: string, req: Request, res: Response) {
|
2022-07-31 21:45:32 +02:00
|
|
|
const note = shaca.getNote(noteId);
|
|
|
|
|
|
|
|
if (!note) {
|
2025-01-09 18:07:02 +02:00
|
|
|
res.status(404).json({ message: `Note '${noteId}' not found.` });
|
2022-12-19 21:39:12 +01:00
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
if (noteId === "_share" && !shaca.shareIndexEnabled) {
|
|
|
|
res.status(403).json({ message: `Accessing share index is forbidden.` });
|
2022-07-31 21:45:32 +02:00
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const credentials = note.getCredentials();
|
|
|
|
|
|
|
|
if (credentials.length === 0) {
|
|
|
|
return note;
|
|
|
|
}
|
|
|
|
|
|
|
|
const header = req.header("Authorization");
|
|
|
|
|
|
|
|
if (!header?.startsWith("Basic ")) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const base64Str = header.substring("Basic ".length);
|
2025-01-09 18:07:02 +02:00
|
|
|
const buffer = Buffer.from(base64Str, "base64");
|
|
|
|
const authString = buffer.toString("utf-8");
|
2022-07-31 21:45:32 +02:00
|
|
|
|
|
|
|
for (const credentialLabel of credentials) {
|
|
|
|
if (safeCompare(authString, credentialLabel.value)) {
|
|
|
|
return note; // success;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-04-17 22:54:05 +03:00
|
|
|
function renderImageAttachment(image: SNote, res: Response, attachmentName: string) {
|
2025-01-09 18:07:02 +02:00
|
|
|
let svgString = "<svg/>";
|
2023-10-21 17:32:07 +02:00
|
|
|
const attachment = image.getAttachmentByTitle(attachmentName);
|
2024-04-10 19:04:38 +03:00
|
|
|
if (!attachment) {
|
|
|
|
res.status(404).render("share/404");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const content = attachment.getContent();
|
|
|
|
if (typeof content === "string") {
|
|
|
|
svgString = content;
|
2023-10-21 17:32:07 +02:00
|
|
|
} else {
|
|
|
|
// backwards compatibility, before attachments, the SVG was stored in the main note content as a separate key
|
|
|
|
const contentSvg = image.getJsonContentSafely()?.svg;
|
|
|
|
|
|
|
|
if (contentSvg) {
|
|
|
|
svgString = contentSvg;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
const svg = svgString;
|
|
|
|
res.set("Content-Type", "image/svg+xml");
|
2023-10-21 17:32:07 +02:00
|
|
|
res.set("Cache-Control", "no-cache, no-store, must-revalidate");
|
|
|
|
res.send(svg);
|
|
|
|
}
|
|
|
|
|
2024-04-17 22:54:05 +03:00
|
|
|
function register(router: Router) {
|
|
|
|
function renderNote(note: SNote, req: Request, res: Response) {
|
2022-03-22 23:17:47 +01:00
|
|
|
if (!note) {
|
2021-12-22 09:10:38 +01:00
|
|
|
res.status(404).render("share/404");
|
2022-03-22 23:17:47 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-07-31 21:45:32 +02:00
|
|
|
if (!checkNoteAccess(note.noteId, req, res)) {
|
2022-08-01 19:56:09 +02:00
|
|
|
requestCredentials(res);
|
|
|
|
|
2022-07-31 21:45:32 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-03-22 23:17:47 +01:00
|
|
|
addNoIndexHeader(note, res);
|
|
|
|
|
2025-02-13 21:28:01 +02:00
|
|
|
if (note.isLabelTruthy("shareRaw") || typeof req.query.raw !== "undefined") {
|
2025-01-09 18:07:02 +02:00
|
|
|
res.setHeader("Content-Type", note.mime).send(note.getContent());
|
2022-03-22 23:17:47 +01:00
|
|
|
|
|
|
|
return;
|
2021-10-17 14:44:59 +02:00
|
|
|
}
|
2022-03-22 23:17:47 +01:00
|
|
|
|
2024-04-09 21:51:23 +03:00
|
|
|
const { header, content, isEmpty } = contentRenderer.getContent(note);
|
2022-03-22 23:17:47 +01:00
|
|
|
const subRoot = getSharedSubTreeRoot(note);
|
2025-02-17 13:19:55 -07:00
|
|
|
const showLoginInShareTheme = options.getOption("showLoginInShareTheme");
|
2025-02-16 21:29:38 -07:00
|
|
|
const opts = { note, header, content, isEmpty, subRoot, assetPath, appPath, showLoginInShareTheme };
|
2023-09-27 14:34:07 -04:00
|
|
|
let useDefaultView = true;
|
|
|
|
|
|
|
|
// Check if the user has their own template
|
2025-01-09 18:07:02 +02:00
|
|
|
if (note.hasRelation("shareTemplate")) {
|
2023-09-27 14:34:07 -04:00
|
|
|
// Get the template note and content
|
2025-01-09 18:07:02 +02:00
|
|
|
const templateId = note.getRelation("shareTemplate")?.value;
|
2024-04-10 19:04:38 +03:00
|
|
|
const templateNote = templateId && shaca.getNote(templateId);
|
2023-09-27 14:34:07 -04:00
|
|
|
|
|
|
|
// Make sure the note type is correct
|
2025-01-09 18:07:02 +02:00
|
|
|
if (templateNote && templateNote.type === "code" && templateNote.mime === "application/x-ejs") {
|
2023-09-27 14:34:07 -04:00
|
|
|
// EJS caches the result of this so we don't need to pre-cache
|
2024-04-10 19:04:38 +03:00
|
|
|
const includer = (path: string) => {
|
2025-01-09 18:07:02 +02:00
|
|
|
const childNote = templateNote.children.find((n) => path === n.title);
|
2024-04-10 19:04:38 +03:00
|
|
|
if (!childNote) throw new Error("Unable to find child note.");
|
2025-01-09 18:07:02 +02:00
|
|
|
if (childNote.type !== "code" || childNote.mime !== "application/x-ejs") throw new Error("Incorrect child note type.");
|
2024-04-10 19:04:38 +03:00
|
|
|
|
|
|
|
const template = childNote.getContent();
|
|
|
|
if (typeof template !== "string") throw new Error("Invalid template content type.");
|
|
|
|
|
|
|
|
return { template };
|
2023-09-27 14:34:07 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
// Try to render user's template, w/ fallback to default view
|
|
|
|
try {
|
2024-04-10 19:04:38 +03:00
|
|
|
const content = templateNote.getContent();
|
|
|
|
if (typeof content === "string") {
|
|
|
|
const ejsResult = ejs.render(content, opts, { includer });
|
|
|
|
res.send(ejsResult);
|
|
|
|
useDefaultView = false; // Rendering went okay, don't use default view
|
|
|
|
}
|
2025-03-10 07:15:43 +01:00
|
|
|
} catch (e: unknown) {
|
|
|
|
const [errMessage, errStack] = safeExtractMessageAndStackFromError(e);
|
|
|
|
log.error(`Rendering user provided share template (${templateId}) threw exception ${errMessage} with stacktrace: ${errStack}`);
|
2023-09-27 14:34:07 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-03-22 23:17:47 +01:00
|
|
|
|
2023-09-27 14:34:07 -04:00
|
|
|
if (useDefaultView) {
|
2025-01-09 18:07:02 +02:00
|
|
|
res.render("share/page", opts);
|
2023-09-27 14:34:07 -04:00
|
|
|
}
|
2022-01-17 23:13:56 +01:00
|
|
|
}
|
|
|
|
|
2025-03-10 07:33:20 +01:00
|
|
|
router.get("/share/", (req, res, _next) => {
|
2025-01-09 18:07:02 +02:00
|
|
|
if (req.path.substr(-1) !== "/") {
|
|
|
|
res.redirect("../share/");
|
2022-10-30 09:05:12 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-01-17 23:13:56 +01:00
|
|
|
shacaLoader.ensureLoad();
|
|
|
|
|
2024-04-10 19:04:38 +03:00
|
|
|
if (!shaca.shareRootNote) {
|
2025-01-09 18:07:02 +02:00
|
|
|
res.status(404).json({ message: "Share root note not found" });
|
2024-12-10 22:35:23 +02:00
|
|
|
return;
|
2024-04-10 19:04:38 +03:00
|
|
|
}
|
|
|
|
|
2022-07-31 21:45:32 +02:00
|
|
|
renderNote(shaca.shareRootNote, req, res);
|
2022-01-17 23:13:56 +01:00
|
|
|
});
|
|
|
|
|
2025-03-10 07:33:20 +01:00
|
|
|
router.get("/share/:shareId", (req, res, _next) => {
|
2022-01-17 23:13:56 +01:00
|
|
|
shacaLoader.ensureLoad();
|
|
|
|
|
2024-04-09 21:51:23 +03:00
|
|
|
const { shareId } = req.params;
|
2022-05-01 23:16:47 +02:00
|
|
|
|
2022-01-17 23:13:56 +01:00
|
|
|
const note = shaca.aliasToNote[shareId] || shaca.notes[shareId];
|
|
|
|
|
2022-07-31 21:45:32 +02:00
|
|
|
renderNote(note, req, res);
|
2021-10-17 14:44:59 +02:00
|
|
|
});
|
2021-10-19 22:48:38 +02:00
|
|
|
|
2025-03-10 07:33:20 +01:00
|
|
|
router.get("/share/api/notes/:noteId", (req, res, _next) => {
|
2022-05-01 23:16:47 +02:00
|
|
|
shacaLoader.ensureLoad();
|
2024-04-10 19:04:38 +03:00
|
|
|
let note: SNote | boolean;
|
2022-05-01 23:16:47 +02:00
|
|
|
|
2022-07-31 21:45:32 +02:00
|
|
|
if (!(note = checkNoteAccess(req.params.noteId, req, res))) {
|
|
|
|
return;
|
2021-10-19 22:48:38 +02:00
|
|
|
}
|
|
|
|
|
2022-03-22 23:17:47 +01:00
|
|
|
addNoIndexHeader(note, res);
|
|
|
|
|
2023-06-05 23:05:05 +02:00
|
|
|
res.json(note.getPojo());
|
2021-10-19 22:48:38 +02:00
|
|
|
});
|
2021-12-06 22:53:17 +01:00
|
|
|
|
2025-03-10 07:33:20 +01:00
|
|
|
router.get("/share/api/notes/:noteId/download", (req, res, _next) => {
|
2022-05-01 23:16:47 +02:00
|
|
|
shacaLoader.ensureLoad();
|
|
|
|
|
2024-04-10 19:04:38 +03:00
|
|
|
let note: SNote | boolean;
|
2021-12-06 22:53:17 +01:00
|
|
|
|
2022-07-31 21:45:32 +02:00
|
|
|
if (!(note = checkNoteAccess(req.params.noteId, req, res))) {
|
|
|
|
return;
|
2021-12-06 22:53:17 +01:00
|
|
|
}
|
|
|
|
|
2022-03-22 23:17:47 +01:00
|
|
|
addNoIndexHeader(note, res);
|
|
|
|
|
2021-12-06 22:53:17 +01:00
|
|
|
const filename = utils.formatDownloadTitle(note.title, note.type, note.mime);
|
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
res.setHeader("Content-Disposition", utils.getContentDisposition(filename));
|
2021-12-06 22:53:17 +01:00
|
|
|
|
|
|
|
res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
2025-01-09 18:07:02 +02:00
|
|
|
res.setHeader("Content-Type", note.mime);
|
2021-12-06 22:53:17 +01:00
|
|
|
|
|
|
|
res.send(note.getContent());
|
|
|
|
});
|
2021-12-27 20:48:14 +01:00
|
|
|
|
2023-05-05 23:17:23 +02:00
|
|
|
// :filename is not used by trilium, but instead used for "save as" to assign a human-readable filename
|
2025-03-10 07:33:20 +01:00
|
|
|
router.get("/share/api/images/:noteId/:filename", (req, res, _next) => {
|
2022-05-01 23:16:47 +02:00
|
|
|
shacaLoader.ensureLoad();
|
|
|
|
|
2024-04-10 19:04:38 +03:00
|
|
|
let image: SNote | boolean;
|
2022-01-01 13:23:09 +01:00
|
|
|
|
2022-07-31 21:45:32 +02:00
|
|
|
if (!(image = checkNoteAccess(req.params.noteId, req, res))) {
|
|
|
|
return;
|
2022-01-01 13:23:09 +01:00
|
|
|
}
|
2022-07-31 21:45:32 +02:00
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
if (image.type === "image") {
|
2022-04-11 21:38:05 +02:00
|
|
|
// normal image
|
2025-01-09 18:07:02 +02:00
|
|
|
res.set("Content-Type", image.mime);
|
2022-05-03 21:56:52 +02:00
|
|
|
addNoIndexHeader(image, res);
|
2022-04-11 21:38:05 +02:00
|
|
|
res.send(image.getContent());
|
2023-10-21 17:32:07 +02:00
|
|
|
} else if (image.type === "canvas") {
|
2025-01-09 18:07:02 +02:00
|
|
|
renderImageAttachment(image, res, "canvas-export.svg");
|
|
|
|
} else if (image.type === "mermaid") {
|
|
|
|
renderImageAttachment(image, res, "mermaid-export.svg");
|
2024-09-01 22:41:48 +03:00
|
|
|
} else if (image.type === "mindMap") {
|
2025-01-09 18:07:02 +02:00
|
|
|
renderImageAttachment(image, res, "mindmap-export.svg");
|
2023-10-21 17:32:07 +02:00
|
|
|
} else {
|
2025-01-09 18:07:02 +02:00
|
|
|
res.status(400).json({ message: "Requested note is not a shareable image" });
|
2022-01-01 13:23:09 +01:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-06-05 23:05:05 +02:00
|
|
|
// :filename is not used by trilium, but instead used for "save as" to assign a human-readable filename
|
2025-03-10 07:33:20 +01:00
|
|
|
router.get("/share/api/attachments/:attachmentId/image/:filename", (req, res, _next) => {
|
2023-06-05 23:05:05 +02:00
|
|
|
shacaLoader.ensureLoad();
|
|
|
|
|
2024-04-10 19:04:38 +03:00
|
|
|
let attachment: SAttachment | boolean;
|
2023-06-05 23:05:05 +02:00
|
|
|
|
|
|
|
if (!(attachment = checkAttachmentAccess(req.params.attachmentId, req, res))) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (attachment.role === "image") {
|
2025-01-09 18:07:02 +02:00
|
|
|
res.set("Content-Type", attachment.mime);
|
2023-06-05 23:05:05 +02:00
|
|
|
addNoIndexHeader(attachment.note, res);
|
|
|
|
res.send(attachment.getContent());
|
|
|
|
} else {
|
2025-01-09 18:07:02 +02:00
|
|
|
res.status(400).json({ message: "Requested attachment is not a shareable image" });
|
2023-06-05 23:05:05 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2025-03-10 07:33:20 +01:00
|
|
|
router.get("/share/api/attachments/:attachmentId/download", (req, res, _next) => {
|
2023-06-06 00:16:32 +02:00
|
|
|
shacaLoader.ensureLoad();
|
|
|
|
|
2024-04-10 19:04:38 +03:00
|
|
|
let attachment: SAttachment | boolean;
|
2023-06-06 00:16:32 +02:00
|
|
|
|
|
|
|
if (!(attachment = checkAttachmentAccess(req.params.attachmentId, req, res))) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
addNoIndexHeader(attachment.note, res);
|
|
|
|
|
|
|
|
const filename = utils.formatDownloadTitle(attachment.title, null, attachment.mime);
|
|
|
|
|
2025-01-09 18:07:02 +02:00
|
|
|
res.setHeader("Content-Disposition", utils.getContentDisposition(filename));
|
2023-06-06 00:16:32 +02:00
|
|
|
|
|
|
|
res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
2025-01-09 18:07:02 +02:00
|
|
|
res.setHeader("Content-Type", attachment.mime);
|
2023-06-06 00:16:32 +02:00
|
|
|
|
|
|
|
res.send(attachment.getContent());
|
|
|
|
});
|
|
|
|
|
2022-01-01 13:23:09 +01:00
|
|
|
// used for PDF viewing
|
2025-03-10 07:33:20 +01:00
|
|
|
router.get("/share/api/notes/:noteId/view", (req, res, _next) => {
|
2022-05-01 23:16:47 +02:00
|
|
|
shacaLoader.ensureLoad();
|
|
|
|
|
2024-04-10 19:04:38 +03:00
|
|
|
let note: SNote | boolean;
|
2021-12-24 21:36:31 +00:00
|
|
|
|
2022-07-31 21:45:32 +02:00
|
|
|
if (!(note = checkNoteAccess(req.params.noteId, req, res))) {
|
|
|
|
return;
|
2021-12-24 21:36:31 +00:00
|
|
|
}
|
|
|
|
|
2022-03-22 23:17:47 +01:00
|
|
|
addNoIndexHeader(note, res);
|
|
|
|
|
2021-12-24 21:36:31 +00:00
|
|
|
res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
2025-01-09 18:07:02 +02:00
|
|
|
res.setHeader("Content-Type", note.mime);
|
2021-12-24 21:36:31 +00:00
|
|
|
|
|
|
|
res.send(note.getContent());
|
|
|
|
});
|
2023-09-27 14:34:07 -04:00
|
|
|
|
|
|
|
// Used for searching, require noteId so we know the subTreeRoot
|
2025-03-10 07:33:20 +01:00
|
|
|
router.get("/share/api/notes", (req, res, _next) => {
|
2023-09-27 14:34:07 -04:00
|
|
|
shacaLoader.ensureLoad();
|
|
|
|
|
2023-10-08 14:54:37 -04:00
|
|
|
const ancestorNoteId = req.query.ancestorNoteId ?? "_share";
|
2023-09-27 14:34:07 -04:00
|
|
|
|
2024-04-10 19:04:38 +03:00
|
|
|
if (typeof ancestorNoteId !== "string") {
|
2024-12-10 22:35:23 +02:00
|
|
|
res.status(400).json({ message: "'ancestorNoteId' parameter is mandatory." });
|
|
|
|
return;
|
2024-04-10 19:04:38 +03:00
|
|
|
}
|
|
|
|
|
2023-10-08 14:54:37 -04:00
|
|
|
// This will automatically return if no ancestorNoteId is provided and there is no shareIndex
|
2025-03-10 07:34:43 +01:00
|
|
|
if (!checkNoteAccess(ancestorNoteId, req, res)) {
|
2023-09-27 14:34:07 -04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-04-09 21:51:23 +03:00
|
|
|
const { search } = req.query;
|
2023-09-27 14:34:07 -04:00
|
|
|
|
2024-04-10 19:04:38 +03:00
|
|
|
if (typeof search !== "string" || !search?.trim()) {
|
2024-12-10 22:35:23 +02:00
|
|
|
res.status(400).json({ message: "'search' parameter is mandatory." });
|
|
|
|
return;
|
2023-09-27 14:34:07 -04:00
|
|
|
}
|
|
|
|
|
2024-04-09 21:51:23 +03:00
|
|
|
const searchContext = new SearchContext({ ancestorNoteId: ancestorNoteId });
|
2023-10-08 14:54:37 -04:00
|
|
|
const searchResults = searchService.findResultsWithQuery(search, searchContext);
|
2025-01-09 18:07:02 +02:00
|
|
|
const filteredResults = searchResults.map((sr) => {
|
2023-09-27 14:34:07 -04:00
|
|
|
const fullNote = shaca.notes[sr.noteId];
|
2023-10-08 14:54:37 -04:00
|
|
|
const startIndex = sr.notePathArray.indexOf(ancestorNoteId);
|
2025-01-09 18:07:02 +02:00
|
|
|
const localPathArray = sr.notePathArray.slice(startIndex + 1).filter((id) => shaca.notes[id]);
|
|
|
|
const pathTitle = localPathArray.map((id) => shaca.notes[id].title).join(" / ");
|
2023-10-08 14:54:37 -04:00
|
|
|
return { id: fullNote.shareId, title: fullNote.title, score: sr.score, path: pathTitle };
|
2023-09-27 14:34:07 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
res.json({ results: filteredResults });
|
|
|
|
});
|
2021-10-17 14:44:59 +02:00
|
|
|
}
|
|
|
|
|
2024-07-18 21:42:44 +03:00
|
|
|
export default {
|
2021-10-17 14:44:59 +02:00
|
|
|
register
|
2025-01-09 18:07:02 +02:00
|
|
|
};
|