mirror of
https://github.com/zadam/trilium.git
synced 2026-01-16 19:44:24 +01:00
feat(core): shared router between lightweight and server
This commit is contained in:
parent
8274f9a220
commit
22590596da
@ -32,14 +32,14 @@ const encoder = new TextEncoder();
|
||||
/**
|
||||
* Convert an Express-style path pattern to a RegExp.
|
||||
* Supports :param syntax for path parameters.
|
||||
*
|
||||
*
|
||||
* Examples:
|
||||
* /api/notes/:noteId -> /^\/api\/notes\/([^\/]+)$/
|
||||
* /api/notes/:noteId/revisions -> /^\/api\/notes\/([^\/]+)\/revisions$/
|
||||
*/
|
||||
function pathToRegex(path: string): { pattern: RegExp; paramNames: string[] } {
|
||||
const paramNames: string[] = [];
|
||||
|
||||
|
||||
// Escape special regex characters except for :param patterns
|
||||
const regexPattern = path
|
||||
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') // Escape special chars
|
||||
@ -47,7 +47,7 @@ function pathToRegex(path: string): { pattern: RegExp; paramNames: string[] } {
|
||||
paramNames.push(paramName);
|
||||
return '([^/]+)';
|
||||
});
|
||||
|
||||
|
||||
return {
|
||||
pattern: new RegExp(`^${regexPattern}$`),
|
||||
paramNames
|
||||
@ -60,7 +60,7 @@ function pathToRegex(path: string): { pattern: RegExp; paramNames: string[] } {
|
||||
function parseQuery(search: string): Record<string, string | undefined> {
|
||||
const query: Record<string, string | undefined> = {};
|
||||
if (!search || search === '?') return query;
|
||||
|
||||
|
||||
const params = new URLSearchParams(search);
|
||||
for (const [key, value] of params) {
|
||||
query[key] = value;
|
||||
@ -206,11 +206,11 @@ export class BrowserRouter {
|
||||
// Check for known error types
|
||||
if (error && typeof error === 'object') {
|
||||
const err = error as { constructor?: { name?: string }; message?: string };
|
||||
|
||||
|
||||
if (err.constructor?.name === 'NotFoundError') {
|
||||
return jsonResponse({ message: err.message || 'Not found' }, 404);
|
||||
}
|
||||
|
||||
|
||||
if (err.constructor?.name === 'ValidationError') {
|
||||
return jsonResponse({ message: err.message || 'Validation error' }, 400);
|
||||
}
|
||||
|
||||
@ -1,80 +1,54 @@
|
||||
/**
|
||||
* Browser route definitions.
|
||||
* This mirrors the server's routes.ts but for the browser worker.
|
||||
* This integrates with the shared route builder from @triliumnext/core.
|
||||
*/
|
||||
|
||||
import type { routes as coreRoutes } from '@triliumnext/core';
|
||||
import { routes } from '@triliumnext/core';
|
||||
import { BrowserRouter, type BrowserRequest } from './browser_router';
|
||||
|
||||
type CoreRoutes = typeof coreRoutes;
|
||||
type HttpMethod = 'get' | 'post' | 'put' | 'patch' | 'delete';
|
||||
|
||||
/**
|
||||
* Register all API routes on the browser router.
|
||||
*
|
||||
* @param router - The browser router instance
|
||||
* @param routes - The core routes module from @triliumnext/core
|
||||
* Wraps a core route handler to work with the BrowserRouter.
|
||||
* Core handlers expect an Express-like request object with params, query, and body.
|
||||
*/
|
||||
export function registerRoutes(router: BrowserRouter, routes: CoreRoutes): void {
|
||||
const { optionsApiRoute, treeApiRoute, keysApiRoute } = routes;
|
||||
|
||||
// Tree routes
|
||||
router.get('/api/tree', (req) =>
|
||||
treeApiRoute.getTree({
|
||||
query: {
|
||||
subTreeNoteId: req.query.subTreeNoteId
|
||||
}
|
||||
} as any)
|
||||
);
|
||||
|
||||
router.post('/api/tree/load', (req) =>
|
||||
treeApiRoute.load({
|
||||
function wrapHandler(handler: (req: any) => unknown) {
|
||||
return (req: BrowserRequest) => {
|
||||
// Create an Express-like request object
|
||||
const expressLikeReq = {
|
||||
params: req.params,
|
||||
query: req.query,
|
||||
body: req.body
|
||||
} as any)
|
||||
);
|
||||
};
|
||||
return handler(expressLikeReq);
|
||||
};
|
||||
}
|
||||
|
||||
// Options routes
|
||||
router.get('/api/options', () =>
|
||||
optionsApiRoute.getOptions()
|
||||
);
|
||||
|
||||
router.put('/api/options/:name/:value', (req) =>
|
||||
optionsApiRoute.updateOption({
|
||||
params: req.params
|
||||
} as any)
|
||||
);
|
||||
|
||||
router.put('/api/options', (req) =>
|
||||
optionsApiRoute.updateOptions({
|
||||
body: req.body
|
||||
} as any)
|
||||
);
|
||||
|
||||
router.get('/api/options/user-themes', () =>
|
||||
optionsApiRoute.getUserThemes()
|
||||
);
|
||||
|
||||
router.get('/api/options/locales', () =>
|
||||
optionsApiRoute.getSupportedLocales()
|
||||
);
|
||||
/**
|
||||
* Creates an apiRoute function compatible with buildSharedApiRoutes.
|
||||
* This bridges the core's route registration to the BrowserRouter.
|
||||
*/
|
||||
function createApiRoute(router: BrowserRouter) {
|
||||
return (method: HttpMethod, path: string, handler: (req: any) => unknown) => {
|
||||
router.register(method, path, wrapHandler(handler));
|
||||
};
|
||||
}
|
||||
|
||||
// Keyboard actions routes
|
||||
router.get('/api/keyboard-actions', () =>
|
||||
keysApiRoute.getKeyboardActions()
|
||||
);
|
||||
|
||||
router.get('/api/keyboard-shortcuts-for-notes', () =>
|
||||
keysApiRoute.getShortcutsForNotes()
|
||||
);
|
||||
|
||||
// Add more routes here as they are implemented in @triliumnext/core
|
||||
// Follow the pattern from apps/server/src/routes/routes.ts
|
||||
/**
|
||||
* Register all API routes on the browser router using the shared builder.
|
||||
*
|
||||
* @param router - The browser router instance
|
||||
*/
|
||||
export function registerRoutes(router: BrowserRouter): void {
|
||||
const apiRoute = createApiRoute(router);
|
||||
routes.buildSharedApiRoutes(apiRoute);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and configure a router with all routes registered.
|
||||
*/
|
||||
export function createConfiguredRouter(routes: CoreRoutes): BrowserRouter {
|
||||
export function createConfiguredRouter(): BrowserRouter {
|
||||
const router = new BrowserRouter();
|
||||
registerRoutes(router, routes);
|
||||
registerRoutes(router);
|
||||
return router;
|
||||
}
|
||||
|
||||
@ -95,7 +95,7 @@ async function initialize(): Promise<void> {
|
||||
console.log("[Worker] Supported routes", Object.keys(coreModule.routes));
|
||||
|
||||
// Create and configure the router
|
||||
router = createConfiguredRouter(coreModule.routes);
|
||||
router = createConfiguredRouter();
|
||||
console.log("[Worker] Router configured");
|
||||
|
||||
console.log("[Worker] Initializing becca...");
|
||||
@ -200,7 +200,7 @@ async function dispatch(request: LocalRequest) {
|
||||
const url = new URL(request.url);
|
||||
|
||||
console.log("[Worker] Dispatch:", url.pathname);
|
||||
|
||||
|
||||
// Bootstrap is handled specially before the router is ready
|
||||
if (request.method === "GET" && url.pathname === "/bootstrap") {
|
||||
return handleBootstrap();
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import { routes } from "@triliumnext/core";
|
||||
import { createPartialContentHandler } from "@triliumnext/express-partial-content";
|
||||
import express from "express";
|
||||
import rateLimit from "express-rate-limit";
|
||||
@ -66,6 +65,7 @@ import loginRoute from "./login.js";
|
||||
import { apiResultHandler, apiRoute, asyncApiRoute, asyncRoute, route, router, uploadMiddlewareWithErrorHandling } from "./route_api.js";
|
||||
// page routes
|
||||
import setupRoute from "./setup.js";
|
||||
import { routes } from "@triliumnext/core";
|
||||
|
||||
const GET = "get",
|
||||
PST = "post",
|
||||
@ -103,9 +103,7 @@ function register(app: express.Application) {
|
||||
apiRoute(GET, '/api/totp_recovery/enabled', recoveryCodes.checkForRecoveryKeys);
|
||||
apiRoute(GET, '/api/totp_recovery/used', recoveryCodes.getUsedRecoveryCodes);
|
||||
|
||||
const { treeApiRoute } = routes;
|
||||
apiRoute(GET, '/api/tree', treeApiRoute.getTree);
|
||||
apiRoute(PST, '/api/tree/load', treeApiRoute.load);
|
||||
routes.buildSharedApiRoutes(apiRoute);
|
||||
|
||||
apiRoute(GET, "/api/notes/:noteId", notesApiRoute.getNote);
|
||||
apiRoute(GET, "/api/notes/:noteId/blob", notesApiRoute.getNoteBlob);
|
||||
@ -210,14 +208,6 @@ function register(app: express.Application) {
|
||||
route(GET, "/api/images/:noteId/:filename", [auth.checkApiAuthOrElectron], imageRoute.returnImageFromNote);
|
||||
route(PUT, "/api/images/:noteId", [auth.checkApiAuthOrElectron, uploadMiddlewareWithErrorHandling, csrfMiddleware], imageRoute.updateImage, apiResultHandler);
|
||||
|
||||
const { optionsApiRoute } = routes;
|
||||
apiRoute(GET, "/api/options", optionsApiRoute.getOptions);
|
||||
// FIXME: possibly change to sending value in the body to avoid host of HTTP server issues with slashes
|
||||
apiRoute(PUT, "/api/options/:name/:value", optionsApiRoute.updateOption);
|
||||
apiRoute(PUT, "/api/options", optionsApiRoute.updateOptions);
|
||||
apiRoute(GET, "/api/options/user-themes", optionsApiRoute.getUserThemes);
|
||||
apiRoute(GET, "/api/options/locales", optionsApiRoute.getSupportedLocales);
|
||||
|
||||
apiRoute(PST, "/api/password/change", passwordApiRoute.changePassword);
|
||||
apiRoute(PST, "/api/password/reset", passwordApiRoute.resetPassword);
|
||||
|
||||
@ -331,9 +321,6 @@ function register(app: express.Application) {
|
||||
asyncRoute(PST, "/api/sender/image", [auth.checkEtapiToken, uploadMiddlewareWithErrorHandling], senderRoute.uploadImage, apiResultHandler);
|
||||
asyncRoute(PST, "/api/sender/note", [auth.checkEtapiToken], senderRoute.saveNote, apiResultHandler);
|
||||
|
||||
apiRoute(GET, "/api/keyboard-actions", routes.keysApiRoute.getKeyboardActions);
|
||||
apiRoute(GET, "/api/keyboard-shortcuts-for-notes", routes.keysApiRoute.getShortcutsForNotes);
|
||||
|
||||
apiRoute(PST, "/api/relation-map", relationMapApiRoute.getRelationMap);
|
||||
apiRoute(PST, "/api/notes/erase-deleted-notes-now", notesApiRoute.eraseDeletedNotesNow);
|
||||
apiRoute(PST, "/api/notes/erase-unused-attachments-now", notesApiRoute.eraseUnusedAttachmentsNow);
|
||||
|
||||
@ -1,3 +1,25 @@
|
||||
export { default as optionsApiRoute } from "./api/options";
|
||||
export { default as treeApiRoute } from "./api/tree";
|
||||
export { default as keysApiRoute } from "./api/keys";
|
||||
import optionsApiRoute from "./api/options";
|
||||
import treeApiRoute from "./api/tree";
|
||||
import keysApiRoute from "./api/keys";
|
||||
|
||||
// TODO: Deduplicate with routes.ts
|
||||
const GET = "get",
|
||||
PST = "post",
|
||||
PUT = "put",
|
||||
PATCH = "patch",
|
||||
DEL = "delete";
|
||||
|
||||
export function buildSharedApiRoutes(apiRoute: any) {
|
||||
apiRoute(GET, '/api/tree', treeApiRoute.getTree);
|
||||
apiRoute(PST, '/api/tree/load', treeApiRoute.load);
|
||||
|
||||
apiRoute(GET, "/api/options", optionsApiRoute.getOptions);
|
||||
// FIXME: possibly change to sending value in the body to avoid host of HTTP server issues with slashes
|
||||
apiRoute(PUT, "/api/options/:name/:value", optionsApiRoute.updateOption);
|
||||
apiRoute(PUT, "/api/options", optionsApiRoute.updateOptions);
|
||||
apiRoute(GET, "/api/options/user-themes", optionsApiRoute.getUserThemes);
|
||||
apiRoute(GET, "/api/options/locales", optionsApiRoute.getSupportedLocales);
|
||||
|
||||
apiRoute(GET, "/api/keyboard-actions", keysApiRoute.getKeyboardActions);
|
||||
apiRoute(GET, "/api/keyboard-shortcuts-for-notes", keysApiRoute.getShortcutsForNotes);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user