mirror of
				https://github.com/TriliumNext/Notes.git
				synced 2025-10-31 21:11:30 +08:00 
			
		
		
		
	server-ts: Port routes/routes
This commit is contained in:
		
							parent
							
								
									4638351ec8
								
							
						
					
					
						commit
						dededcd303
					
				| @ -38,7 +38,7 @@ app.use(sessionParser); | ||||
| app.use(favicon(`${__dirname}/../images/app-icons/win/icon.ico`)); | ||||
| 
 | ||||
| require('./routes/assets').register(app); | ||||
| require('./routes/routes.js').register(app); | ||||
| require('./routes/routes').register(app); | ||||
| require('./routes/custom').register(app); | ||||
| require('./routes/error_handlers').register(app); | ||||
| 
 | ||||
|  | ||||
| @ -103,6 +103,6 @@ function register(router: Router) { | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| module.exports = { | ||||
| export = { | ||||
|     register | ||||
| }; | ||||
|  | ||||
| @ -229,7 +229,7 @@ function findNotesByUrl(req: Request){ | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| module.exports = { | ||||
| export = { | ||||
|     createNote, | ||||
|     addClipping, | ||||
|     openNote, | ||||
|  | ||||
| @ -55,6 +55,6 @@ function exportBranch(req: Request, res: Response) { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| module.exports = { | ||||
| export = { | ||||
|     exportBranch | ||||
| }; | ||||
|  | ||||
| @ -124,7 +124,7 @@ function attachmentContentProvider(req: Request) { | ||||
|     return streamContent(attachment.getContent(), attachment.getFileName(), attachment.mime); | ||||
| } | ||||
| 
 | ||||
| function streamContent(content: string | Buffer, fileName: string, mimeType: string) { | ||||
| async function streamContent(content: string | Buffer, fileName: string, mimeType: string) { | ||||
|     if (typeof content === "string") { | ||||
|         content = Buffer.from(content, 'utf8'); | ||||
|     } | ||||
|  | ||||
| @ -1,87 +1,95 @@ | ||||
| "use strict"; | ||||
| 
 | ||||
| const utils = require('../services/utils'); | ||||
| const multer = require('multer'); | ||||
| const log = require('../services/log'); | ||||
| const express = require('express'); | ||||
| import utils = require('../services/utils'); | ||||
| import multer = require('multer'); | ||||
| import log = require('../services/log'); | ||||
| import express = require('express'); | ||||
| const router = express.Router(); | ||||
| const auth = require('../services/auth'); | ||||
| const cls = require('../services/cls'); | ||||
| const sql = require('../services/sql'); | ||||
| const entityChangesService = require('../services/entity_changes'); | ||||
| const csurf = require('csurf'); | ||||
| const { createPartialContentHandler } = require("express-partial-content"); | ||||
| const rateLimit = require("express-rate-limit"); | ||||
| const AbstractBeccaEntity = require('../becca/entities/abstract_becca_entity'); | ||||
| const NotFoundError = require('../errors/not_found_error'); | ||||
| const ValidationError = require('../errors/validation_error'); | ||||
| import auth = require('../services/auth'); | ||||
| import cls = require('../services/cls'); | ||||
| import sql = require('../services/sql'); | ||||
| import entityChangesService = require('../services/entity_changes'); | ||||
| import csurf = require('csurf'); | ||||
| import { createPartialContentHandler } from "express-partial-content"; | ||||
| import rateLimit = require("express-rate-limit"); | ||||
| import AbstractBeccaEntity = require('../becca/entities/abstract_becca_entity'); | ||||
| import NotFoundError = require('../errors/not_found_error'); | ||||
| import ValidationError = require('../errors/validation_error'); | ||||
| 
 | ||||
| // page routes
 | ||||
| const setupRoute = require('./setup'); | ||||
| const loginRoute = require('./login'); | ||||
| const indexRoute = require('./index'); | ||||
| import setupRoute = require('./setup'); | ||||
| import loginRoute = require('./login'); | ||||
| import indexRoute = require('./index'); | ||||
| 
 | ||||
| // API routes
 | ||||
| const treeApiRoute = require('./api/tree'); | ||||
| const notesApiRoute = require('./api/notes'); | ||||
| const branchesApiRoute = require('./api/branches'); | ||||
| const attachmentsApiRoute = require('./api/attachments'); | ||||
| const autocompleteApiRoute = require('./api/autocomplete'); | ||||
| const cloningApiRoute = require('./api/cloning'); | ||||
| const revisionsApiRoute = require('./api/revisions'); | ||||
| const recentChangesApiRoute = require('./api/recent_changes'); | ||||
| const optionsApiRoute = require('./api/options'); | ||||
| const passwordApiRoute = require('./api/password'); | ||||
| const syncApiRoute = require('./api/sync'); | ||||
| const loginApiRoute = require('./api/login'); | ||||
| const recentNotesRoute = require('./api/recent_notes'); | ||||
| const appInfoRoute = require('./api/app_info'); | ||||
| const exportRoute = require('./api/export'); | ||||
| const importRoute = require('./api/import'); | ||||
| const setupApiRoute = require('./api/setup'); | ||||
| const sqlRoute = require('./api/sql'); | ||||
| const databaseRoute = require('./api/database'); | ||||
| const imageRoute = require('./api/image'); | ||||
| const attributesRoute = require('./api/attributes'); | ||||
| const scriptRoute = require('./api/script'); | ||||
| const senderRoute = require('./api/sender'); | ||||
| const filesRoute = require('./api/files'); | ||||
| const searchRoute = require('./api/search'); | ||||
| const bulkActionRoute = require('./api/bulk_action'); | ||||
| const specialNotesRoute = require('./api/special_notes'); | ||||
| const noteMapRoute = require('./api/note_map'); | ||||
| const clipperRoute = require('./api/clipper'); | ||||
| const similarNotesRoute = require('./api/similar_notes'); | ||||
| const keysRoute = require('./api/keys'); | ||||
| const backendLogRoute = require('./api/backend_log'); | ||||
| const statsRoute = require('./api/stats'); | ||||
| const fontsRoute = require('./api/fonts'); | ||||
| const etapiTokensApiRoutes = require('./api/etapi_tokens'); | ||||
| const relationMapApiRoute = require('./api/relation-map'); | ||||
| const otherRoute = require('./api/other'); | ||||
| const shareRoutes = require('../share/routes'); | ||||
| import treeApiRoute = require('./api/tree'); | ||||
| import notesApiRoute = require('./api/notes'); | ||||
| import branchesApiRoute = require('./api/branches'); | ||||
| import attachmentsApiRoute = require('./api/attachments'); | ||||
| import autocompleteApiRoute = require('./api/autocomplete'); | ||||
| import cloningApiRoute = require('./api/cloning'); | ||||
| import revisionsApiRoute = require('./api/revisions'); | ||||
| import recentChangesApiRoute = require('./api/recent_changes'); | ||||
| import optionsApiRoute = require('./api/options'); | ||||
| import passwordApiRoute = require('./api/password'); | ||||
| import syncApiRoute = require('./api/sync'); | ||||
| import loginApiRoute = require('./api/login'); | ||||
| import recentNotesRoute = require('./api/recent_notes'); | ||||
| import appInfoRoute = require('./api/app_info'); | ||||
| import exportRoute = require('./api/export'); | ||||
| import importRoute = require('./api/import'); | ||||
| import setupApiRoute = require('./api/setup'); | ||||
| import sqlRoute = require('./api/sql'); | ||||
| import databaseRoute = require('./api/database'); | ||||
| import imageRoute = require('./api/image'); | ||||
| import attributesRoute = require('./api/attributes'); | ||||
| import scriptRoute = require('./api/script'); | ||||
| import senderRoute = require('./api/sender'); | ||||
| import filesRoute = require('./api/files'); | ||||
| import searchRoute = require('./api/search'); | ||||
| import bulkActionRoute = require('./api/bulk_action'); | ||||
| import specialNotesRoute = require('./api/special_notes'); | ||||
| import noteMapRoute = require('./api/note_map'); | ||||
| import clipperRoute = require('./api/clipper'); | ||||
| import similarNotesRoute = require('./api/similar_notes'); | ||||
| import keysRoute = require('./api/keys'); | ||||
| import backendLogRoute = require('./api/backend_log'); | ||||
| import statsRoute = require('./api/stats'); | ||||
| import fontsRoute = require('./api/fonts'); | ||||
| import etapiTokensApiRoutes = require('./api/etapi_tokens'); | ||||
| import relationMapApiRoute = require('./api/relation-map'); | ||||
| import otherRoute = require('./api/other'); | ||||
| import shareRoutes = require('../share/routes'); | ||||
| 
 | ||||
| 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'); | ||||
| const etapiNoteRoutes = require('../etapi/notes'); | ||||
| const etapiSpecialNoteRoutes = require('../etapi/special_notes'); | ||||
| const etapiSpecRoute = require('../etapi/spec'); | ||||
| const etapiBackupRoute = require('../etapi/backup'); | ||||
| import etapiAuthRoutes = require('../etapi/auth'); | ||||
| import etapiAppInfoRoutes = require('../etapi/app_info'); | ||||
| import etapiAttachmentRoutes = require('../etapi/attachments'); | ||||
| import etapiAttributeRoutes = require('../etapi/attributes'); | ||||
| import etapiBranchRoutes = require('../etapi/branches'); | ||||
| import etapiNoteRoutes = require('../etapi/notes'); | ||||
| import etapiSpecialNoteRoutes = require('../etapi/special_notes'); | ||||
| import etapiSpecRoute = require('../etapi/spec'); | ||||
| import etapiBackupRoute = require('../etapi/backup'); | ||||
| import { RequestHandlerParams } from 'express-serve-static-core'; | ||||
| import { AppRequest, AppRequestHandler } from './route-interface'; | ||||
| 
 | ||||
| const csrfMiddleware = csurf({ | ||||
|     cookie: true, | ||||
|     path: '' // empty, so cookie is valid only for the current path
 | ||||
| }); | ||||
|     // TODO: Typescript complains that path does not exist in csurf options, but it's still in the
 | ||||
| } as any); | ||||
| 
 | ||||
| const MAX_ALLOWED_FILE_SIZE_MB = 250; | ||||
| const GET = 'get', PST = 'post', PUT = 'put', PATCH = 'patch', DEL = 'delete'; | ||||
| 
 | ||||
| type ApiResultHandler = (req: express.Request, res: express.Response, result: unknown) => number; | ||||
| 
 | ||||
| // TODO: Deduplicate with etapi_utils.ts afterwards.
 | ||||
| type HttpMethod = "all" | "get" | "post" | "put" | "delete" | "patch" | "options" | "head"; | ||||
| 
 | ||||
| const uploadMiddleware = createUploadMiddleware(); | ||||
| 
 | ||||
| const uploadMiddlewareWithErrorHandling = function (req, res, next) { | ||||
| const uploadMiddlewareWithErrorHandling = function (req: express.Request, res: express.Response, next: express.NextFunction) { | ||||
|     uploadMiddleware(req, res, function (err) { | ||||
|         if (err?.code === 'LIMIT_FILE_SIZE') { | ||||
|             res.setHeader("Content-Type", "text/plain") | ||||
| @ -94,12 +102,12 @@ const uploadMiddlewareWithErrorHandling = function (req, res, next) { | ||||
|     }); | ||||
| }; | ||||
| 
 | ||||
| function register(app) { | ||||
| function register(app: express.Application) { | ||||
|     route(GET, '/', [auth.checkAuth, csrfMiddleware], indexRoute.index); | ||||
|     route(GET, '/login', [auth.checkAppInitialized, auth.checkPasswordSet], loginRoute.loginPage); | ||||
|     route(GET, '/set-password', [auth.checkAppInitialized, auth.checkPasswordNotSet], loginRoute.setPasswordPage); | ||||
| 
 | ||||
|     const loginRateLimiter = rateLimit({ | ||||
|     const loginRateLimiter = rateLimit.rateLimit({ | ||||
|         windowMs: 15 * 60 * 1000, // 15 minutes
 | ||||
|         max: 10, // limit each IP to 10 requests per windowMs
 | ||||
|         skipSuccessfulRequests: true // successful auth to rate-limited ETAPI routes isn't counted. However, successful auth to /login is still counted!
 | ||||
| @ -353,7 +361,7 @@ function register(app) { | ||||
| } | ||||
| 
 | ||||
| /** Handling common patterns. If entity is not caught, serialization to JSON will fail */ | ||||
| function convertEntitiesToPojo(result) { | ||||
| function convertEntitiesToPojo(result: unknown) { | ||||
|     if (result instanceof AbstractBeccaEntity) { | ||||
|         result = result.getPojo(); | ||||
|     } | ||||
| @ -364,24 +372,24 @@ function convertEntitiesToPojo(result) { | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     else { | ||||
|         if (result && result.note instanceof AbstractBeccaEntity) { | ||||
|     else if (result && typeof result === "object") { | ||||
|         if ("note" in result && result.note instanceof AbstractBeccaEntity) { | ||||
|             result.note = result.note.getPojo(); | ||||
|         } | ||||
| 
 | ||||
|         if (result && result.branch instanceof AbstractBeccaEntity) { | ||||
|         if ("branch" in result && result.branch instanceof AbstractBeccaEntity) { | ||||
|             result.branch = result.branch.getPojo(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if (result && result.executionResult) { // from runOnBackend()
 | ||||
|     if (result && typeof result === "object" && "executionResult" in result) { // from runOnBackend()
 | ||||
|         result.executionResult = convertEntitiesToPojo(result.executionResult); | ||||
|     } | ||||
| 
 | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| function apiResultHandler(req, res, result) { | ||||
| function apiResultHandler(req: express.Request, res: express.Response, result: unknown) { | ||||
|     res.setHeader('trilium-max-entity-change-id', entityChangesService.getMaxEntityChangeId()); | ||||
| 
 | ||||
|     result = convertEntitiesToPojo(result); | ||||
| @ -404,7 +412,7 @@ function apiResultHandler(req, res, result) { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| function send(res, statusCode, response) { | ||||
| function send(res: express.Response, statusCode: number, response: unknown) { | ||||
|     if (typeof response === 'string') { | ||||
|         if (statusCode >= 400) { | ||||
|             res.setHeader("Content-Type", "text/plain"); | ||||
| @ -424,12 +432,12 @@ function send(res, statusCode, response) { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| function apiRoute(method, path, routeHandler) { | ||||
| function apiRoute(method: HttpMethod, path: string, routeHandler: express.Handler) { | ||||
|     route(method, path, [auth.checkApiAuth, csrfMiddleware], routeHandler, apiResultHandler); | ||||
| } | ||||
| 
 | ||||
| function route(method, path, middleware, routeHandler, resultHandler = null, transactional = true) { | ||||
|     router[method](path, ...middleware, (req, res, next) => { | ||||
| function route(method: HttpMethod, path: string, middleware: (express.Handler | AppRequestHandler)[], routeHandler: AppRequestHandler, resultHandler: ApiResultHandler | null = null, transactional = true) { | ||||
|     router[method](path, ...(middleware as express.Handler[]), (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||||
|         const start = Date.now(); | ||||
| 
 | ||||
|         try { | ||||
| @ -441,7 +449,7 @@ function route(method, path, middleware, routeHandler, resultHandler = null, tra | ||||
|                 cls.set('localNowDateTime', req.headers['trilium-local-now-datetime']); | ||||
|                 cls.set('hoistedNoteId', req.headers['trilium-hoisted-note-id'] || 'root'); | ||||
| 
 | ||||
|                 const cb = () => routeHandler(req, res, next); | ||||
|                 const cb = () => routeHandler(req as AppRequest, res, next); | ||||
| 
 | ||||
|                 return transactional ? sql.transactional(cb) : cb(); | ||||
|             }); | ||||
| @ -452,8 +460,8 @@ function route(method, path, middleware, routeHandler, resultHandler = null, tra | ||||
| 
 | ||||
|             if (result?.then) { // promise
 | ||||
|                 result | ||||
|                     .then(promiseResult => handleResponse(resultHandler, req, res, promiseResult, start)) | ||||
|                     .catch(e => handleException(e, method, path, res)); | ||||
|                     .then((promiseResult: unknown) => handleResponse(resultHandler, req, res, promiseResult, start)) | ||||
|                     .catch((e: any) => handleException(e, method, path, res)); | ||||
|             } else { | ||||
|                 handleResponse(resultHandler, req, res, result, start) | ||||
|             } | ||||
| @ -464,13 +472,13 @@ function route(method, path, middleware, routeHandler, resultHandler = null, tra | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| function handleResponse(resultHandler, req, res, result, start) { | ||||
| function handleResponse(resultHandler: ApiResultHandler, req: express.Request, res: express.Response, result: unknown, start: number) { | ||||
|     const responseLength = resultHandler(req, res, result); | ||||
| 
 | ||||
|     log.request(req, res, Date.now() - start, responseLength); | ||||
| } | ||||
| 
 | ||||
| function handleException(e, method, path, res) { | ||||
| function handleException(e: any, method: HttpMethod, path: string, res: express.Response) { | ||||
|     log.error(`${method} ${path} threw exception: '${e.message}', stack: ${e.stack}`); | ||||
| 
 | ||||
|     if (e instanceof ValidationError) { | ||||
| @ -492,8 +500,8 @@ function handleException(e, method, path, res) { | ||||
| } | ||||
| 
 | ||||
| function createUploadMiddleware() { | ||||
|     const multerOptions = { | ||||
|         fileFilter: (req, file, cb) => { | ||||
|     const multerOptions: multer.Options = { | ||||
|         fileFilter: (req: express.Request, file, cb) => { | ||||
|             // UTF-8 file names are not well decoded by multer/busboy, so we handle the conversion on our side.
 | ||||
|             // See https://github.com/expressjs/multer/pull/1102.
 | ||||
|             file.originalname = Buffer.from(file.originalname, "latin1").toString("utf-8"); | ||||
| @ -510,6 +518,6 @@ function createUploadMiddleware() { | ||||
|     return multer(multerOptions).single('upload'); | ||||
| } | ||||
| 
 | ||||
| module.exports = { | ||||
| export = { | ||||
|     register | ||||
| }; | ||||
| @ -76,7 +76,7 @@ function error(message: string) { | ||||
| 
 | ||||
| const requestBlacklist = [ "/libraries", "/app", "/images", "/stylesheets", "/api/recent-notes" ]; | ||||
| 
 | ||||
| function request(req: Request, res: Response, timeMs: number, responseLength = "?") { | ||||
| function request(req: Request, res: Response, timeMs: number, responseLength: number | string = "?") { | ||||
|     for (const bl of requestBlacklist) { | ||||
|         if (req.url.startsWith(bl)) { | ||||
|             return; | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Elian Doran
						Elian Doran