mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +01:00
server-ts: Convert etapi/notes
This commit is contained in:
parent
e4024408bd
commit
5fdf094e9d
@ -5,6 +5,7 @@ import becca = require('../becca/becca');
|
|||||||
import etapiTokenService = require('../services/etapi_tokens');
|
import etapiTokenService = require('../services/etapi_tokens');
|
||||||
import config = require('../services/config');
|
import config = require('../services/config');
|
||||||
import { NextFunction, Request, RequestHandler, Response, Router } from 'express';
|
import { NextFunction, Request, RequestHandler, Response, Router } from 'express';
|
||||||
|
import { AppRequest, AppRequestHandler } from '../routes/route-interface';
|
||||||
const GENERIC_CODE = "GENERIC";
|
const GENERIC_CODE = "GENERIC";
|
||||||
|
|
||||||
type HttpMethod = "all" | "get" | "post" | "put" | "delete" | "patch" | "options" | "head";
|
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 {
|
try {
|
||||||
cls.namespace.bindEmitter(req);
|
cls.namespace.bindEmitter(req);
|
||||||
cls.namespace.bindEmitter(res);
|
cls.namespace.bindEmitter(res);
|
||||||
@ -53,7 +54,7 @@ function processRequest(req: Request, res: Response, routeHandler: RequestHandle
|
|||||||
cls.set('componentId', "etapi");
|
cls.set('componentId', "etapi");
|
||||||
cls.set('localNowDateTime', req.headers['trilium-local-now-datetime']);
|
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);
|
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));
|
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');
|
import becca = require('../becca/becca');
|
||||||
const utils = require('../services/utils');
|
import utils = require('../services/utils');
|
||||||
const eu = require('./etapi_utils');
|
import eu = require('./etapi_utils');
|
||||||
const mappers = require('./mappers');
|
import mappers = require('./mappers');
|
||||||
const noteService = require('../services/notes');
|
import noteService = require('../services/notes');
|
||||||
const TaskContext = require('../services/task_context');
|
import TaskContext = require('../services/task_context');
|
||||||
const v = require('./validators');
|
import v = require('./validators');
|
||||||
const searchService = require('../services/search/services/search');
|
import searchService = require('../services/search/services/search');
|
||||||
const SearchContext = require('../services/search/search_context');
|
import SearchContext = require('../services/search/search_context');
|
||||||
const zipExportService = require('../services/export/zip');
|
import zipExportService = require('../services/export/zip');
|
||||||
const zipImportService = require('../services/import/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) => {
|
eu.route(router, 'get', '/etapi/notes', (req, res, next) => {
|
||||||
const { search } = req.query;
|
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.");
|
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 searchResults = searchService.findResultsWithQuery(search, searchContext);
|
||||||
const foundNotes = searchResults.map(sr => becca.notes[sr.noteId]);
|
const foundNotes = searchResults.map(sr => becca.notes[sr.noteId]);
|
||||||
|
|
||||||
const resp = {
|
const resp: any = {
|
||||||
results: foundNotes.map(note => mappers.mapNoteToPojo(note))
|
results: foundNotes.map(note => mappers.mapNoteToPojo(note)),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (searchContext.debugInfo) {
|
if (searchContext.debugInfo) {
|
||||||
@ -41,7 +47,7 @@ function register(router) {
|
|||||||
res.json(mappers.mapNoteToPojo(note));
|
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],
|
'parentNoteId': [v.mandatory, v.notNull, v.isNoteId],
|
||||||
'title': [v.mandatory, v.notNull, v.isString],
|
'title': [v.mandatory, v.notNull, v.isString],
|
||||||
'type': [v.mandatory, v.notNull, v.isNoteType],
|
'type': [v.mandatory, v.notNull, v.isNoteType],
|
||||||
@ -56,9 +62,9 @@ function register(router) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
eu.route(router, 'post', '/etapi/create-note', (req, res, next) => {
|
eu.route(router, 'post', '/etapi/create-note', (req, res, next) => {
|
||||||
const params = {};
|
const _params = {};
|
||||||
|
eu.validateAndPatch(_params, req.body, ALLOWED_PROPERTIES_FOR_CREATE_NOTE);
|
||||||
eu.validateAndPatch(params, req.body, ALLOWED_PROPERTIES_FOR_CREATE_NOTE);
|
const params = _params as NoteParams;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const resp = noteService.createNewNote(params);
|
const resp = noteService.createNewNote(params);
|
||||||
@ -68,7 +74,7 @@ function register(router) {
|
|||||||
branch: mappers.mapBranchToPojo(resp.branch)
|
branch: mappers.mapBranchToPojo(resp.branch)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e: any) {
|
||||||
return eu.sendError(res, 500, eu.GENERIC_CODE, e.message);
|
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 note = eu.getAndCheckNote(req.params.noteId);
|
||||||
const format = req.query.format || "html";
|
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'.`);
|
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.
|
// (e.g. branchIds are not seen in UI), that we export "note export" instead.
|
||||||
const branch = note.getParentBranches()[0];
|
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) => {
|
eu.route(router, 'post', '/etapi/notes/:noteId/import', (req, res, next) => {
|
||||||
@ -186,23 +192,24 @@ function register(router) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseSearchParams(req) {
|
function parseSearchParams(req: AppRequest) {
|
||||||
const rawSearchParams = {
|
const rawSearchParams: SearchParams = {
|
||||||
fastSearch: parseBoolean(req.query, 'fastSearch'),
|
fastSearch: parseBoolean(req.query, 'fastSearch'),
|
||||||
includeArchivedNotes: parseBoolean(req.query, 'includeArchivedNotes'),
|
includeArchivedNotes: parseBoolean(req.query, 'includeArchivedNotes'),
|
||||||
ancestorNoteId: req.query['ancestorNoteId'],
|
ancestorNoteId: parseString(req.query['ancestorNoteId']),
|
||||||
ancestorDepth: req.query['ancestorDepth'], // e.g. "eq5"
|
ancestorDepth: parseString(req.query['ancestorDepth']), // e.g. "eq5"
|
||||||
orderBy: req.query['orderBy'],
|
orderBy: parseString(req.query['orderBy']),
|
||||||
orderDirection: parseOrderDirection(req.query, 'orderDirection'),
|
// 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'),
|
limit: parseInteger(req.query, 'limit'),
|
||||||
debug: parseBoolean(req.query, 'debug')
|
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) {
|
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";
|
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)) {
|
if (!(name in obj)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@ -223,11 +238,7 @@ function parseBoolean(obj, name) {
|
|||||||
return obj[name] === 'true';
|
return obj[name] === 'true';
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseOrderDirection(obj, name) {
|
function parseOrderDirection(obj: any, name: string) {
|
||||||
if (!(name in obj)) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const integer = parseInt(obj[name]);
|
const integer = parseInt(obj[name]);
|
||||||
|
|
||||||
if (!['asc', 'desc'].includes(obj[name])) {
|
if (!['asc', 'desc'].includes(obj[name])) {
|
||||||
@ -237,7 +248,7 @@ function parseOrderDirection(obj, name) {
|
|||||||
return integer;
|
return integer;
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseInteger(obj, name) {
|
function parseInteger(obj: any, name: string) {
|
||||||
if (!(name in obj)) {
|
if (!(name in obj)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@ -251,6 +262,6 @@ function parseInteger(obj, name) {
|
|||||||
return integer;
|
return integer;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
export = {
|
||||||
register
|
register
|
||||||
};
|
};
|
@ -20,6 +20,13 @@ function updateFile(req: AppRequest) {
|
|||||||
const note = becca.getNoteOrThrow(req.params.noteId);
|
const note = becca.getNoteOrThrow(req.params.noteId);
|
||||||
|
|
||||||
const file = req.file;
|
const file = req.file;
|
||||||
|
if (!file) {
|
||||||
|
return {
|
||||||
|
uploaded: false,
|
||||||
|
message: `Missing file.`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
note.saveRevision();
|
note.saveRevision();
|
||||||
|
|
||||||
note.mime = file.mimetype.toLowerCase();
|
note.mime = file.mimetype.toLowerCase();
|
||||||
@ -39,6 +46,12 @@ function updateFile(req: AppRequest) {
|
|||||||
function updateAttachment(req: AppRequest) {
|
function updateAttachment(req: AppRequest) {
|
||||||
const attachment = becca.getAttachmentOrThrow(req.params.attachmentId);
|
const attachment = becca.getAttachmentOrThrow(req.params.attachmentId);
|
||||||
const file = req.file;
|
const file = req.file;
|
||||||
|
if (!file) {
|
||||||
|
return {
|
||||||
|
uploaded: false,
|
||||||
|
message: `Missing file.`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
attachment.getNote().saveRevision();
|
attachment.getNote().saveRevision();
|
||||||
|
|
||||||
|
@ -88,6 +88,13 @@ function updateImage(req: AppRequest) {
|
|||||||
|
|
||||||
const note = becca.getNoteOrThrow(noteId);
|
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)) {
|
if (!["image/png", "image/jpeg", "image/gif", "image/webp", "image/svg+xml"].includes(file.mimetype)) {
|
||||||
return {
|
return {
|
||||||
uploaded: false,
|
uploaded: false,
|
||||||
|
@ -11,6 +11,13 @@ import { AppRequest } from '../route-interface';
|
|||||||
function uploadImage(req: AppRequest) {
|
function uploadImage(req: AppRequest) {
|
||||||
const file = req.file;
|
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)) {
|
if (!["image/png", "image/jpeg", "image/gif", "image/webp", "image/svg+xml"].includes(file.mimetype)) {
|
||||||
return [400, `Unknown image type: ${file.mimetype}`];
|
return [400, `Unknown image type: ${file.mimetype}`];
|
||||||
}
|
}
|
||||||
|
@ -67,7 +67,7 @@ function login(req: AppRequest, res: Response) {
|
|||||||
if (rememberMe) {
|
if (rememberMe) {
|
||||||
req.session.cookie.maxAge = 21 * 24 * 3600000; // 3 weeks
|
req.session.cookie.maxAge = 21 * 24 * 3600000; // 3 weeks
|
||||||
} else {
|
} else {
|
||||||
req.session.cookie.expires = false;
|
req.session.cookie.expires = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
req.session.loggedIn = true;
|
req.session.loggedIn = true;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Request } from "express";
|
import { NextFunction, Request, Response } from "express";
|
||||||
import { File } from "../services/import/common";
|
import { Session, SessionData } from "express-session";
|
||||||
|
|
||||||
export interface AppRequest extends Request {
|
export interface AppRequest extends Request {
|
||||||
headers: {
|
headers: {
|
||||||
@ -7,14 +7,15 @@ export interface AppRequest extends Request {
|
|||||||
"trilium-cred"?: string;
|
"trilium-cred"?: string;
|
||||||
"x-local-date"?: string;
|
"x-local-date"?: string;
|
||||||
"x-labels"?: string;
|
"x-labels"?: string;
|
||||||
|
"trilium-local-now-datetime"?: string;
|
||||||
}
|
}
|
||||||
session: {
|
session: Session & Partial<SessionData> & {
|
||||||
loggedIn: boolean;
|
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 etapiAttachmentRoutes = require('../etapi/attachments');
|
||||||
const etapiAttributeRoutes = require('../etapi/attributes');
|
const etapiAttributeRoutes = require('../etapi/attributes');
|
||||||
const etapiBranchRoutes = require('../etapi/branches');
|
const etapiBranchRoutes = require('../etapi/branches');
|
||||||
const etapiNoteRoutes = require('../etapi/notes.js');
|
const etapiNoteRoutes = require('../etapi/notes');
|
||||||
const etapiSpecialNoteRoutes = require('../etapi/special_notes');
|
const etapiSpecialNoteRoutes = require('../etapi/special_notes');
|
||||||
const etapiSpecRoute = require('../etapi/spec.js');
|
const etapiSpecRoute = require('../etapi/spec.js');
|
||||||
const etapiBackupRoute = require('../etapi/backup');
|
const etapiBackupRoute = require('../etapi/backup');
|
||||||
|
Loading…
x
Reference in New Issue
Block a user