Added pageNamespace hook support
This commit is contained in:
@@ -36,6 +36,8 @@ import {
|
||||
ensureFTSTable,
|
||||
fullTextSearchSyscalls,
|
||||
} from "@plugos/plugos/syscalls/fulltext.knex_sqlite";
|
||||
import { PlugSpacePrimitives } from "./hooks/plug_space_primitives";
|
||||
import { PageNamespaceHook } from "./hooks/page_namespace";
|
||||
|
||||
const safeFilename = /^[a-zA-Z0-9_\-\.]+$/;
|
||||
|
||||
@@ -69,9 +71,14 @@ export class ExpressServer {
|
||||
// Setup system
|
||||
this.eventHook = new EventHook();
|
||||
this.system.addHook(this.eventHook);
|
||||
let namespaceHook = new PageNamespaceHook();
|
||||
this.system.addHook(namespaceHook);
|
||||
this.space = new Space(
|
||||
new EventedSpacePrimitives(
|
||||
new DiskSpacePrimitives(options.pagesPath),
|
||||
new PlugSpacePrimitives(
|
||||
new DiskSpacePrimitives(options.pagesPath),
|
||||
namespaceHook
|
||||
),
|
||||
this.eventHook
|
||||
),
|
||||
true
|
||||
@@ -227,6 +234,7 @@ export class ExpressServer {
|
||||
let pageData = await this.space.readPage(pageName);
|
||||
res.status(200);
|
||||
res.header("Last-Modified", "" + pageData.meta.lastModified);
|
||||
res.header("X-Permission", pageData.meta.perm);
|
||||
res.header("Content-Type", "text/markdown");
|
||||
res.send(pageData.text);
|
||||
} catch (e) {
|
||||
@@ -251,6 +259,7 @@ export class ExpressServer {
|
||||
);
|
||||
res.status(200);
|
||||
res.header("Last-Modified", "" + meta.lastModified);
|
||||
res.header("X-Permission", meta.perm);
|
||||
res.send("OK");
|
||||
} catch (err) {
|
||||
res.status(500);
|
||||
@@ -264,6 +273,7 @@ export class ExpressServer {
|
||||
const meta = await this.space.getPageMeta(pageName);
|
||||
res.status(200);
|
||||
res.header("Last-Modified", "" + meta.lastModified);
|
||||
res.header("X-Permission", meta.perm);
|
||||
res.header("Content-Type", "text/markdown");
|
||||
res.send("");
|
||||
} catch (e) {
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
import { Plug } from "@plugos/plugos/plug";
|
||||
import { System } from "@plugos/plugos/system";
|
||||
import { Hook, Manifest } from "@plugos/plugos/types";
|
||||
import { Express, NextFunction, Request, Response, Router } from "express";
|
||||
|
||||
export type PageNamespaceOperation =
|
||||
| "readPage"
|
||||
| "writePage"
|
||||
| "listPages"
|
||||
| "getPageMeta"
|
||||
| "deletePage";
|
||||
|
||||
export type PageNamespaceDef = {
|
||||
pattern: string;
|
||||
operation: PageNamespaceOperation;
|
||||
};
|
||||
|
||||
export type PageNamespaceHookT = {
|
||||
pageNamespace?: PageNamespaceDef;
|
||||
};
|
||||
|
||||
type SpaceFunction = {
|
||||
operation: PageNamespaceOperation;
|
||||
pattern: RegExp;
|
||||
plug: Plug<PageNamespaceHookT>;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export class PageNamespaceHook implements Hook<PageNamespaceHookT> {
|
||||
spaceFunctions: SpaceFunction[] = [];
|
||||
constructor() {}
|
||||
|
||||
apply(system: System<PageNamespaceHookT>): void {
|
||||
system.on({
|
||||
plugLoaded: () => {
|
||||
this.updateCache(system);
|
||||
},
|
||||
plugUnloaded: () => {
|
||||
this.updateCache(system);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
updateCache(system: System<PageNamespaceHookT>) {
|
||||
this.spaceFunctions = [];
|
||||
for (let plug of system.loadedPlugs.values()) {
|
||||
if (plug.manifest?.functions) {
|
||||
for (let [funcName, funcDef] of Object.entries(
|
||||
plug.manifest.functions
|
||||
)) {
|
||||
if (funcDef.pageNamespace) {
|
||||
this.spaceFunctions.push({
|
||||
operation: funcDef.pageNamespace.operation,
|
||||
pattern: new RegExp(funcDef.pageNamespace.pattern),
|
||||
plug,
|
||||
name: funcName,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
validateManifest(manifest: Manifest<PageNamespaceHookT>): string[] {
|
||||
let errors: string[] = [];
|
||||
if (!manifest.functions) {
|
||||
return [];
|
||||
}
|
||||
for (let [funcName, funcDef] of Object.entries(manifest.functions)) {
|
||||
if (funcDef.pageNamespace) {
|
||||
if (!funcDef.pageNamespace.pattern) {
|
||||
errors.push(`Function ${funcName} has a namespace but no pattern`);
|
||||
}
|
||||
if (!funcDef.pageNamespace.operation) {
|
||||
errors.push(`Function ${funcName} has a namespace but no operation`);
|
||||
}
|
||||
if (
|
||||
!["readPage", "writePage", "getPageMeta", "listPages"].includes(
|
||||
funcDef.pageNamespace.operation
|
||||
)
|
||||
) {
|
||||
errors.push(
|
||||
`Function ${funcName} has an invalid operation ${funcDef.pageNamespace.operation}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
import { Plug } from "@plugos/plugos/plug";
|
||||
import { SpacePrimitives } from "@silverbulletmd/common/spaces/space_primitives";
|
||||
import { PageMeta } from "@silverbulletmd/common/types";
|
||||
import { PageNamespaceHook, PageNamespaceOperation } from "./page_namespace";
|
||||
|
||||
export class PlugSpacePrimitives implements SpacePrimitives {
|
||||
constructor(
|
||||
private wrapped: SpacePrimitives,
|
||||
private hook: PageNamespaceHook
|
||||
) {}
|
||||
|
||||
performOperation(
|
||||
type: PageNamespaceOperation,
|
||||
pageName: string,
|
||||
...args: any[]
|
||||
): Promise<any> | false {
|
||||
for (let { operation, pattern, plug, name } of this.hook.spaceFunctions) {
|
||||
if (operation === type && pageName.match(pattern)) {
|
||||
return plug.invoke(name, [pageName, ...args]);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async fetchPageList(): Promise<{
|
||||
pages: Set<PageMeta>;
|
||||
nowTimestamp: number;
|
||||
}> {
|
||||
let allPages = new Set<PageMeta>();
|
||||
for (let { plug, name, operation } of this.hook.spaceFunctions) {
|
||||
if (operation === "listPages") {
|
||||
for (let pm of await plug.invoke(name, [])) {
|
||||
allPages.add(pm);
|
||||
}
|
||||
}
|
||||
}
|
||||
let result = await this.wrapped.fetchPageList();
|
||||
for (let pm of result.pages) {
|
||||
allPages.add(pm);
|
||||
}
|
||||
return {
|
||||
nowTimestamp: result.nowTimestamp,
|
||||
pages: allPages,
|
||||
};
|
||||
}
|
||||
|
||||
readPage(name: string): Promise<{ text: string; meta: PageMeta }> {
|
||||
let result = this.performOperation("readPage", name);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
return this.wrapped.readPage(name);
|
||||
}
|
||||
|
||||
getPageMeta(name: string): Promise<PageMeta> {
|
||||
let result = this.performOperation("getPageMeta", name);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
return this.wrapped.getPageMeta(name);
|
||||
}
|
||||
|
||||
writePage(
|
||||
name: string,
|
||||
text: string,
|
||||
selfUpdate?: boolean,
|
||||
lastModified?: number
|
||||
): Promise<PageMeta> {
|
||||
let result = this.performOperation(
|
||||
"writePage",
|
||||
name,
|
||||
text,
|
||||
selfUpdate,
|
||||
lastModified
|
||||
);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return this.wrapped.writePage(name, text, selfUpdate, lastModified);
|
||||
}
|
||||
|
||||
deletePage(name: string): Promise<void> {
|
||||
let result = this.performOperation("deletePage", name);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
return this.wrapped.deletePage(name);
|
||||
}
|
||||
|
||||
proxySyscall(plug: Plug<any>, name: string, args: any[]): Promise<any> {
|
||||
return this.wrapped.proxySyscall(plug, name, args);
|
||||
}
|
||||
|
||||
invokeFunction(
|
||||
plug: Plug<any>,
|
||||
env: string,
|
||||
name: string,
|
||||
args: any[]
|
||||
): Promise<any> {
|
||||
return this.wrapped.invokeFunction(plug, env, name, args);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user