From 569bdf19be39592ef19c00d5cd6d8df6ca064c46 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 7 Apr 2024 14:54:01 +0300 Subject: [PATCH 01/15] server-ts: Convert etapi/etapi_utils --- src/etapi/{etapi_utils.js => etapi_utils.ts} | 48 +++++++++++--------- 1 file changed, 27 insertions(+), 21 deletions(-) rename src/etapi/{etapi_utils.js => etapi_utils.ts} (63%) diff --git a/src/etapi/etapi_utils.js b/src/etapi/etapi_utils.ts similarity index 63% rename from src/etapi/etapi_utils.js rename to src/etapi/etapi_utils.ts index b5928f4d7..f7678b81f 100644 --- a/src/etapi/etapi_utils.js +++ b/src/etapi/etapi_utils.ts @@ -1,15 +1,21 @@ -const cls = require('../services/cls'); -const sql = require('../services/sql'); -const log = require('../services/log'); -const becca = require('../becca/becca'); -const etapiTokenService = require('../services/etapi_tokens'); -const config = require('../services/config'); +import cls = require('../services/cls'); +import sql = require('../services/sql'); +import log = require('../services/log'); +import becca = require('../becca/becca'); +import etapiTokenService = require('../services/etapi_tokens'); +import config = require('../services/config'); +import { NextFunction, Request, RequestHandler, Response, Router } from 'express'; const GENERIC_CODE = "GENERIC"; +type HttpMethod = "all" | "get" | "post" | "put" | "delete" | "patch" | "options" | "head"; + const noAuthentication = config.General && config.General.noAuthentication === true; class EtapiError extends Error { - constructor(statusCode, code, message) { + statusCode: number; + code: string; + + constructor(statusCode: number, code: string, message: string) { super(); this.statusCode = statusCode; @@ -18,7 +24,7 @@ class EtapiError extends Error { } } -function sendError(res, statusCode, code, message) { +function sendError(res: Response, statusCode: number, code: string, message: string) { return res .set('Content-Type', 'application/json') .status(statusCode) @@ -29,7 +35,7 @@ function sendError(res, statusCode, code, message) { })); } -function checkEtapiAuth(req, res, next) { +function checkEtapiAuth(req: Request, res: Response, next: NextFunction) { if (noAuthentication || etapiTokenService.isValidAuthHeader(req.headers.authorization)) { next(); } @@ -38,7 +44,7 @@ function checkEtapiAuth(req, res, next) { } } -function processRequest(req, res, routeHandler, next, method, path) { +function processRequest(req: Request, res: Response, routeHandler: RequestHandler, next: NextFunction, method: string, path: string) { try { cls.namespace.bindEmitter(req); cls.namespace.bindEmitter(res); @@ -51,7 +57,7 @@ function processRequest(req, res, routeHandler, next, method, path) { return sql.transactional(cb); }); - } catch (e) { + } catch (e: any) { log.error(`${method} ${path} threw exception ${e.message} with stacktrace: ${e.stack}`); if (e instanceof EtapiError) { @@ -62,15 +68,15 @@ function processRequest(req, res, routeHandler, next, method, path) { } } -function route(router, method, path, routeHandler) { - router[method](path, checkEtapiAuth, (req, res, next) => processRequest(req, res, routeHandler, next, method, path)); +function route(router: Router, method: HttpMethod, path: string, routeHandler: RequestHandler) { + router[method](path, checkEtapiAuth, (req: Request, res: Response, next: NextFunction) => processRequest(req, res, routeHandler, next, method, path)); } -function NOT_AUTHENTICATED_ROUTE(router, method, path, middleware, routeHandler) { - router[method](path, ...middleware, (req, res, next) => processRequest(req, res, routeHandler, next, method, path)); +function NOT_AUTHENTICATED_ROUTE(router: Router, method: HttpMethod, path: string, middleware: RequestHandler[], routeHandler: RequestHandler) { + router[method](path, ...middleware, (req: Request, res: Response, next: NextFunction) => processRequest(req, res, routeHandler, next, method, path)); } -function getAndCheckNote(noteId) { +function getAndCheckNote(noteId: string) { const note = becca.getNote(noteId); if (note) { @@ -81,7 +87,7 @@ function getAndCheckNote(noteId) { } } -function getAndCheckAttachment(attachmentId) { +function getAndCheckAttachment(attachmentId: string) { const attachment = becca.getAttachment(attachmentId, {includeContentLength: true}); if (attachment) { @@ -92,7 +98,7 @@ function getAndCheckAttachment(attachmentId) { } } -function getAndCheckBranch(branchId) { +function getAndCheckBranch(branchId: string) { const branch = becca.getBranch(branchId); if (branch) { @@ -103,7 +109,7 @@ function getAndCheckBranch(branchId) { } } -function getAndCheckAttribute(attributeId) { +function getAndCheckAttribute(attributeId: string) { const attribute = becca.getAttribute(attributeId); if (attribute) { @@ -114,7 +120,7 @@ function getAndCheckAttribute(attributeId) { } } -function validateAndPatch(target, source, allowedProperties) { +function validateAndPatch(target: Record, source: Record, allowedProperties: Record boolean)[]>) { for (const key of Object.keys(source)) { if (!(key in allowedProperties)) { throw new EtapiError(400, "PROPERTY_NOT_ALLOWED", `Property '${key}' is not allowed for this method.`); @@ -136,7 +142,7 @@ function validateAndPatch(target, source, allowedProperties) { } } -module.exports = { +export = { EtapiError, sendError, route, From 26859e83e4b87597b3de730e5a5cbf5302725d6e Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 7 Apr 2024 14:54:42 +0300 Subject: [PATCH 02/15] server-ts: Convert etapi/app_info --- src/etapi/app_info.js | 12 ------------ src/etapi/app_info.ts | 13 +++++++++++++ 2 files changed, 13 insertions(+), 12 deletions(-) delete mode 100644 src/etapi/app_info.js create mode 100644 src/etapi/app_info.ts diff --git a/src/etapi/app_info.js b/src/etapi/app_info.js deleted file mode 100644 index 20c1381f0..000000000 --- a/src/etapi/app_info.js +++ /dev/null @@ -1,12 +0,0 @@ -const appInfo = require('../services/app_info'); -const eu = require('./etapi_utils'); - -function register(router) { - eu.route(router, 'get', '/etapi/app-info', (req, res, next) => { - res.status(200).json(appInfo); - }); -} - -module.exports = { - register -}; diff --git a/src/etapi/app_info.ts b/src/etapi/app_info.ts new file mode 100644 index 000000000..81830fec2 --- /dev/null +++ b/src/etapi/app_info.ts @@ -0,0 +1,13 @@ +import { Router } from 'express'; +import appInfo = require('../services/app_info'); +import eu = require('./etapi_utils'); + +function register(router: Router) { + eu.route(router, 'get', '/etapi/app-info', (req, res, next) => { + res.status(200).json(appInfo); + }); +} + +export = { + register +}; From 4bb46aeb9c70cf9d6bc56632785cdabaf0ff22ba Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 7 Apr 2024 14:56:22 +0300 Subject: [PATCH 03/15] server-ts: Convert etapi/mappers --- src/etapi/attachments.js | 8 ++++---- src/etapi/attributes.js | 8 ++++---- src/etapi/branches.js | 10 +++++----- src/etapi/{mappers.js => mappers.ts} | 19 ++++++++++--------- src/etapi/notes.js | 20 ++++++++++---------- src/etapi/special_notes.js | 2 +- 6 files changed, 34 insertions(+), 33 deletions(-) rename src/etapi/{mappers.js => mappers.ts} (81%) diff --git a/src/etapi/attachments.js b/src/etapi/attachments.js index 8a3802061..e39dee81f 100644 --- a/src/etapi/attachments.js +++ b/src/etapi/attachments.js @@ -1,6 +1,6 @@ const becca = require('../becca/becca'); const eu = require('./etapi_utils'); -const mappers = require('./mappers.js'); +const mappers = require('./mappers'); const v = require('./validators.js'); const utils = require('../services/utils'); @@ -14,7 +14,7 @@ function register(router) { 'content': [v.isString], }; - eu.route(router, 'post' ,'/etapi/attachments', (req, res, next) => { + eu.route(router, 'post', '/etapi/attachments', (req, res, next) => { const params = {}; eu.validateAndPatch(params, req.body, ALLOWED_PROPERTIES_FOR_CREATE_ATTACHMENT); @@ -43,7 +43,7 @@ function register(router) { 'position': [v.notNull, v.isInteger], }; - eu.route(router, 'patch' ,'/etapi/attachments/:attachmentId', (req, res, next) => { + eu.route(router, 'patch', '/etapi/attachments/:attachmentId', (req, res, next) => { const attachment = eu.getAndCheckAttachment(req.params.attachmentId); if (attachment.isProtected) { @@ -85,7 +85,7 @@ function register(router) { return res.sendStatus(204); }); - eu.route(router, 'delete' ,'/etapi/attachments/:attachmentId', (req, res, next) => { + eu.route(router, 'delete', '/etapi/attachments/:attachmentId', (req, res, next) => { const attachment = becca.getAttachment(req.params.attachmentId); if (!attachment) { diff --git a/src/etapi/attributes.js b/src/etapi/attributes.js index 17ca00c07..db1c8f129 100644 --- a/src/etapi/attributes.js +++ b/src/etapi/attributes.js @@ -1,6 +1,6 @@ const becca = require('../becca/becca'); const eu = require('./etapi_utils'); -const mappers = require('./mappers.js'); +const mappers = require('./mappers'); const attributeService = require('../services/attributes'); const v = require('./validators.js'); @@ -21,7 +21,7 @@ function register(router) { 'position': [v.notNull, v.isInteger] }; - eu.route(router, 'post' ,'/etapi/attributes', (req, res, next) => { + eu.route(router, 'post', '/etapi/attributes', (req, res, next) => { if (req.body.type === 'relation') { eu.getAndCheckNote(req.body.value); } @@ -49,7 +49,7 @@ function register(router) { 'position': [v.notNull, v.isInteger] }; - eu.route(router, 'patch' ,'/etapi/attributes/:attributeId', (req, res, next) => { + eu.route(router, 'patch', '/etapi/attributes/:attributeId', (req, res, next) => { const attribute = eu.getAndCheckAttribute(req.params.attributeId); if (attribute.type === 'label') { @@ -65,7 +65,7 @@ function register(router) { res.json(mappers.mapAttributeToPojo(attribute)); }); - eu.route(router, 'delete' ,'/etapi/attributes/:attributeId', (req, res, next) => { + eu.route(router, 'delete', '/etapi/attributes/:attributeId', (req, res, next) => { const attribute = becca.getAttribute(req.params.attributeId); if (!attribute) { diff --git a/src/etapi/branches.js b/src/etapi/branches.js index e0337e5cb..efd8b3ae9 100644 --- a/src/etapi/branches.js +++ b/src/etapi/branches.js @@ -1,6 +1,6 @@ const becca = require('../becca/becca'); const eu = require('./etapi_utils'); -const mappers = require('./mappers.js'); +const mappers = require('./mappers'); const BBranch = require('../becca/entities/bbranch'); const entityChangesService = require('../services/entity_changes'); const v = require('./validators.js'); @@ -20,7 +20,7 @@ function register(router) { 'isExpanded': [v.notNull, v.isBoolean] }; - eu.route(router, 'post' ,'/etapi/branches', (req, res, next) => { + eu.route(router, 'post', '/etapi/branches', (req, res, next) => { const params = {}; eu.validateAndPatch(params, req.body, ALLOWED_PROPERTIES_FOR_CREATE_BRANCH); @@ -51,7 +51,7 @@ function register(router) { 'isExpanded': [v.notNull, v.isBoolean] }; - eu.route(router, 'patch' ,'/etapi/branches/:branchId', (req, res, next) => { + eu.route(router, 'patch', '/etapi/branches/:branchId', (req, res, next) => { const branch = eu.getAndCheckBranch(req.params.branchId); eu.validateAndPatch(branch, req.body, ALLOWED_PROPERTIES_FOR_PATCH); @@ -60,7 +60,7 @@ function register(router) { res.json(mappers.mapBranchToPojo(branch)); }); - eu.route(router, 'delete' ,'/etapi/branches/:branchId', (req, res, next) => { + eu.route(router, 'delete', '/etapi/branches/:branchId', (req, res, next) => { const branch = becca.getBranch(req.params.branchId); if (!branch) { @@ -72,7 +72,7 @@ function register(router) { res.sendStatus(204); }); - eu.route(router, 'post' ,'/etapi/refresh-note-ordering/:parentNoteId', (req, res, next) => { + eu.route(router, 'post', '/etapi/refresh-note-ordering/:parentNoteId', (req, res, next) => { eu.getAndCheckNote(req.params.parentNoteId); entityChangesService.putNoteReorderingEntityChange(req.params.parentNoteId, "etapi"); diff --git a/src/etapi/mappers.js b/src/etapi/mappers.ts similarity index 81% rename from src/etapi/mappers.js rename to src/etapi/mappers.ts index d94cf2e69..537b51738 100644 --- a/src/etapi/mappers.js +++ b/src/etapi/mappers.ts @@ -1,5 +1,9 @@ -/** @param {BNote} note */ -function mapNoteToPojo(note) { +import BAttachment = require("../becca/entities/battachment"); +import BAttribute = require("../becca/entities/battribute"); +import BBranch = require("../becca/entities/bbranch"); +import BNote = require("../becca/entities/bnote"); + +function mapNoteToPojo(note: BNote) { return { noteId: note.noteId, isProtected: note.isProtected, @@ -19,8 +23,7 @@ function mapNoteToPojo(note) { }; } -/** @param {BBranch} branch */ -function mapBranchToPojo(branch) { +function mapBranchToPojo(branch: BBranch) { return { branchId: branch.branchId, noteId: branch.noteId, @@ -32,8 +35,7 @@ function mapBranchToPojo(branch) { }; } -/** @param {BAttribute} attr */ -function mapAttributeToPojo(attr) { +function mapAttributeToPojo(attr: BAttribute) { return { attributeId: attr.attributeId, noteId: attr.noteId, @@ -46,8 +48,7 @@ function mapAttributeToPojo(attr) { }; } -/** @param {BAttachment} attachment */ -function mapAttachmentToPojo(attachment) { +function mapAttachmentToPojo(attachment: BAttachment) { return { attachmentId: attachment.attachmentId, ownerId: attachment.ownerId, @@ -63,7 +64,7 @@ function mapAttachmentToPojo(attachment) { }; } -module.exports = { +export = { mapNoteToPojo, mapBranchToPojo, mapAttributeToPojo, diff --git a/src/etapi/notes.js b/src/etapi/notes.js index 3ca024367..101b9c55a 100644 --- a/src/etapi/notes.js +++ b/src/etapi/notes.js @@ -1,7 +1,7 @@ const becca = require('../becca/becca'); const utils = require('../services/utils'); const eu = require('./etapi_utils'); -const mappers = require('./mappers.js'); +const mappers = require('./mappers'); const noteService = require('../services/notes'); const TaskContext = require('../services/task_context'); const v = require('./validators.js'); @@ -12,7 +12,7 @@ const zipImportService = require('../services/import/zip'); function register(router) { eu.route(router, 'get', '/etapi/notes', (req, res, next) => { - const {search} = req.query; + const { search } = req.query; if (!search?.trim()) { throw new eu.EtapiError(400, 'SEARCH_QUERY_PARAM_MANDATORY', "'search' query parameter is mandatory."); @@ -55,7 +55,7 @@ function register(router) { 'utcDateCreated': [v.notNull, v.isString, v.isUtcDateTime] }; - eu.route(router, 'post' ,'/etapi/create-note', (req, res, next) => { + eu.route(router, 'post', '/etapi/create-note', (req, res, next) => { const params = {}; eu.validateAndPatch(params, req.body, ALLOWED_PROPERTIES_FOR_CREATE_NOTE); @@ -81,7 +81,7 @@ function register(router) { 'utcDateCreated': [v.notNull, v.isString, v.isUtcDateTime] }; - eu.route(router, 'patch' ,'/etapi/notes/:noteId', (req, res, next) => { + eu.route(router, 'patch', '/etapi/notes/:noteId', (req, res, next) => { const note = eu.getAndCheckNote(req.params.noteId); if (note.isProtected) { @@ -94,8 +94,8 @@ function register(router) { res.json(mappers.mapNoteToPojo(note)); }); - eu.route(router, 'delete' ,'/etapi/notes/:noteId', (req, res, next) => { - const {noteId} = req.params; + eu.route(router, 'delete', '/etapi/notes/:noteId', (req, res, next) => { + const { noteId } = req.params; const note = becca.getNote(noteId); @@ -139,7 +139,7 @@ function register(router) { return res.sendStatus(204); }); - eu.route(router, 'get' ,'/etapi/notes/:noteId/export', (req, res, next) => { + eu.route(router, 'get', '/etapi/notes/:noteId/export', (req, res, next) => { const note = eu.getAndCheckNote(req.params.noteId); const format = req.query.format || "html"; @@ -156,7 +156,7 @@ function register(router) { zipExportService.exportToZip(taskContext, branch, format, res); }); - eu.route(router, 'post' ,'/etapi/notes/:noteId/import', (req, res, next) => { + eu.route(router, 'post', '/etapi/notes/:noteId/import', (req, res, next) => { const note = eu.getAndCheckNote(req.params.noteId); const taskContext = new TaskContext('no-progress-reporting'); @@ -168,7 +168,7 @@ function register(router) { }); // we need better error handling here, async errors won't be properly processed. }); - eu.route(router, 'post' ,'/etapi/notes/:noteId/revision', (req, res, next) => { + eu.route(router, 'post', '/etapi/notes/:noteId/revision', (req, res, next) => { const note = eu.getAndCheckNote(req.params.noteId); note.saveRevision(); @@ -178,7 +178,7 @@ function register(router) { eu.route(router, 'get', '/etapi/notes/:noteId/attachments', (req, res, next) => { const note = eu.getAndCheckNote(req.params.noteId); - const attachments = note.getAttachments({includeContentLength: true}) + const attachments = note.getAttachments({ includeContentLength: true }) res.json( attachments.map(attachment => mappers.mapAttachmentToPojo(attachment)) diff --git a/src/etapi/special_notes.js b/src/etapi/special_notes.js index 68ef75fae..af771a724 100644 --- a/src/etapi/special_notes.js +++ b/src/etapi/special_notes.js @@ -1,7 +1,7 @@ const specialNotesService = require('../services/special_notes'); const dateNotesService = require('../services/date_notes'); const eu = require('./etapi_utils'); -const mappers = require('./mappers.js'); +const mappers = require('./mappers'); const getDateInvalidError = date => new eu.EtapiError(400, "DATE_INVALID", `Date "${date}" is not valid.`); const getMonthInvalidError = month => new eu.EtapiError(400, "MONTH_INVALID", `Month "${month}" is not valid.`); From 3bd7231ba94c933aeedd7f2304b19ffc85bf122a Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 7 Apr 2024 14:59:40 +0300 Subject: [PATCH 04/15] server-ts: Convert etapi/validators --- src/etapi/attachments.js | 2 +- src/etapi/attributes.js | 2 +- src/etapi/branches.js | 2 +- src/etapi/notes.js | 2 +- src/etapi/{validators.js => validators.ts} | 38 +++++++++++----------- 5 files changed, 23 insertions(+), 23 deletions(-) rename src/etapi/{validators.js => validators.ts} (64%) diff --git a/src/etapi/attachments.js b/src/etapi/attachments.js index e39dee81f..6ea042db1 100644 --- a/src/etapi/attachments.js +++ b/src/etapi/attachments.js @@ -1,7 +1,7 @@ const becca = require('../becca/becca'); const eu = require('./etapi_utils'); const mappers = require('./mappers'); -const v = require('./validators.js'); +const v = require('./validators'); const utils = require('../services/utils'); function register(router) { diff --git a/src/etapi/attributes.js b/src/etapi/attributes.js index db1c8f129..5a7fb1e53 100644 --- a/src/etapi/attributes.js +++ b/src/etapi/attributes.js @@ -2,7 +2,7 @@ const becca = require('../becca/becca'); const eu = require('./etapi_utils'); const mappers = require('./mappers'); const attributeService = require('../services/attributes'); -const v = require('./validators.js'); +const v = require('./validators'); function register(router) { eu.route(router, 'get', '/etapi/attributes/:attributeId', (req, res, next) => { diff --git a/src/etapi/branches.js b/src/etapi/branches.js index efd8b3ae9..9408b84e1 100644 --- a/src/etapi/branches.js +++ b/src/etapi/branches.js @@ -3,7 +3,7 @@ const eu = require('./etapi_utils'); const mappers = require('./mappers'); const BBranch = require('../becca/entities/bbranch'); const entityChangesService = require('../services/entity_changes'); -const v = require('./validators.js'); +const v = require('./validators'); function register(router) { eu.route(router, 'get', '/etapi/branches/:branchId', (req, res, next) => { diff --git a/src/etapi/notes.js b/src/etapi/notes.js index 101b9c55a..71485388e 100644 --- a/src/etapi/notes.js +++ b/src/etapi/notes.js @@ -4,7 +4,7 @@ const eu = require('./etapi_utils'); const mappers = require('./mappers'); const noteService = require('../services/notes'); const TaskContext = require('../services/task_context'); -const v = require('./validators.js'); +const v = require('./validators'); const searchService = require('../services/search/services/search'); const SearchContext = require('../services/search/search_context'); const zipExportService = require('../services/export/zip'); diff --git a/src/etapi/validators.js b/src/etapi/validators.ts similarity index 64% rename from src/etapi/validators.js rename to src/etapi/validators.ts index bcca288ca..7119a5319 100644 --- a/src/etapi/validators.js +++ b/src/etapi/validators.ts @@ -1,19 +1,19 @@ -const noteTypeService = require('../services/note_types'); -const dateUtils = require('../services/date_utils'); +import noteTypeService = require('../services/note_types'); +import dateUtils = require('../services/date_utils'); -function mandatory(obj) { - if (obj === undefined ) { +function mandatory(obj: any | undefined) { + if (obj === undefined) { return `mandatory, but not set`; } } -function notNull(obj) { +function notNull(obj: any | null) { if (obj === null) { return `cannot be null`; } } -function isString(obj) { +function isString(obj: object | undefined | null) { if (obj === undefined || obj === null) { return; } @@ -23,23 +23,23 @@ function isString(obj) { } } -function isLocalDateTime(obj) { - if (obj === undefined || obj === null) { +function isLocalDateTime(obj: object | undefined | null) { + if (obj === undefined || obj === null || typeof obj !== "string") { return; } return dateUtils.validateLocalDateTime(obj); } -function isUtcDateTime(obj) { - if (obj === undefined || obj === null) { +function isUtcDateTime(obj: object | undefined | null) { + if (obj === undefined || obj === null || typeof obj !== "string") { return; } return dateUtils.validateUtcDateTime(obj); } -function isBoolean(obj) { +function isBoolean(obj: object | undefined | null) { if (obj === undefined || obj === null) { return; } @@ -49,7 +49,7 @@ function isBoolean(obj) { } } -function isInteger(obj) { +function isInteger(obj: object | undefined | null) { if (obj === undefined || obj === null) { return; } @@ -59,7 +59,7 @@ function isInteger(obj) { } } -function isNoteId(obj) { +function isNoteId(obj: object | undefined | null) { if (obj === undefined || obj === null) { return; } @@ -75,29 +75,29 @@ function isNoteId(obj) { } } -function isNoteType(obj) { +function isNoteType(obj: object | undefined | null) { if (obj === undefined || obj === null) { return; } const noteTypes = noteTypeService.getNoteTypeNames(); - if (!noteTypes.includes(obj)) { + if (typeof obj !== "string" || !noteTypes.includes(obj)) { return `'${obj}' is not a valid note type, allowed types are: ${noteTypes.join(", ")}`; } } -function isAttributeType(obj) { +function isAttributeType(obj: object | undefined | null) { if (obj === undefined || obj === null) { return; } - if (!['label', 'relation'].includes(obj)) { + if (typeof obj !== "string" || !['label', 'relation'].includes(obj)) { return `'${obj}' is not a valid attribute type, allowed types are: label, relation`; } } -function isValidEntityId(obj) { +function isValidEntityId(obj: object | undefined | null) { if (obj === undefined || obj === null) { return; } @@ -107,7 +107,7 @@ function isValidEntityId(obj) { } } -module.exports = { +export = { mandatory, notNull, isString, From a6de065bf41e46f52863593dbcd016a6a52fbce1 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 7 Apr 2024 15:13:34 +0300 Subject: [PATCH 05/15] server-ts: Convert etapi/attachments --- src/etapi/{attachments.js => attachments.ts} | 27 ++++++++++++-------- src/etapi/etapi-interface.ts | 5 ++++ src/etapi/etapi_utils.ts | 2 +- src/etapi/validators.ts | 22 ++++++++-------- src/routes/routes.js | 2 +- 5 files changed, 34 insertions(+), 24 deletions(-) rename src/etapi/{attachments.js => attachments.ts} (81%) create mode 100644 src/etapi/etapi-interface.ts diff --git a/src/etapi/attachments.js b/src/etapi/attachments.ts similarity index 81% rename from src/etapi/attachments.js rename to src/etapi/attachments.ts index 6ea042db1..258b9a186 100644 --- a/src/etapi/attachments.js +++ b/src/etapi/attachments.ts @@ -1,11 +1,13 @@ -const becca = require('../becca/becca'); -const eu = require('./etapi_utils'); -const mappers = require('./mappers'); -const v = require('./validators'); -const utils = require('../services/utils'); +import becca = require('../becca/becca'); +import eu = require('./etapi_utils'); +import mappers = require('./mappers'); +import v = require('./validators'); +import utils = require('../services/utils'); +import { Router } from 'express'; +import { AttachmentRow } from '../becca/entities/rows'; -function register(router) { - const ALLOWED_PROPERTIES_FOR_CREATE_ATTACHMENT = { +function register(router: Router) { + const ALLOWED_PROPERTIES_FOR_CREATE_ATTACHMENT: ValidatorMap = { 'ownerId': [v.notNull, v.isNoteId], 'role': [v.notNull, v.isString], 'mime': [v.notNull, v.isString], @@ -15,17 +17,20 @@ function register(router) { }; eu.route(router, 'post', '/etapi/attachments', (req, res, next) => { - const params = {}; - - eu.validateAndPatch(params, req.body, ALLOWED_PROPERTIES_FOR_CREATE_ATTACHMENT); + const _params: Partial = {}; + eu.validateAndPatch(_params, req.body, ALLOWED_PROPERTIES_FOR_CREATE_ATTACHMENT); + const params = _params as AttachmentRow; try { + if (!params.ownerId) { + throw new Error("Missing owner ID."); + } const note = becca.getNoteOrThrow(params.ownerId); const attachment = note.saveAttachment(params); res.status(201).json(mappers.mapAttachmentToPojo(attachment)); } - catch (e) { + catch (e: any) { throw new eu.EtapiError(500, eu.GENERIC_CODE, e.message); } }); diff --git a/src/etapi/etapi-interface.ts b/src/etapi/etapi-interface.ts new file mode 100644 index 000000000..b53d983dd --- /dev/null +++ b/src/etapi/etapi-interface.ts @@ -0,0 +1,5 @@ +type ValidatorArg = object | undefined | null; + +type ValidatorFunc = (obj: ValidatorArg) => (string | undefined); + +type ValidatorMap = Record; \ No newline at end of file diff --git a/src/etapi/etapi_utils.ts b/src/etapi/etapi_utils.ts index f7678b81f..4880bed86 100644 --- a/src/etapi/etapi_utils.ts +++ b/src/etapi/etapi_utils.ts @@ -120,7 +120,7 @@ function getAndCheckAttribute(attributeId: string) { } } -function validateAndPatch(target: Record, source: Record, allowedProperties: Record boolean)[]>) { +function validateAndPatch(target: any, source: any, allowedProperties: ValidatorMap) { for (const key of Object.keys(source)) { if (!(key in allowedProperties)) { throw new EtapiError(400, "PROPERTY_NOT_ALLOWED", `Property '${key}' is not allowed for this method.`); diff --git a/src/etapi/validators.ts b/src/etapi/validators.ts index 7119a5319..485bd8d33 100644 --- a/src/etapi/validators.ts +++ b/src/etapi/validators.ts @@ -1,19 +1,19 @@ import noteTypeService = require('../services/note_types'); import dateUtils = require('../services/date_utils'); -function mandatory(obj: any | undefined) { +function mandatory(obj: ValidatorArg) { if (obj === undefined) { return `mandatory, but not set`; } } -function notNull(obj: any | null) { +function notNull(obj: ValidatorArg) { if (obj === null) { return `cannot be null`; } } -function isString(obj: object | undefined | null) { +function isString(obj: ValidatorArg) { if (obj === undefined || obj === null) { return; } @@ -23,7 +23,7 @@ function isString(obj: object | undefined | null) { } } -function isLocalDateTime(obj: object | undefined | null) { +function isLocalDateTime(obj: ValidatorArg) { if (obj === undefined || obj === null || typeof obj !== "string") { return; } @@ -31,7 +31,7 @@ function isLocalDateTime(obj: object | undefined | null) { return dateUtils.validateLocalDateTime(obj); } -function isUtcDateTime(obj: object | undefined | null) { +function isUtcDateTime(obj: ValidatorArg) { if (obj === undefined || obj === null || typeof obj !== "string") { return; } @@ -39,7 +39,7 @@ function isUtcDateTime(obj: object | undefined | null) { return dateUtils.validateUtcDateTime(obj); } -function isBoolean(obj: object | undefined | null) { +function isBoolean(obj: ValidatorArg) { if (obj === undefined || obj === null) { return; } @@ -49,7 +49,7 @@ function isBoolean(obj: object | undefined | null) { } } -function isInteger(obj: object | undefined | null) { +function isInteger(obj: ValidatorArg) { if (obj === undefined || obj === null) { return; } @@ -59,7 +59,7 @@ function isInteger(obj: object | undefined | null) { } } -function isNoteId(obj: object | undefined | null) { +function isNoteId(obj: ValidatorArg) { if (obj === undefined || obj === null) { return; } @@ -75,7 +75,7 @@ function isNoteId(obj: object | undefined | null) { } } -function isNoteType(obj: object | undefined | null) { +function isNoteType(obj: ValidatorArg) { if (obj === undefined || obj === null) { return; } @@ -87,7 +87,7 @@ function isNoteType(obj: object | undefined | null) { } } -function isAttributeType(obj: object | undefined | null) { +function isAttributeType(obj: ValidatorArg) { if (obj === undefined || obj === null) { return; } @@ -97,7 +97,7 @@ function isAttributeType(obj: object | undefined | null) { } } -function isValidEntityId(obj: object | undefined | null) { +function isValidEntityId(obj: ValidatorArg) { if (obj === undefined || obj === null) { return; } diff --git a/src/routes/routes.js b/src/routes/routes.js index 182639b84..a264f3c5b 100644 --- a/src/routes/routes.js +++ b/src/routes/routes.js @@ -63,7 +63,7 @@ const shareRoutes = require('../share/routes.js'); const etapiAuthRoutes = require('../etapi/auth.js'); const etapiAppInfoRoutes = require('../etapi/app_info'); -const etapiAttachmentRoutes = require('../etapi/attachments.js'); +const etapiAttachmentRoutes = require('../etapi/attachments'); const etapiAttributeRoutes = require('../etapi/attributes'); const etapiBranchRoutes = require('../etapi/branches.js'); const etapiNoteRoutes = require('../etapi/notes.js'); From 1e2a30adcc76696d3cc8bfe88c6aab314d201400 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 7 Apr 2024 15:15:50 +0300 Subject: [PATCH 06/15] server-ts: Convert etapi/attributes --- src/etapi/{attributes.js => attributes.ts} | 26 ++++++++++++---------- 1 file changed, 14 insertions(+), 12 deletions(-) rename src/etapi/{attributes.js => attributes.ts} (77%) diff --git a/src/etapi/attributes.js b/src/etapi/attributes.ts similarity index 77% rename from src/etapi/attributes.js rename to src/etapi/attributes.ts index 5a7fb1e53..d5b407fb3 100644 --- a/src/etapi/attributes.js +++ b/src/etapi/attributes.ts @@ -1,17 +1,19 @@ -const becca = require('../becca/becca'); -const eu = require('./etapi_utils'); -const mappers = require('./mappers'); -const attributeService = require('../services/attributes'); -const v = require('./validators'); +import becca = require('../becca/becca'); +import eu = require('./etapi_utils'); +import mappers = require('./mappers'); +import attributeService = require('../services/attributes'); +import v = require('./validators'); +import { Router } from 'express'; +import { AttributeRow } from '../becca/entities/rows'; -function register(router) { +function register(router: Router) { eu.route(router, 'get', '/etapi/attributes/:attributeId', (req, res, next) => { const attribute = eu.getAndCheckAttribute(req.params.attributeId); res.json(mappers.mapAttributeToPojo(attribute)); }); - const ALLOWED_PROPERTIES_FOR_CREATE_ATTRIBUTE = { + const ALLOWED_PROPERTIES_FOR_CREATE_ATTRIBUTE: ValidatorMap = { 'attributeId': [v.mandatory, v.notNull, v.isValidEntityId], 'noteId': [v.mandatory, v.notNull, v.isNoteId], 'type': [v.mandatory, v.notNull, v.isAttributeType], @@ -26,16 +28,16 @@ function register(router) { eu.getAndCheckNote(req.body.value); } - const params = {}; - - eu.validateAndPatch(params, req.body, ALLOWED_PROPERTIES_FOR_CREATE_ATTRIBUTE); + const _params = {}; + eu.validateAndPatch(_params, req.body, ALLOWED_PROPERTIES_FOR_CREATE_ATTRIBUTE); + const params: AttributeRow = _params as AttributeRow; try { const attr = attributeService.createAttribute(params); res.status(201).json(mappers.mapAttributeToPojo(attr)); } - catch (e) { + catch (e: any) { throw new eu.EtapiError(500, eu.GENERIC_CODE, e.message); } }); @@ -78,6 +80,6 @@ function register(router) { }); } -module.exports = { +export = { register }; From 071f9400d7327e24643db4c1a49f33214605e89a Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 7 Apr 2024 15:17:45 +0300 Subject: [PATCH 07/15] server-ts: Convert etapi/auth --- src/etapi/{auth.js => auth.ts} | 13 +++++++------ src/routes/routes.js | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) rename src/etapi/{auth.js => auth.ts} (75%) diff --git a/src/etapi/auth.js b/src/etapi/auth.ts similarity index 75% rename from src/etapi/auth.js rename to src/etapi/auth.ts index 7a3b258d8..e6c440e52 100644 --- a/src/etapi/auth.js +++ b/src/etapi/auth.ts @@ -1,9 +1,10 @@ -const becca = require('../becca/becca'); -const eu = require('./etapi_utils'); -const passwordEncryptionService = require('../services/encryption/password_encryption'); -const etapiTokenService = require('../services/etapi_tokens'); +import becca = require('../becca/becca'); +import eu = require('./etapi_utils'); +import passwordEncryptionService = require('../services/encryption/password_encryption'); +import etapiTokenService = require('../services/etapi_tokens'); +import { RequestHandler, Router } from 'express'; -function register(router, loginMiddleware) { +function register(router: Router, loginMiddleware: RequestHandler[]) { eu.NOT_AUTHENTICATED_ROUTE(router, 'post', '/etapi/auth/login', loginMiddleware, (req, res, next) => { const {password, tokenName} = req.body; @@ -38,6 +39,6 @@ function register(router, loginMiddleware) { }); } -module.exports = { +export = { register } diff --git a/src/routes/routes.js b/src/routes/routes.js index a264f3c5b..4b6706e58 100644 --- a/src/routes/routes.js +++ b/src/routes/routes.js @@ -61,7 +61,7 @@ const relationMapApiRoute = require('./api/relation-map'); const otherRoute = require('./api/other'); const shareRoutes = require('../share/routes.js'); -const etapiAuthRoutes = require('../etapi/auth.js'); +const etapiAuthRoutes = require('../etapi/auth'); const etapiAppInfoRoutes = require('../etapi/app_info'); const etapiAttachmentRoutes = require('../etapi/attachments'); const etapiAttributeRoutes = require('../etapi/attributes'); From 602b4988aec55017fd5082ea7aa49b3b1f39e5e6 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 7 Apr 2024 15:18:35 +0300 Subject: [PATCH 08/15] server-ts: Convert etapi/backup --- src/etapi/{backup.js => backup.ts} | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) rename src/etapi/{backup.js => backup.ts} (54%) diff --git a/src/etapi/backup.js b/src/etapi/backup.ts similarity index 54% rename from src/etapi/backup.js rename to src/etapi/backup.ts index 7900570c4..50c2bd49c 100644 --- a/src/etapi/backup.js +++ b/src/etapi/backup.ts @@ -1,7 +1,9 @@ -const eu = require('./etapi_utils'); -const backupService = require('../services/backup'); +import { Router } from "express"; -function register(router) { +import eu = require('./etapi_utils'); +import backupService = require('../services/backup'); + +function register(router: Router) { eu.route(router, 'put', '/etapi/backup/:backupName', async (req, res, next) => { await backupService.backupNow(req.params.backupName); @@ -9,6 +11,6 @@ function register(router) { }); } -module.exports = { +export = { register }; From e4024408bde5936e34d38b6a2445d4865e8c362f Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 7 Apr 2024 15:21:42 +0300 Subject: [PATCH 09/15] server-ts: Convert etapi/bbranches --- src/becca/entities/bbranch.ts | 2 +- src/etapi/{branches.js => branches.ts} | 33 ++++++++++++++------------ src/routes/routes.js | 2 +- 3 files changed, 20 insertions(+), 17 deletions(-) rename src/etapi/{branches.js => branches.ts} (73%) diff --git a/src/becca/entities/bbranch.ts b/src/becca/entities/bbranch.ts index 677bc75ef..2b6dc8657 100644 --- a/src/becca/entities/bbranch.ts +++ b/src/becca/entities/bbranch.ts @@ -141,7 +141,7 @@ class BBranch extends AbstractBeccaEntity { * * @returns true if note has been deleted, false otherwise */ - deleteBranch(deleteId: string, taskContext: TaskContext): boolean { + deleteBranch(deleteId?: string, taskContext?: TaskContext): boolean { if (!deleteId) { deleteId = utils.randomString(10); } diff --git a/src/etapi/branches.js b/src/etapi/branches.ts similarity index 73% rename from src/etapi/branches.js rename to src/etapi/branches.ts index 9408b84e1..d41a5857b 100644 --- a/src/etapi/branches.js +++ b/src/etapi/branches.ts @@ -1,11 +1,14 @@ -const becca = require('../becca/becca'); -const eu = require('./etapi_utils'); -const mappers = require('./mappers'); -const BBranch = require('../becca/entities/bbranch'); -const entityChangesService = require('../services/entity_changes'); -const v = require('./validators'); +import { Router } from "express"; -function register(router) { +import becca = require('../becca/becca'); +import eu = require('./etapi_utils'); +import mappers = require('./mappers'); +import BBranch = require('../becca/entities/bbranch'); +import entityChangesService = require('../services/entity_changes'); +import v = require('./validators'); +import { BranchRow } from "../becca/entities/rows"; + +function register(router: Router) { eu.route(router, 'get', '/etapi/branches/:branchId', (req, res, next) => { const branch = eu.getAndCheckBranch(req.params.branchId); @@ -21,16 +24,16 @@ function register(router) { }; eu.route(router, 'post', '/etapi/branches', (req, res, next) => { - const params = {}; - - eu.validateAndPatch(params, req.body, ALLOWED_PROPERTIES_FOR_CREATE_BRANCH); + const _params = {}; + eu.validateAndPatch(_params, req.body, ALLOWED_PROPERTIES_FOR_CREATE_BRANCH); + const params: BranchRow = _params as BranchRow; const existing = becca.getBranchFromChildAndParent(params.noteId, params.parentNoteId); if (existing) { - existing.notePosition = params.notePosition; - existing.prefix = params.prefix; - existing.isExpanded = params.isExpanded; + existing.notePosition = params.notePosition as number; + existing.prefix = params.prefix as string; + existing.isExpanded = params.isExpanded as boolean; existing.save(); return res.status(200).json(mappers.mapBranchToPojo(existing)); @@ -39,7 +42,7 @@ function register(router) { const branch = new BBranch(params).save(); res.status(201).json(mappers.mapBranchToPojo(branch)); - } catch (e) { + } catch (e: any) { throw new eu.EtapiError(400, eu.GENERIC_CODE, e.message); } } @@ -81,6 +84,6 @@ function register(router) { }); } -module.exports = { +export = { register }; diff --git a/src/routes/routes.js b/src/routes/routes.js index 4b6706e58..efa8afb8c 100644 --- a/src/routes/routes.js +++ b/src/routes/routes.js @@ -65,7 +65,7 @@ const etapiAuthRoutes = require('../etapi/auth'); const etapiAppInfoRoutes = require('../etapi/app_info'); const etapiAttachmentRoutes = require('../etapi/attachments'); const etapiAttributeRoutes = require('../etapi/attributes'); -const etapiBranchRoutes = require('../etapi/branches.js'); +const etapiBranchRoutes = require('../etapi/branches'); const etapiNoteRoutes = require('../etapi/notes.js'); const etapiSpecialNoteRoutes = require('../etapi/special_notes'); const etapiSpecRoute = require('../etapi/spec.js'); From 5fdf094e9d11007faed5cf3fa2dc1bbdc2fd85ec Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 7 Apr 2024 16:56:45 +0300 Subject: [PATCH 10/15] server-ts: Convert etapi/notes --- src/etapi/etapi_utils.ts | 7 +-- src/etapi/{notes.js => notes.ts} | 89 ++++++++++++++++++-------------- src/routes/api/files.ts | 13 +++++ src/routes/api/image.ts | 7 +++ src/routes/api/sender.ts | 7 +++ src/routes/login.ts | 2 +- src/routes/route-interface.ts | 23 +++++---- src/routes/routes.js | 2 +- 8 files changed, 95 insertions(+), 55 deletions(-) rename src/etapi/{notes.js => notes.ts} (76%) diff --git a/src/etapi/etapi_utils.ts b/src/etapi/etapi_utils.ts index 4880bed86..bcb589325 100644 --- a/src/etapi/etapi_utils.ts +++ b/src/etapi/etapi_utils.ts @@ -5,6 +5,7 @@ import becca = require('../becca/becca'); import etapiTokenService = require('../services/etapi_tokens'); import config = require('../services/config'); import { NextFunction, Request, RequestHandler, Response, Router } from 'express'; +import { AppRequest, AppRequestHandler } from '../routes/route-interface'; const GENERIC_CODE = "GENERIC"; type HttpMethod = "all" | "get" | "post" | "put" | "delete" | "patch" | "options" | "head"; @@ -44,7 +45,7 @@ function checkEtapiAuth(req: Request, res: Response, next: NextFunction) { } } -function processRequest(req: Request, res: Response, routeHandler: RequestHandler, next: NextFunction, method: string, path: string) { +function processRequest(req: Request, res: Response, routeHandler: AppRequestHandler, next: NextFunction, method: string, path: string) { try { cls.namespace.bindEmitter(req); cls.namespace.bindEmitter(res); @@ -53,7 +54,7 @@ function processRequest(req: Request, res: Response, routeHandler: RequestHandle cls.set('componentId', "etapi"); cls.set('localNowDateTime', req.headers['trilium-local-now-datetime']); - const cb = () => routeHandler(req, res, next); + const cb = () => routeHandler(req as AppRequest, res, next); return sql.transactional(cb); }); @@ -68,7 +69,7 @@ function processRequest(req: Request, res: Response, routeHandler: RequestHandle } } -function route(router: Router, method: HttpMethod, path: string, routeHandler: RequestHandler) { +function route(router: Router, method: HttpMethod, path: string, routeHandler: AppRequestHandler) { router[method](path, checkEtapiAuth, (req: Request, res: Response, next: NextFunction) => processRequest(req, res, routeHandler, next, method, path)); } diff --git a/src/etapi/notes.js b/src/etapi/notes.ts similarity index 76% rename from src/etapi/notes.js rename to src/etapi/notes.ts index 71485388e..fcb2d314f 100644 --- a/src/etapi/notes.js +++ b/src/etapi/notes.ts @@ -1,20 +1,26 @@ -const becca = require('../becca/becca'); -const utils = require('../services/utils'); -const eu = require('./etapi_utils'); -const mappers = require('./mappers'); -const noteService = require('../services/notes'); -const TaskContext = require('../services/task_context'); -const v = require('./validators'); -const searchService = require('../services/search/services/search'); -const SearchContext = require('../services/search/search_context'); -const zipExportService = require('../services/export/zip'); -const zipImportService = require('../services/import/zip'); +import becca = require('../becca/becca'); +import utils = require('../services/utils'); +import eu = require('./etapi_utils'); +import mappers = require('./mappers'); +import noteService = require('../services/notes'); +import TaskContext = require('../services/task_context'); +import v = require('./validators'); +import searchService = require('../services/search/services/search'); +import SearchContext = require('../services/search/search_context'); +import zipExportService = require('../services/export/zip'); +import zipImportService = require('../services/import/zip'); +import { Router } from 'express'; +import { AppRequest } from '../routes/route-interface'; +import { ParsedQs } from 'qs'; +import { NoteParams } from '../services/note-interface'; +import BNote = require('../becca/entities/bnote'); +import { SearchParams } from '../services/search/services/types'; -function register(router) { +function register(router: Router) { eu.route(router, 'get', '/etapi/notes', (req, res, next) => { const { search } = req.query; - if (!search?.trim()) { + if (typeof search !== "string" || !search?.trim()) { throw new eu.EtapiError(400, 'SEARCH_QUERY_PARAM_MANDATORY', "'search' query parameter is mandatory."); } @@ -24,8 +30,8 @@ function register(router) { const searchResults = searchService.findResultsWithQuery(search, searchContext); const foundNotes = searchResults.map(sr => becca.notes[sr.noteId]); - const resp = { - results: foundNotes.map(note => mappers.mapNoteToPojo(note)) + const resp: any = { + results: foundNotes.map(note => mappers.mapNoteToPojo(note)), }; if (searchContext.debugInfo) { @@ -41,7 +47,7 @@ function register(router) { res.json(mappers.mapNoteToPojo(note)); }); - const ALLOWED_PROPERTIES_FOR_CREATE_NOTE = { + const ALLOWED_PROPERTIES_FOR_CREATE_NOTE: ValidatorMap = { 'parentNoteId': [v.mandatory, v.notNull, v.isNoteId], 'title': [v.mandatory, v.notNull, v.isString], 'type': [v.mandatory, v.notNull, v.isNoteType], @@ -56,9 +62,9 @@ function register(router) { }; eu.route(router, 'post', '/etapi/create-note', (req, res, next) => { - const params = {}; - - eu.validateAndPatch(params, req.body, ALLOWED_PROPERTIES_FOR_CREATE_NOTE); + const _params = {}; + eu.validateAndPatch(_params, req.body, ALLOWED_PROPERTIES_FOR_CREATE_NOTE); + const params = _params as NoteParams; try { const resp = noteService.createNewNote(params); @@ -68,7 +74,7 @@ function register(router) { branch: mappers.mapBranchToPojo(resp.branch) }); } - catch (e) { + catch (e: any) { return eu.sendError(res, 500, eu.GENERIC_CODE, e.message); } }); @@ -143,7 +149,7 @@ function register(router) { const note = eu.getAndCheckNote(req.params.noteId); const format = req.query.format || "html"; - if (!["html", "markdown"].includes(format)) { + if (typeof format !== "string" || !["html", "markdown"].includes(format)) { throw new eu.EtapiError(400, "UNRECOGNIZED_EXPORT_FORMAT", `Unrecognized export format '${format}', supported values are 'html' (default) or 'markdown'.`); } @@ -153,7 +159,7 @@ function register(router) { // (e.g. branchIds are not seen in UI), that we export "note export" instead. const branch = note.getParentBranches()[0]; - zipExportService.exportToZip(taskContext, branch, format, res); + zipExportService.exportToZip(taskContext, branch, format as "html" | "markdown", res); }); eu.route(router, 'post', '/etapi/notes/:noteId/import', (req, res, next) => { @@ -186,23 +192,24 @@ function register(router) { }); } -function parseSearchParams(req) { - const rawSearchParams = { +function parseSearchParams(req: AppRequest) { + const rawSearchParams: SearchParams = { fastSearch: parseBoolean(req.query, 'fastSearch'), includeArchivedNotes: parseBoolean(req.query, 'includeArchivedNotes'), - ancestorNoteId: req.query['ancestorNoteId'], - ancestorDepth: req.query['ancestorDepth'], // e.g. "eq5" - orderBy: req.query['orderBy'], - orderDirection: parseOrderDirection(req.query, 'orderDirection'), + ancestorNoteId: parseString(req.query['ancestorNoteId']), + ancestorDepth: parseString(req.query['ancestorDepth']), // e.g. "eq5" + orderBy: parseString(req.query['orderBy']), + // TODO: Check why the order direction was provided as a number, but it's a string everywhere else. + orderDirection: parseOrderDirection(req.query, 'orderDirection') as unknown as string, limit: parseInteger(req.query, 'limit'), debug: parseBoolean(req.query, 'debug') }; - const searchParams = {}; + const searchParams: SearchParams = {}; - for (const paramName of Object.keys(rawSearchParams)) { + for (const paramName of Object.keys(rawSearchParams) as (keyof SearchParams)[]) { if (rawSearchParams[paramName] !== undefined) { - searchParams[paramName] = rawSearchParams[paramName]; + (searchParams as any)[paramName] = rawSearchParams[paramName]; } } @@ -211,7 +218,15 @@ function parseSearchParams(req) { const SEARCH_PARAM_ERROR = "SEARCH_PARAM_VALIDATION_ERROR"; -function parseBoolean(obj, name) { +function parseString(value: string | ParsedQs | string[] | ParsedQs[] | undefined): string | undefined { + if (typeof value === "string") { + return value; + } + + return undefined; +} + +function parseBoolean(obj: any, name: string) { if (!(name in obj)) { return undefined; } @@ -223,11 +238,7 @@ function parseBoolean(obj, name) { return obj[name] === 'true'; } -function parseOrderDirection(obj, name) { - if (!(name in obj)) { - return undefined; - } - +function parseOrderDirection(obj: any, name: string) { const integer = parseInt(obj[name]); if (!['asc', 'desc'].includes(obj[name])) { @@ -237,7 +248,7 @@ function parseOrderDirection(obj, name) { return integer; } -function parseInteger(obj, name) { +function parseInteger(obj: any, name: string) { if (!(name in obj)) { return undefined; } @@ -251,6 +262,6 @@ function parseInteger(obj, name) { return integer; } -module.exports = { +export = { register }; diff --git a/src/routes/api/files.ts b/src/routes/api/files.ts index 44cd1dacf..682bc94e4 100644 --- a/src/routes/api/files.ts +++ b/src/routes/api/files.ts @@ -20,6 +20,13 @@ function updateFile(req: AppRequest) { const note = becca.getNoteOrThrow(req.params.noteId); const file = req.file; + if (!file) { + return { + uploaded: false, + message: `Missing file.` + }; + } + note.saveRevision(); note.mime = file.mimetype.toLowerCase(); @@ -39,6 +46,12 @@ function updateFile(req: AppRequest) { function updateAttachment(req: AppRequest) { const attachment = becca.getAttachmentOrThrow(req.params.attachmentId); const file = req.file; + if (!file) { + return { + uploaded: false, + message: `Missing file.` + }; + } attachment.getNote().saveRevision(); diff --git a/src/routes/api/image.ts b/src/routes/api/image.ts index a0ee6140c..b00c7fd7f 100644 --- a/src/routes/api/image.ts +++ b/src/routes/api/image.ts @@ -88,6 +88,13 @@ function updateImage(req: AppRequest) { const note = becca.getNoteOrThrow(noteId); + if (!file) { + return { + uploaded: false, + message: `Missing image data.` + }; + } + if (!["image/png", "image/jpeg", "image/gif", "image/webp", "image/svg+xml"].includes(file.mimetype)) { return { uploaded: false, diff --git a/src/routes/api/sender.ts b/src/routes/api/sender.ts index 515011739..8d20c1fd9 100644 --- a/src/routes/api/sender.ts +++ b/src/routes/api/sender.ts @@ -11,6 +11,13 @@ import { AppRequest } from '../route-interface'; function uploadImage(req: AppRequest) { const file = req.file; + if (!file) { + return { + uploaded: false, + message: `Missing image data.` + }; + } + if (!["image/png", "image/jpeg", "image/gif", "image/webp", "image/svg+xml"].includes(file.mimetype)) { return [400, `Unknown image type: ${file.mimetype}`]; } diff --git a/src/routes/login.ts b/src/routes/login.ts index a4c13ca90..cce214fbe 100644 --- a/src/routes/login.ts +++ b/src/routes/login.ts @@ -67,7 +67,7 @@ function login(req: AppRequest, res: Response) { if (rememberMe) { req.session.cookie.maxAge = 21 * 24 * 3600000; // 3 weeks } else { - req.session.cookie.expires = false; + req.session.cookie.expires = null; } req.session.loggedIn = true; diff --git a/src/routes/route-interface.ts b/src/routes/route-interface.ts index acf86cbe5..4510c5de6 100644 --- a/src/routes/route-interface.ts +++ b/src/routes/route-interface.ts @@ -1,5 +1,5 @@ -import { Request } from "express"; -import { File } from "../services/import/common"; +import { NextFunction, Request, Response } from "express"; +import { Session, SessionData } from "express-session"; export interface AppRequest extends Request { headers: { @@ -7,14 +7,15 @@ export interface AppRequest extends Request { "trilium-cred"?: string; "x-local-date"?: string; "x-labels"?: string; + "trilium-local-now-datetime"?: string; } - session: { - loggedIn: boolean; - cookie: { - maxAge: number; - expires: boolean - }; - regenerate: (callback: () => void) => void; + session: Session & Partial & { + loggedIn: boolean; } - file: File; -} \ No newline at end of file +} + +export type AppRequestHandler = ( + req: AppRequest, + res: Response, + next: NextFunction +) => void; \ No newline at end of file diff --git a/src/routes/routes.js b/src/routes/routes.js index efa8afb8c..dc330e54f 100644 --- a/src/routes/routes.js +++ b/src/routes/routes.js @@ -66,7 +66,7 @@ const etapiAppInfoRoutes = require('../etapi/app_info'); const etapiAttachmentRoutes = require('../etapi/attachments'); const etapiAttributeRoutes = require('../etapi/attributes'); const etapiBranchRoutes = require('../etapi/branches'); -const etapiNoteRoutes = require('../etapi/notes.js'); +const etapiNoteRoutes = require('../etapi/notes'); const etapiSpecialNoteRoutes = require('../etapi/special_notes'); const etapiSpecRoute = require('../etapi/spec.js'); const etapiBackupRoute = require('../etapi/backup'); From 9b9b4520556f6ec7aec76706c7f5bf4cd1e22ff7 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 7 Apr 2024 18:18:26 +0300 Subject: [PATCH 11/15] server-ts: Convert etapi/spec --- src/etapi/{spec.js => spec.ts} | 12 +++++++----- src/routes/routes.js | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) rename src/etapi/{spec.js => spec.ts} (69%) diff --git a/src/etapi/spec.js b/src/etapi/spec.ts similarity index 69% rename from src/etapi/spec.js rename to src/etapi/spec.ts index 83ac93a4b..ed8a555a5 100644 --- a/src/etapi/spec.js +++ b/src/etapi/spec.ts @@ -1,10 +1,12 @@ -const fs = require('fs'); -const path = require('path'); +import { Router } from "express"; + +import fs = require('fs'); +import path = require('path'); const specPath = path.join(__dirname, 'etapi.openapi.yaml'); -let spec = null; +let spec: any = null; -function register(router) { +function register(router: Router) { router.get('/etapi/etapi.openapi.yaml', (req, res, next) => { if (!spec) { spec = fs.readFileSync(specPath, 'utf8'); @@ -15,6 +17,6 @@ function register(router) { }); } -module.exports = { +export = { register }; diff --git a/src/routes/routes.js b/src/routes/routes.js index dc330e54f..00c803cb0 100644 --- a/src/routes/routes.js +++ b/src/routes/routes.js @@ -68,7 +68,7 @@ const etapiAttributeRoutes = require('../etapi/attributes'); const etapiBranchRoutes = require('../etapi/branches'); const etapiNoteRoutes = require('../etapi/notes'); const etapiSpecialNoteRoutes = require('../etapi/special_notes'); -const etapiSpecRoute = require('../etapi/spec.js'); +const etapiSpecRoute = require('../etapi/spec'); const etapiBackupRoute = require('../etapi/backup'); const csrfMiddleware = csurf({ From ed79c1c62a5bf32f84879d905f11627010033139 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 7 Apr 2024 18:21:18 +0300 Subject: [PATCH 12/15] server-ts: Convert etapi/special_notes --- .../{special_notes.js => special_notes.ts} | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) rename src/etapi/{special_notes.js => special_notes.ts} (72%) diff --git a/src/etapi/special_notes.js b/src/etapi/special_notes.ts similarity index 72% rename from src/etapi/special_notes.js rename to src/etapi/special_notes.ts index af771a724..4de5d77df 100644 --- a/src/etapi/special_notes.js +++ b/src/etapi/special_notes.ts @@ -1,13 +1,14 @@ -const specialNotesService = require('../services/special_notes'); -const dateNotesService = require('../services/date_notes'); -const eu = require('./etapi_utils'); -const mappers = require('./mappers'); +import specialNotesService = require('../services/special_notes'); +import dateNotesService = require('../services/date_notes'); +import eu = require('./etapi_utils'); +import mappers = require('./mappers'); +import { Router } from 'express'; -const getDateInvalidError = date => new eu.EtapiError(400, "DATE_INVALID", `Date "${date}" is not valid.`); -const getMonthInvalidError = month => new eu.EtapiError(400, "MONTH_INVALID", `Month "${month}" is not valid.`); -const getYearInvalidError = year => new eu.EtapiError(400, "YEAR_INVALID", `Year "${year}" is not valid.`); +const getDateInvalidError = (date: string) => new eu.EtapiError(400, "DATE_INVALID", `Date "${date}" is not valid.`); +const getMonthInvalidError = (month: string)=> new eu.EtapiError(400, "MONTH_INVALID", `Month "${month}" is not valid.`); +const getYearInvalidError = (year: string) => new eu.EtapiError(400, "YEAR_INVALID", `Year "${year}" is not valid.`); -function isValidDate(date) { +function isValidDate(date: string) { if (!/[0-9]{4}-[0-9]{2}-[0-9]{2}/.test(date)) { return false; } @@ -15,7 +16,7 @@ function isValidDate(date) { return !!Date.parse(date); } -function register(router) { +function register(router: Router) { eu.route(router, 'get', '/etapi/inbox/:date', (req, res, next) => { const { date } = req.params; @@ -72,6 +73,6 @@ function register(router) { }); } -module.exports = { +export = { register }; From 138be84e452abe5eb813db2dacaf0fbefec7ac27 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 16 Apr 2024 21:10:39 +0300 Subject: [PATCH 13/15] server-ts: Address requested changes --- src/etapi/notes.ts | 4 ++++ src/etapi/spec.ts | 2 +- src/etapi/validators.ts | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/etapi/notes.ts b/src/etapi/notes.ts index fcb2d314f..620e9f3d8 100644 --- a/src/etapi/notes.ts +++ b/src/etapi/notes.ts @@ -239,6 +239,10 @@ function parseBoolean(obj: any, name: string) { } function parseOrderDirection(obj: any, name: string) { + if (!(name in obj)) { + return undefined; + } + const integer = parseInt(obj[name]); if (!['asc', 'desc'].includes(obj[name])) { diff --git a/src/etapi/spec.ts b/src/etapi/spec.ts index ed8a555a5..530249d46 100644 --- a/src/etapi/spec.ts +++ b/src/etapi/spec.ts @@ -4,7 +4,7 @@ import fs = require('fs'); import path = require('path'); const specPath = path.join(__dirname, 'etapi.openapi.yaml'); -let spec: any = null; +let spec: string | null = null; function register(router: Router) { router.get('/etapi/etapi.openapi.yaml', (req, res, next) => { diff --git a/src/etapi/validators.ts b/src/etapi/validators.ts index 485bd8d33..a8e98882e 100644 --- a/src/etapi/validators.ts +++ b/src/etapi/validators.ts @@ -24,7 +24,7 @@ function isString(obj: ValidatorArg) { } function isLocalDateTime(obj: ValidatorArg) { - if (obj === undefined || obj === null || typeof obj !== "string") { + if (typeof obj !== "string") { return; } @@ -32,7 +32,7 @@ function isLocalDateTime(obj: ValidatorArg) { } function isUtcDateTime(obj: ValidatorArg) { - if (obj === undefined || obj === null || typeof obj !== "string") { + if (typeof obj !== "string") { return; } From c814187a2535b00c481a8b114b70593b11af31e3 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 17 Apr 2024 22:24:27 +0300 Subject: [PATCH 14/15] server-ts: Address review --- src/etapi/etapi-interface.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/etapi/etapi-interface.ts b/src/etapi/etapi-interface.ts index b53d983dd..a78b98c2c 100644 --- a/src/etapi/etapi-interface.ts +++ b/src/etapi/etapi-interface.ts @@ -1,4 +1,4 @@ -type ValidatorArg = object | undefined | null; +type ValidatorArg = object | string | undefined | null; type ValidatorFunc = (obj: ValidatorArg) => (string | undefined); From 15168fb21377035c9ce1dbf59ebed91742e3b2fe Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 18 Apr 2024 21:26:29 +0300 Subject: [PATCH 15/15] server-ts: Use unknown for validators --- src/etapi/etapi-interface.ts | 4 +--- src/etapi/validators.ts | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/etapi/etapi-interface.ts b/src/etapi/etapi-interface.ts index a78b98c2c..c0b6d3f04 100644 --- a/src/etapi/etapi-interface.ts +++ b/src/etapi/etapi-interface.ts @@ -1,5 +1,3 @@ -type ValidatorArg = object | string | undefined | null; - -type ValidatorFunc = (obj: ValidatorArg) => (string | undefined); +type ValidatorFunc = (obj: unknown) => (string | undefined); type ValidatorMap = Record; \ No newline at end of file diff --git a/src/etapi/validators.ts b/src/etapi/validators.ts index a8e98882e..ce6935c7c 100644 --- a/src/etapi/validators.ts +++ b/src/etapi/validators.ts @@ -1,19 +1,19 @@ import noteTypeService = require('../services/note_types'); import dateUtils = require('../services/date_utils'); -function mandatory(obj: ValidatorArg) { +function mandatory(obj: unknown) { if (obj === undefined) { return `mandatory, but not set`; } } -function notNull(obj: ValidatorArg) { +function notNull(obj: unknown) { if (obj === null) { return `cannot be null`; } } -function isString(obj: ValidatorArg) { +function isString(obj: unknown) { if (obj === undefined || obj === null) { return; } @@ -23,7 +23,7 @@ function isString(obj: ValidatorArg) { } } -function isLocalDateTime(obj: ValidatorArg) { +function isLocalDateTime(obj: unknown) { if (typeof obj !== "string") { return; } @@ -31,7 +31,7 @@ function isLocalDateTime(obj: ValidatorArg) { return dateUtils.validateLocalDateTime(obj); } -function isUtcDateTime(obj: ValidatorArg) { +function isUtcDateTime(obj: unknown) { if (typeof obj !== "string") { return; } @@ -39,7 +39,7 @@ function isUtcDateTime(obj: ValidatorArg) { return dateUtils.validateUtcDateTime(obj); } -function isBoolean(obj: ValidatorArg) { +function isBoolean(obj: unknown) { if (obj === undefined || obj === null) { return; } @@ -49,7 +49,7 @@ function isBoolean(obj: ValidatorArg) { } } -function isInteger(obj: ValidatorArg) { +function isInteger(obj: unknown) { if (obj === undefined || obj === null) { return; } @@ -59,7 +59,7 @@ function isInteger(obj: ValidatorArg) { } } -function isNoteId(obj: ValidatorArg) { +function isNoteId(obj: unknown) { if (obj === undefined || obj === null) { return; } @@ -75,7 +75,7 @@ function isNoteId(obj: ValidatorArg) { } } -function isNoteType(obj: ValidatorArg) { +function isNoteType(obj: unknown) { if (obj === undefined || obj === null) { return; } @@ -87,7 +87,7 @@ function isNoteType(obj: ValidatorArg) { } } -function isAttributeType(obj: ValidatorArg) { +function isAttributeType(obj: unknown) { if (obj === undefined || obj === null) { return; } @@ -97,7 +97,7 @@ function isAttributeType(obj: ValidatorArg) { } } -function isValidEntityId(obj: ValidatorArg) { +function isValidEntityId(obj: unknown) { if (obj === undefined || obj === null) { return; }