mirror of
				https://github.com/TriliumNext/Notes.git
				synced 2025-11-04 15:11:31 +08:00 
			
		
		
		
	Merge pull request #1347 from TriliumNext/chore_eslint-fixes_src-routes
chore(lint): fix eslint issues in `src/routes`
This commit is contained in:
		
						commit
						14c3fd5892
					
				@ -1618,7 +1618,7 @@ class BNote extends AbstractBeccaEntity<BNote> {
 | 
				
			|||||||
     * @param matchBy - choose by which property we detect if to update an existing attachment.
 | 
					     * @param matchBy - choose by which property we detect if to update an existing attachment.
 | 
				
			||||||
     *                      Supported values are either 'attachmentId' (default) or 'title'
 | 
					     *                      Supported values are either 'attachmentId' (default) or 'title'
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    saveAttachment({ attachmentId, role, mime, title, content, position }: AttachmentRow, matchBy = "attachmentId") {
 | 
					    saveAttachment({ attachmentId, role, mime, title, content, position }: AttachmentRow, matchBy: "attachmentId" | "title" | undefined = "attachmentId") {
 | 
				
			||||||
        if (!["attachmentId", "title"].includes(matchBy)) {
 | 
					        if (!["attachmentId", "title"].includes(matchBy)) {
 | 
				
			||||||
            throw new Error(`Unsupported value '${matchBy}' for matchBy param, has to be either 'attachmentId' or 'title'.`);
 | 
					            throw new Error(`Unsupported value '${matchBy}' for matchBy param, has to be either 'attachmentId' or 'title'.`);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -7,7 +7,7 @@ import becca from "../becca.js";
 | 
				
			|||||||
import AbstractBeccaEntity from "./abstract_becca_entity.js";
 | 
					import AbstractBeccaEntity from "./abstract_becca_entity.js";
 | 
				
			||||||
import sql from "../../services/sql.js";
 | 
					import sql from "../../services/sql.js";
 | 
				
			||||||
import BAttachment from "./battachment.js";
 | 
					import BAttachment from "./battachment.js";
 | 
				
			||||||
import type { AttachmentRow, RevisionRow } from "./rows.js";
 | 
					import type { AttachmentRow, NoteType, RevisionRow } from "./rows.js";
 | 
				
			||||||
import eraseService from "../../services/erase.js";
 | 
					import eraseService from "../../services/erase.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface ContentOpts {
 | 
					interface ContentOpts {
 | 
				
			||||||
@ -36,7 +36,7 @@ class BRevision extends AbstractBeccaEntity<BRevision> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    revisionId?: string;
 | 
					    revisionId?: string;
 | 
				
			||||||
    noteId!: string;
 | 
					    noteId!: string;
 | 
				
			||||||
    type!: string;
 | 
					    type!: NoteType;
 | 
				
			||||||
    mime!: string;
 | 
					    mime!: string;
 | 
				
			||||||
    title!: string;
 | 
					    title!: string;
 | 
				
			||||||
    dateLastEdited?: string;
 | 
					    dateLastEdited?: string;
 | 
				
			||||||
 | 
				
			|||||||
@ -22,7 +22,7 @@ export interface AttachmentRow {
 | 
				
			|||||||
export interface RevisionRow {
 | 
					export interface RevisionRow {
 | 
				
			||||||
    revisionId?: string;
 | 
					    revisionId?: string;
 | 
				
			||||||
    noteId: string;
 | 
					    noteId: string;
 | 
				
			||||||
    type: string;
 | 
					    type: NoteType;
 | 
				
			||||||
    mime: string;
 | 
					    mime: string;
 | 
				
			||||||
    isProtected?: boolean;
 | 
					    isProtected?: boolean;
 | 
				
			||||||
    title: string;
 | 
					    title: string;
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										12
									
								
								src/errors/forbidden_error.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/errors/forbidden_error.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					import HttpError from "./http_error.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ForbiddenError extends HttpError {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructor(message: string) {
 | 
				
			||||||
 | 
					        super(message, 403);
 | 
				
			||||||
 | 
					        this.name = "ForbiddenError";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default ForbiddenError;
 | 
				
			||||||
							
								
								
									
										13
									
								
								src/errors/http_error.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/errors/http_error.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					class HttpError extends Error {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    statusCode: number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructor(message: string, statusCode: number) {
 | 
				
			||||||
 | 
					        super(message);
 | 
				
			||||||
 | 
					        this.name = "HttpError";
 | 
				
			||||||
 | 
					        this.statusCode = statusCode;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default HttpError;
 | 
				
			||||||
@ -1,9 +1,12 @@
 | 
				
			|||||||
class NotFoundError {
 | 
					import HttpError from "./http_error.js";
 | 
				
			||||||
    message: string;
 | 
					
 | 
				
			||||||
 | 
					class NotFoundError extends HttpError {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor(message: string) {
 | 
					    constructor(message: string) {
 | 
				
			||||||
        this.message = message;
 | 
					        super(message, 404);
 | 
				
			||||||
 | 
					        this.name = "NotFoundError";
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default NotFoundError;
 | 
					export default NotFoundError;
 | 
				
			||||||
 | 
				
			|||||||
@ -1,9 +1,12 @@
 | 
				
			|||||||
class ValidationError {
 | 
					import HttpError from "./http_error.js";
 | 
				
			||||||
    message: string;
 | 
					
 | 
				
			||||||
 | 
					class ValidationError extends HttpError {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor(message: string) {
 | 
					    constructor(message: string) {
 | 
				
			||||||
        this.message = message;
 | 
					        super(message, 400)
 | 
				
			||||||
 | 
					        this.name = "ValidationError";
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default ValidationError;
 | 
					export default ValidationError;
 | 
				
			||||||
 | 
				
			|||||||
@ -33,7 +33,9 @@ function getAllAttachments(req: Request) {
 | 
				
			|||||||
function saveAttachment(req: Request) {
 | 
					function saveAttachment(req: Request) {
 | 
				
			||||||
    const { noteId } = req.params;
 | 
					    const { noteId } = req.params;
 | 
				
			||||||
    const { attachmentId, role, mime, title, content } = req.body;
 | 
					    const { attachmentId, role, mime, title, content } = req.body;
 | 
				
			||||||
    const { matchBy } = req.query as any;
 | 
					    const matchByQuery = req.query.matchBy
 | 
				
			||||||
 | 
					    const isValidMatchBy = (typeof matchByQuery === "string") && (matchByQuery === "attachmentId" || matchByQuery === "title");
 | 
				
			||||||
 | 
					    const matchBy = isValidMatchBy ? matchByQuery : undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const note = becca.getNoteOrThrow(noteId);
 | 
					    const note = becca.getNoteOrThrow(noteId);
 | 
				
			||||||
    note.saveAttachment({ attachmentId, role, mime, title, content }, matchBy);
 | 
					    note.saveAttachment({ attachmentId, role, mime, title, content }, matchBy);
 | 
				
			||||||
@ -41,7 +43,14 @@ function saveAttachment(req: Request) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
function uploadAttachment(req: Request) {
 | 
					function uploadAttachment(req: Request) {
 | 
				
			||||||
    const { noteId } = req.params;
 | 
					    const { noteId } = req.params;
 | 
				
			||||||
    const { file } = req as any;
 | 
					    const { file } = req;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!file) {
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            uploaded: false,
 | 
				
			||||||
 | 
					            message: `Missing attachment data.`
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const note = becca.getNoteOrThrow(noteId);
 | 
					    const note = becca.getNoteOrThrow(noteId);
 | 
				
			||||||
    let url;
 | 
					    let url;
 | 
				
			||||||
 | 
				
			|||||||
@ -30,12 +30,12 @@ function addClipping(req: Request) {
 | 
				
			|||||||
    // if a note under the clipperInbox has the same 'pageUrl' attribute,
 | 
					    // if a note under the clipperInbox has the same 'pageUrl' attribute,
 | 
				
			||||||
    // add the content to that note and clone it under today's inbox
 | 
					    // add the content to that note and clone it under today's inbox
 | 
				
			||||||
    // otherwise just create a new note under today's inbox
 | 
					    // otherwise just create a new note under today's inbox
 | 
				
			||||||
    let { title, content, pageUrl, images } = req.body;
 | 
					    const { title, content, images } = req.body;
 | 
				
			||||||
    const clipType = "clippings";
 | 
					    const clipType = "clippings";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const clipperInbox = getClipperInboxNote();
 | 
					    const clipperInbox = getClipperInboxNote();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pageUrl = htmlSanitizer.sanitizeUrl(pageUrl);
 | 
					    const pageUrl = htmlSanitizer.sanitizeUrl(req.body.pageUrl);
 | 
				
			||||||
    let clippingNote = findClippingNote(clipperInbox, pageUrl, clipType);
 | 
					    let clippingNote = findClippingNote(clipperInbox, pageUrl, clipType);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!clippingNote) {
 | 
					    if (!clippingNote) {
 | 
				
			||||||
@ -100,16 +100,15 @@ function getClipperInboxNote() {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function createNote(req: Request) {
 | 
					function createNote(req: Request) {
 | 
				
			||||||
    let { title, content, pageUrl, images, clipType, labels } = req.body;
 | 
					    const { content, images, labels } = req.body;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!title || !title.trim()) {
 | 
					    const clipType = htmlSanitizer.sanitize(req.body.clipType);
 | 
				
			||||||
        title = `Clipped note from ${pageUrl}`;
 | 
					    const pageUrl = htmlSanitizer.sanitizeUrl(req.body.pageUrl);
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    clipType = htmlSanitizer.sanitize(clipType);
 | 
					    const trimmedTitle = (typeof req.body.title === "string") ? req.body.title.trim() : "";
 | 
				
			||||||
 | 
					    const title = trimmedTitle || `Clipped note from ${pageUrl}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const clipperInbox = getClipperInboxNote();
 | 
					    const clipperInbox = getClipperInboxNote();
 | 
				
			||||||
    pageUrl = htmlSanitizer.sanitizeUrl(pageUrl);
 | 
					 | 
				
			||||||
    let note = findClippingNote(clipperInbox, pageUrl, clipType);
 | 
					    let note = findClippingNote(clipperInbox, pageUrl, clipType);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!note) {
 | 
					    if (!note) {
 | 
				
			||||||
@ -123,8 +122,6 @@ function createNote(req: Request) {
 | 
				
			|||||||
        note.setLabel("clipType", clipType);
 | 
					        note.setLabel("clipType", clipType);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (pageUrl) {
 | 
					        if (pageUrl) {
 | 
				
			||||||
            pageUrl = htmlSanitizer.sanitizeUrl(pageUrl);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            note.setLabel("pageUrl", pageUrl);
 | 
					            note.setLabel("pageUrl", pageUrl);
 | 
				
			||||||
            note.setLabel("iconClass", "bx bx-globe");
 | 
					            note.setLabel("iconClass", "bx bx-globe");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -139,7 +136,7 @@ function createNote(req: Request) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    const existingContent = note.getContent();
 | 
					    const existingContent = note.getContent();
 | 
				
			||||||
    if (typeof existingContent !== "string") {
 | 
					    if (typeof existingContent !== "string") {
 | 
				
			||||||
        throw new ValidationError("Invalid note content tpye.");
 | 
					        throw new ValidationError("Invalid note content type.");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    const rewrittenContent = processContent(images, note, content);
 | 
					    const rewrittenContent = processContent(images, note, content);
 | 
				
			||||||
    const newContent = `${existingContent}${existingContent.trim() ? "<br/>" : ""}${rewrittenContent}`;
 | 
					    const newContent = `${existingContent}${existingContent.trim() ? "<br/>" : ""}${rewrittenContent}`;
 | 
				
			||||||
@ -219,9 +216,9 @@ function handshake() {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function findNotesByUrl(req: Request) {
 | 
					function findNotesByUrl(req: Request) {
 | 
				
			||||||
    let pageUrl = req.params.noteUrl;
 | 
					    const pageUrl = req.params.noteUrl;
 | 
				
			||||||
    const clipperInbox = getClipperInboxNote();
 | 
					    const clipperInbox = getClipperInboxNote();
 | 
				
			||||||
    let foundPage = findClippingNote(clipperInbox, pageUrl, null);
 | 
					    const foundPage = findClippingNote(clipperInbox, pageUrl, null);
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
        noteId: foundPage ? foundPage.noteId : null
 | 
					        noteId: foundPage ? foundPage.noteId : null
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
				
			|||||||
@ -9,6 +9,7 @@ import log from "../../services/log.js";
 | 
				
			|||||||
import NotFoundError from "../../errors/not_found_error.js";
 | 
					import NotFoundError from "../../errors/not_found_error.js";
 | 
				
			||||||
import type { Request, Response } from "express";
 | 
					import type { Request, Response } from "express";
 | 
				
			||||||
import ValidationError from "../../errors/validation_error.js";
 | 
					import ValidationError from "../../errors/validation_error.js";
 | 
				
			||||||
 | 
					import { safeExtractMessageAndStackFromError } from "../../services/utils.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function exportBranch(req: Request, res: Response) {
 | 
					function exportBranch(req: Request, res: Response) {
 | 
				
			||||||
    const { branchId, type, format, version, taskId } = req.params;
 | 
					    const { branchId, type, format, version, taskId } = req.params;
 | 
				
			||||||
@ -37,11 +38,12 @@ function exportBranch(req: Request, res: Response) {
 | 
				
			|||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            throw new NotFoundError(`Unrecognized export format '${format}'`);
 | 
					            throw new NotFoundError(`Unrecognized export format '${format}'`);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    } catch (e: any) {
 | 
					    } catch (e: unknown) {
 | 
				
			||||||
        const message = `Export failed with following error: '${e.message}'. More details might be in the logs.`;
 | 
					        const [errMessage, errStack] = safeExtractMessageAndStackFromError(e);
 | 
				
			||||||
 | 
					        const message = `Export failed with following error: '${errMessage}'. More details might be in the logs.`;
 | 
				
			||||||
        taskContext.reportError(message);
 | 
					        taskContext.reportError(message);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        log.error(message + e.stack);
 | 
					        log.error(errMessage + errStack);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        res.setHeader("Content-Type", "text/plain").status(500).send(message);
 | 
					        res.setHeader("Content-Type", "text/plain").status(500).send(message);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -82,7 +82,7 @@ function updateImage(req: Request) {
 | 
				
			|||||||
    const { noteId } = req.params;
 | 
					    const { noteId } = req.params;
 | 
				
			||||||
    const { file } = req;
 | 
					    const { file } = req;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const note = becca.getNoteOrThrow(noteId);
 | 
					    const _note = becca.getNoteOrThrow(noteId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!file) {
 | 
					    if (!file) {
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
 | 
				
			|||||||
@ -13,6 +13,7 @@ import TaskContext from "../../services/task_context.js";
 | 
				
			|||||||
import ValidationError from "../../errors/validation_error.js";
 | 
					import ValidationError from "../../errors/validation_error.js";
 | 
				
			||||||
import type { Request } from "express";
 | 
					import type { Request } from "express";
 | 
				
			||||||
import type BNote from "../../becca/entities/bnote.js";
 | 
					import type BNote from "../../becca/entities/bnote.js";
 | 
				
			||||||
 | 
					import { safeExtractMessageAndStackFromError } from "../../services/utils.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function importNotesToBranch(req: Request) {
 | 
					async function importNotesToBranch(req: Request) {
 | 
				
			||||||
    const { parentNoteId } = req.params;
 | 
					    const { parentNoteId } = req.params;
 | 
				
			||||||
@ -68,11 +69,12 @@ async function importNotesToBranch(req: Request) {
 | 
				
			|||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            note = await singleImportService.importSingleFile(taskContext, file, parentNote);
 | 
					            note = await singleImportService.importSingleFile(taskContext, file, parentNote);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    } catch (e: any) {
 | 
					    } catch (e: unknown) {
 | 
				
			||||||
        const message = `Import failed with following error: '${e.message}'. More details might be in the logs.`;
 | 
					        const [errMessage, errStack] = safeExtractMessageAndStackFromError(e);
 | 
				
			||||||
 | 
					        const message = `Import failed with following error: '${errMessage}'. More details might be in the logs.`;
 | 
				
			||||||
        taskContext.reportError(message);
 | 
					        taskContext.reportError(message);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        log.error(message + e.stack);
 | 
					        log.error(message + errStack);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return [500, message];
 | 
					        return [500, message];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -120,11 +122,13 @@ async function importAttachmentsToNote(req: Request) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
        await singleImportService.importAttachment(taskContext, file, parentNote);
 | 
					        await singleImportService.importAttachment(taskContext, file, parentNote);
 | 
				
			||||||
    } catch (e: any) {
 | 
					    } catch (e: unknown) {
 | 
				
			||||||
        const message = `Import failed with following error: '${e.message}'. More details might be in the logs.`;
 | 
					        const [errMessage, errStack] = safeExtractMessageAndStackFromError(e);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const message = `Import failed with following error: '${errMessage}'. More details might be in the logs.`;
 | 
				
			||||||
        taskContext.reportError(message);
 | 
					        taskContext.reportError(message);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        log.error(message + e.stack);
 | 
					        log.error(message + errStack);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return [500, message];
 | 
					        return [500, message];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -5,7 +5,6 @@ import protectedSessionService from "../../services/protected_session.js";
 | 
				
			|||||||
import noteService from "../../services/notes.js";
 | 
					import noteService from "../../services/notes.js";
 | 
				
			||||||
import becca from "../../becca/becca.js";
 | 
					import becca from "../../becca/becca.js";
 | 
				
			||||||
import type { Request } from "express";
 | 
					import type { Request } from "express";
 | 
				
			||||||
import type { RevisionRow } from "../../becca/entities/rows.js";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface RecentChangeRow {
 | 
					interface RecentChangeRow {
 | 
				
			||||||
    noteId: string;
 | 
					    noteId: string;
 | 
				
			||||||
 | 
				
			|||||||
@ -30,7 +30,7 @@ function getRelationMap(req: Request) {
 | 
				
			|||||||
        return resp;
 | 
					        return resp;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const questionMarks = noteIds.map((noteId) => "?").join(",");
 | 
					    const questionMarks = noteIds.map((_noteId) => "?").join(",");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const relationMapNote = becca.getNoteOrThrow(relationMapNoteId);
 | 
					    const relationMapNote = becca.getNoteOrThrow(relationMapNoteId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,6 @@
 | 
				
			|||||||
"use strict";
 | 
					"use strict";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import beccaService from "../../becca/becca_service.js";
 | 
					import beccaService from "../../becca/becca_service.js";
 | 
				
			||||||
import revisionService from "../../services/revisions.js";
 | 
					 | 
				
			||||||
import utils from "../../services/utils.js";
 | 
					import utils from "../../services/utils.js";
 | 
				
			||||||
import sql from "../../services/sql.js";
 | 
					import sql from "../../services/sql.js";
 | 
				
			||||||
import cls from "../../services/cls.js";
 | 
					import cls from "../../services/cls.js";
 | 
				
			||||||
@ -111,7 +110,7 @@ function eraseRevision(req: Request) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function eraseAllExcessRevisions() {
 | 
					function eraseAllExcessRevisions() {
 | 
				
			||||||
    let allNoteIds = sql.getRows("SELECT noteId FROM notes WHERE SUBSTRING(noteId, 1, 1) != '_'") as { noteId: string }[];
 | 
					    const allNoteIds = sql.getRows("SELECT noteId FROM notes WHERE SUBSTRING(noteId, 1, 1) != '_'") as { noteId: string }[];
 | 
				
			||||||
    allNoteIds.forEach((row) => {
 | 
					    allNoteIds.forEach((row) => {
 | 
				
			||||||
        becca.getNote(row.noteId)?.eraseExcessRevisionSnapshots();
 | 
					        becca.getNote(row.noteId)?.eraseExcessRevisionSnapshots();
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
@ -145,7 +144,7 @@ function restoreRevision(req: Request) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            note.title = revision.title;
 | 
					            note.title = revision.title;
 | 
				
			||||||
            note.mime = revision.mime;
 | 
					            note.mime = revision.mime;
 | 
				
			||||||
            note.type = revision.type as any;
 | 
					            note.type = revision.type;
 | 
				
			||||||
            note.setContent(revisionContent, { forceSave: true });
 | 
					            note.setContent(revisionContent, { forceSave: true });
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -6,6 +6,7 @@ import becca from "../../becca/becca.js";
 | 
				
			|||||||
import syncService from "../../services/sync.js";
 | 
					import syncService from "../../services/sync.js";
 | 
				
			||||||
import sql from "../../services/sql.js";
 | 
					import sql from "../../services/sql.js";
 | 
				
			||||||
import type { Request } from "express";
 | 
					import type { Request } from "express";
 | 
				
			||||||
 | 
					import { safeExtractMessageAndStackFromError } from "../../services/utils.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface ScriptBody {
 | 
					interface ScriptBody {
 | 
				
			||||||
    script: string;
 | 
					    script: string;
 | 
				
			||||||
@ -33,8 +34,12 @@ async function exec(req: Request) {
 | 
				
			|||||||
            executionResult: result,
 | 
					            executionResult: result,
 | 
				
			||||||
            maxEntityChangeId: syncService.getMaxEntityChangeId()
 | 
					            maxEntityChangeId: syncService.getMaxEntityChangeId()
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
    } catch (e: any) {
 | 
					    } catch (e: unknown) {
 | 
				
			||||||
        return { success: false, error: e.message };
 | 
					        const [errMessage] = safeExtractMessageAndStackFromError(e);
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            success: false,
 | 
				
			||||||
 | 
					            error: errMessage
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -8,7 +8,7 @@ import becca from "../../becca/becca.js";
 | 
				
			|||||||
async function getSimilarNotes(req: Request) {
 | 
					async function getSimilarNotes(req: Request) {
 | 
				
			||||||
    const noteId = req.params.noteId;
 | 
					    const noteId = req.params.noteId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const note = becca.getNoteOrThrow(noteId);
 | 
					    const _note = becca.getNoteOrThrow(noteId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return await similarityService.findSimilarNotes(noteId);
 | 
					    return await similarityService.findSimilarNotes(noteId);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -4,6 +4,7 @@ import sql from "../../services/sql.js";
 | 
				
			|||||||
import becca from "../../becca/becca.js";
 | 
					import becca from "../../becca/becca.js";
 | 
				
			||||||
import type { Request } from "express";
 | 
					import type { Request } from "express";
 | 
				
			||||||
import ValidationError from "../../errors/validation_error.js";
 | 
					import ValidationError from "../../errors/validation_error.js";
 | 
				
			||||||
 | 
					import { safeExtractMessageAndStackFromError } from "../../services/utils.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function getSchema() {
 | 
					function getSchema() {
 | 
				
			||||||
    const tableNames = sql.getColumn(`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name`);
 | 
					    const tableNames = sql.getColumn(`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name`);
 | 
				
			||||||
@ -56,10 +57,11 @@ function execute(req: Request) {
 | 
				
			|||||||
            success: true,
 | 
					            success: true,
 | 
				
			||||||
            results
 | 
					            results
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
    } catch (e: any) {
 | 
					    } catch (e: unknown) {
 | 
				
			||||||
 | 
					        const [errMessage] = safeExtractMessageAndStackFromError(e);
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            success: false,
 | 
					            success: false,
 | 
				
			||||||
            error: e.message
 | 
					            error: errMessage
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -9,7 +9,7 @@ import optionService from "../../services/options.js";
 | 
				
			|||||||
import contentHashService from "../../services/content_hash.js";
 | 
					import contentHashService from "../../services/content_hash.js";
 | 
				
			||||||
import log from "../../services/log.js";
 | 
					import log from "../../services/log.js";
 | 
				
			||||||
import syncOptions from "../../services/sync_options.js";
 | 
					import syncOptions from "../../services/sync_options.js";
 | 
				
			||||||
import utils from "../../services/utils.js";
 | 
					import utils, { safeExtractMessageAndStackFromError } from "../../services/utils.js";
 | 
				
			||||||
import ws from "../../services/ws.js";
 | 
					import ws from "../../services/ws.js";
 | 
				
			||||||
import type { Request } from "express";
 | 
					import type { Request } from "express";
 | 
				
			||||||
import type { EntityChange } from "../../services/entity_changes_interface.js";
 | 
					import type { EntityChange } from "../../services/entity_changes_interface.js";
 | 
				
			||||||
@ -30,10 +30,11 @@ async function testSync() {
 | 
				
			|||||||
        syncService.sync();
 | 
					        syncService.sync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return { success: true, message: t("test_sync.successful") };
 | 
					        return { success: true, message: t("test_sync.successful") };
 | 
				
			||||||
    } catch (e: any) {
 | 
					    } catch (e: unknown) {
 | 
				
			||||||
 | 
					        const [errMessage] = safeExtractMessageAndStackFromError(e);
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            success: false,
 | 
					            success: false,
 | 
				
			||||||
            message: e.message
 | 
					            error: errMessage
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
import type { Application, Router } from "express";
 | 
					import type { Application } from "express";
 | 
				
			||||||
import swaggerUi from "swagger-ui-express";
 | 
					import swaggerUi from "swagger-ui-express";
 | 
				
			||||||
import { readFile } from "fs/promises";
 | 
					import { readFile } from "fs/promises";
 | 
				
			||||||
import { fileURLToPath } from "url";
 | 
					import { fileURLToPath } from "url";
 | 
				
			||||||
 | 
				
			|||||||
@ -5,7 +5,7 @@ import express from "express";
 | 
				
			|||||||
import { isDev, isElectron } from "../services/utils.js";
 | 
					import { isDev, isElectron } from "../services/utils.js";
 | 
				
			||||||
import type serveStatic from "serve-static";
 | 
					import type serveStatic from "serve-static";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const persistentCacheStatic = (root: string, options?: serveStatic.ServeStaticOptions<express.Response<any, Record<string, any>>>) => {
 | 
					const persistentCacheStatic = (root: string, options?: serveStatic.ServeStaticOptions<express.Response<unknown, Record<string, unknown>>>) => {
 | 
				
			||||||
    if (!isDev) {
 | 
					    if (!isDev) {
 | 
				
			||||||
        options = {
 | 
					        options = {
 | 
				
			||||||
            maxAge: "1y",
 | 
					            maxAge: "1y",
 | 
				
			||||||
 | 
				
			|||||||
@ -5,6 +5,7 @@ import cls from "../services/cls.js";
 | 
				
			|||||||
import sql from "../services/sql.js";
 | 
					import sql from "../services/sql.js";
 | 
				
			||||||
import becca from "../becca/becca.js";
 | 
					import becca from "../becca/becca.js";
 | 
				
			||||||
import type { Request, Response, Router } from "express";
 | 
					import type { Request, Response, Router } from "express";
 | 
				
			||||||
 | 
					import { safeExtractMessageAndStackFromError } from "../services/utils.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function handleRequest(req: Request, res: Response) {
 | 
					function handleRequest(req: Request, res: Response) {
 | 
				
			||||||
    // express puts content after first slash into 0 index element
 | 
					    // express puts content after first slash into 0 index element
 | 
				
			||||||
@ -25,8 +26,9 @@ function handleRequest(req: Request, res: Response) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            match = path.match(regex);
 | 
					            match = path.match(regex);
 | 
				
			||||||
        } catch (e: any) {
 | 
					        } catch (e: unknown) {
 | 
				
			||||||
            log.error(`Testing path for label '${attr.attributeId}', regex '${attr.value}' failed with error: ${e.message}, stack: ${e.stack}`);
 | 
					            const [errMessage, errStack] = safeExtractMessageAndStackFromError(e);
 | 
				
			||||||
 | 
					            log.error(`Testing path for label '${attr.attributeId}', regex '${attr.value}' failed with error: ${errMessage}, stack: ${errStack}`);
 | 
				
			||||||
            continue;
 | 
					            continue;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -45,10 +47,10 @@ function handleRequest(req: Request, res: Response) {
 | 
				
			|||||||
                    req,
 | 
					                    req,
 | 
				
			||||||
                    res
 | 
					                    res
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
            } catch (e: any) {
 | 
					            } catch (e: unknown) {
 | 
				
			||||||
                log.error(`Custom handler '${note.noteId}' failed with: ${e.message}, ${e.stack}`);
 | 
					                const [errMessage, errStack] = safeExtractMessageAndStackFromError(e);
 | 
				
			||||||
 | 
					                log.error(`Custom handler '${note.noteId}' failed with: ${errMessage}, ${errStack}`);
 | 
				
			||||||
                res.setHeader("Content-Type", "text/plain").status(500).send(e.message);
 | 
					                res.setHeader("Content-Type", "text/plain").status(500).send(errMessage);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        } else if (attr.name === "customResourceProvider") {
 | 
					        } else if (attr.name === "customResourceProvider") {
 | 
				
			||||||
            fileService.downloadNoteInt(attr.noteId, res);
 | 
					            fileService.downloadNoteInt(attr.noteId, res);
 | 
				
			||||||
@ -68,7 +70,7 @@ function handleRequest(req: Request, res: Response) {
 | 
				
			|||||||
function register(router: Router) {
 | 
					function register(router: Router) {
 | 
				
			||||||
    // explicitly no CSRF middleware since it's meant to allow integration from external services
 | 
					    // explicitly no CSRF middleware since it's meant to allow integration from external services
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    router.all("/custom/:path*", (req: Request, res: Response, next) => {
 | 
					    router.all("/custom/:path*", (req: Request, res: Response, _next) => {
 | 
				
			||||||
        cls.namespace.bindEmitter(req);
 | 
					        cls.namespace.bindEmitter(req);
 | 
				
			||||||
        cls.namespace.bindEmitter(res);
 | 
					        cls.namespace.bindEmitter(res);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -7,7 +7,7 @@ interface Response {
 | 
				
			|||||||
    setHeader: (name: string, value: string) => Response;
 | 
					    setHeader: (name: string, value: string) => Response;
 | 
				
			||||||
    header: (name: string, value: string) => Response;
 | 
					    header: (name: string, value: string) => Response;
 | 
				
			||||||
    status: (statusCode: number) => Response;
 | 
					    status: (statusCode: number) => Response;
 | 
				
			||||||
    send: (obj: {}) => void;
 | 
					    send: (obj: {}) => void; // eslint-disable-line @typescript-eslint/no-empty-object-type
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function init(app: Application) {
 | 
					function init(app: Application) {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,38 +1,46 @@
 | 
				
			|||||||
import type { Application, NextFunction, Request, Response } from "express";
 | 
					import type { Application, NextFunction, Request, Response } from "express";
 | 
				
			||||||
import log from "../services/log.js";
 | 
					import log from "../services/log.js";
 | 
				
			||||||
 | 
					import NotFoundError from "../errors/not_found_error.js";
 | 
				
			||||||
 | 
					import ForbiddenError from "../errors/forbidden_error.js";
 | 
				
			||||||
 | 
					import HttpError from "../errors/http_error.js";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function register(app: Application) {
 | 
					function register(app: Application) {
 | 
				
			||||||
    app.use((err: any, req: Request, res: Response, next: NextFunction) => {
 | 
					
 | 
				
			||||||
        if (err.code !== "EBADCSRFTOKEN") {
 | 
					    app.use((err: unknown | Error, req: Request, res: Response, next: NextFunction) => {
 | 
				
			||||||
            return next(err);
 | 
					
 | 
				
			||||||
 | 
					        const isCsrfTokenError = typeof err === "object"
 | 
				
			||||||
 | 
					            && err
 | 
				
			||||||
 | 
					            && "code" in err
 | 
				
			||||||
 | 
					            && err.code === "EBADCSRFTOKEN";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (isCsrfTokenError) {
 | 
				
			||||||
 | 
					            log.error(`Invalid CSRF token: ${req.headers["x-csrf-token"]}, secret: ${req.cookies["_csrf"]}`);
 | 
				
			||||||
 | 
					            return next(new ForbiddenError("Invalid CSRF token"));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        log.error(`Invalid CSRF token: ${req.headers["x-csrf-token"]}, secret: ${req.cookies["_csrf"]}`);
 | 
					        return next(err);
 | 
				
			||||||
 | 
					 | 
				
			||||||
        err = new Error("Invalid CSRF token");
 | 
					 | 
				
			||||||
        err.status = 403;
 | 
					 | 
				
			||||||
        next(err);
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // catch 404 and forward to error handler
 | 
					    // catch 404 and forward to error handler
 | 
				
			||||||
    app.use((req, res, next) => {
 | 
					    app.use((req, res, next) => {
 | 
				
			||||||
        const err = new Error(`Router not found for request ${req.method} ${req.url}`);
 | 
					        const err = new NotFoundError(`Router not found for request ${req.method} ${req.url}`);
 | 
				
			||||||
        (err as any).status = 404;
 | 
					 | 
				
			||||||
        next(err);
 | 
					        next(err);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // error handler
 | 
					    // error handler
 | 
				
			||||||
    app.use((err: any, req: Request, res: Response, next: NextFunction) => {
 | 
					    app.use((err: unknown | Error, req: Request, res: Response, _next: NextFunction) => {
 | 
				
			||||||
        if (err.status !== 404) {
 | 
					 | 
				
			||||||
            log.info(err);
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            log.info(`${err.status} ${req.method} ${req.url}`);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        res.status(err.status || 500);
 | 
					        const statusCode = (err instanceof HttpError) ? err.statusCode : 500;
 | 
				
			||||||
        res.send({
 | 
					        const errMessage = (err instanceof Error && statusCode !== 404)
 | 
				
			||||||
            message: err.message
 | 
					            ? err
 | 
				
			||||||
 | 
					            : `${statusCode} ${req.method} ${req.url}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        log.info(errMessage);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        res.status(statusCode).send({
 | 
				
			||||||
 | 
					            message: err instanceof Error ? err.message : "Unknown Error"
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,6 @@
 | 
				
			|||||||
"use strict";
 | 
					"use strict";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { isElectron } from "../services/utils.js";
 | 
					import { isElectron, safeExtractMessageAndStackFromError } from "../services/utils.js";
 | 
				
			||||||
import multer from "multer";
 | 
					import multer from "multer";
 | 
				
			||||||
import log from "../services/log.js";
 | 
					import log from "../services/log.js";
 | 
				
			||||||
import express from "express";
 | 
					import express from "express";
 | 
				
			||||||
@ -471,7 +471,7 @@ function route(method: HttpMethod, path: string, middleware: express.Handler[],
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            if (result?.then) {
 | 
					            if (result?.then) {
 | 
				
			||||||
                // promise
 | 
					                // promise
 | 
				
			||||||
                result.then((promiseResult: unknown) => handleResponse(resultHandler, req, res, promiseResult, start)).catch((e: any) => handleException(e, method, path, res));
 | 
					                result.then((promiseResult: unknown) => handleResponse(resultHandler, req, res, promiseResult, start)).catch((e: unknown) => handleException(e, method, path, res));
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                handleResponse(resultHandler, req, res, result, start);
 | 
					                handleResponse(resultHandler, req, res, result, start);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@ -487,22 +487,17 @@ function handleResponse(resultHandler: ApiResultHandler, req: express.Request, r
 | 
				
			|||||||
    log.request(req, res, Date.now() - start, responseLength);
 | 
					    log.request(req, res, Date.now() - start, responseLength);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function handleException(e: any, method: HttpMethod, path: string, res: express.Response) {
 | 
					function handleException(e: unknown | Error, method: HttpMethod, path: string, res: express.Response) {
 | 
				
			||||||
    log.error(`${method} ${path} threw exception: '${e.message}', stack: ${e.stack}`);
 | 
					    const [errMessage, errStack] = safeExtractMessageAndStackFromError(e);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    log.error(`${method} ${path} threw exception: '${errMessage}', stack: ${errStack}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const resStatusCode = (e instanceof ValidationError || e instanceof NotFoundError) ?  e.statusCode : 500;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    res.status(resStatusCode).json({
 | 
				
			||||||
 | 
					        message: errMessage
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (e instanceof ValidationError) {
 | 
					 | 
				
			||||||
        res.status(400).json({
 | 
					 | 
				
			||||||
            message: e.message
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    } else if (e instanceof NotFoundError) {
 | 
					 | 
				
			||||||
        res.status(404).json({
 | 
					 | 
				
			||||||
            message: e.message
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        res.status(500).json({
 | 
					 | 
				
			||||||
            message: e.message
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function createUploadMiddleware() {
 | 
					function createUploadMiddleware() {
 | 
				
			||||||
 | 
				
			|||||||
@ -500,6 +500,23 @@ describe("#isDev", () => {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe("#safeExtractMessageAndStackFromError", () => {
 | 
				
			||||||
 | 
					    it("should correctly extract the message and stack property if it gets passed an instance of an Error", () => {
 | 
				
			||||||
 | 
					        const testMessage = "Test Message";
 | 
				
			||||||
 | 
					        const testError = new Error(testMessage);
 | 
				
			||||||
 | 
					        const actual = utils.safeExtractMessageAndStackFromError(testError);
 | 
				
			||||||
 | 
					        expect(actual[0]).toBe(testMessage);
 | 
				
			||||||
 | 
					        expect(actual[1]).not.toBeUndefined();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it("should use the fallback 'Unknown Error' message, if it gets passed anything else than an instance of an Error", () => {
 | 
				
			||||||
 | 
					        const testNonError = "this is not an instance of an Error, but JS technically allows us to throw this anyways";
 | 
				
			||||||
 | 
					        const actual = utils.safeExtractMessageAndStackFromError(testNonError);
 | 
				
			||||||
 | 
					        expect(actual[0]).toBe("Unknown Error");
 | 
				
			||||||
 | 
					        expect(actual[1]).toBeUndefined();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe("#formatDownloadTitle", () => {
 | 
					describe("#formatDownloadTitle", () => {
 | 
				
			||||||
    //prettier-ignore
 | 
					    //prettier-ignore
 | 
				
			||||||
    const testCases: [fnValue: Parameters<typeof utils.formatDownloadTitle>, expectedValue: ReturnType<typeof utils.formatDownloadTitle>][] = [
 | 
					    const testCases: [fnValue: Parameters<typeof utils.formatDownloadTitle>, expectedValue: ReturnType<typeof utils.formatDownloadTitle>][] = [
 | 
				
			||||||
 | 
				
			|||||||
@ -362,6 +362,11 @@ export function processStringOrBuffer(data: string | Buffer | null) {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function safeExtractMessageAndStackFromError(err: unknown) {
 | 
				
			||||||
 | 
					    return (err instanceof Error) ? [err.message, err.stack] as const : ["Unknown Error", undefined] as const;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default {
 | 
					export default {
 | 
				
			||||||
    compareVersions,
 | 
					    compareVersions,
 | 
				
			||||||
    crash,
 | 
					    crash,
 | 
				
			||||||
@ -392,6 +397,7 @@ export default {
 | 
				
			|||||||
    removeDiacritic,
 | 
					    removeDiacritic,
 | 
				
			||||||
    removeTextFileExtension,
 | 
					    removeTextFileExtension,
 | 
				
			||||||
    replaceAll,
 | 
					    replaceAll,
 | 
				
			||||||
 | 
					    safeExtractMessageAndStackFromError,
 | 
				
			||||||
    sanitizeSqlIdentifier,
 | 
					    sanitizeSqlIdentifier,
 | 
				
			||||||
    stripTags,
 | 
					    stripTags,
 | 
				
			||||||
    timeLimit,
 | 
					    timeLimit,
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user