mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 18:36:30 +01:00 
			
		
		
		
	server-ts: Convert etapi/notes
This commit is contained in:
		| @@ -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)); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
| }; | ||||
| @@ -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(); | ||||
|  | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
| @@ -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}`]; | ||||
|     } | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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: { | ||||
|     session: Session & Partial<SessionData> & { | ||||
|         loggedIn: boolean; | ||||
|         cookie: { | ||||
|             maxAge: number; | ||||
|             expires: boolean | ||||
|         }; | ||||
|         regenerate: (callback: () => void) => void;      | ||||
|     } | ||||
|     file: File; | ||||
| } | ||||
|  | ||||
| export type AppRequestHandler = ( | ||||
|     req: AppRequest, | ||||
|     res: Response, | ||||
|     next: NextFunction | ||||
| ) => void; | ||||
| @@ -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'); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user