rhizome/src/http-api.ts

156 lines
4.8 KiB
TypeScript
Raw Normal View History

import Debug from "debug";
2024-12-25 19:27:36 -06:00
import express, {Express, Router} from "express";
2024-12-25 16:13:48 -06:00
import {Server} from "http";
import {Collection} from "./collection";
2024-12-25 16:13:48 -06:00
import {RhizomeNode} from "./node";
2024-12-23 17:29:38 -06:00
import {Delta} from "./types";
2024-12-25 19:27:36 -06:00
import {htmlDocFromMarkdown, MDFiles} from "./util/md-files";
2024-12-23 17:29:38 -06:00
const debug = Debug('http-api');
2024-12-25 16:13:48 -06:00
export class HttpApi {
rhizomeNode: RhizomeNode;
2024-12-25 19:27:36 -06:00
app: Express;
router: Router;
2024-12-25 16:13:48 -06:00
mdFiles = new MDFiles();
server?: Server;
constructor(rhizomeNode: RhizomeNode) {
this.rhizomeNode = rhizomeNode;
2024-12-25 19:27:36 -06:00
this.app = express();
this.router = Router();
2024-12-25 16:13:48 -06:00
this.app.use(express.json());
2024-12-25 19:27:36 -06:00
this.app.use(this.router);
2024-12-25 16:13:48 -06:00
}
start() {
// Scan and watch for markdown files
this.mdFiles.readDir();
this.mdFiles.readReadme();
this.mdFiles.watchDir();
this.mdFiles.watchReadme();
2024-12-23 17:29:38 -06:00
2024-12-25 16:13:48 -06:00
// Serve README
2024-12-25 19:27:36 -06:00
this.router.get('/html/README', (_req: express.Request, res: express.Response) => {
2024-12-25 16:13:48 -06:00
const html = this.mdFiles.getReadmeHTML();
2024-12-23 17:29:38 -06:00
res.setHeader('content-type', 'text/html').send(html);
});
2024-12-25 16:13:48 -06:00
// Serve markdown files as html
2024-12-25 19:27:36 -06:00
this.router.get('/html/:name', (req: express.Request, res: express.Response) => {
2024-12-25 16:13:48 -06:00
let html = this.mdFiles.getHtml(req.params.name);
if (!html) {
res.status(404);
html = htmlDocFromMarkdown('# 404\n\n## [Index](/html)');
}
res.setHeader('content-type', 'text/html');
res.send(html);
});
2024-12-23 17:29:38 -06:00
2024-12-25 16:13:48 -06:00
// Serve index
{
let md = `# Files\n\n`;
md += `[README](/html/README)\n\n`;
for (const name of this.mdFiles.list()) {
md += `- [${name}](./${name})\n`;
}
const html = htmlDocFromMarkdown(md);
2024-12-23 17:29:38 -06:00
2024-12-25 19:27:36 -06:00
this.router.get('/html', (_req: express.Request, res: express.Response) => {
2024-12-25 16:13:48 -06:00
res.setHeader('content-type', 'text/html').send(html);
2024-12-23 17:29:38 -06:00
});
2024-12-25 16:13:48 -06:00
}
2024-12-23 17:29:38 -06:00
2024-12-25 16:13:48 -06:00
// Serve list of all deltas accepted
// TODO: This won't scale well
2024-12-25 19:27:36 -06:00
this.router.get("/deltas", (_req: express.Request, res: express.Response) => {
2024-12-25 16:13:48 -06:00
res.json(this.rhizomeNode.deltaStream.deltasAccepted);
});
2024-12-25 00:42:16 -06:00
2024-12-25 16:13:48 -06:00
// Get the number of deltas ingested by this node
2024-12-25 19:27:36 -06:00
this.router.get("/deltas/count", (_req: express.Request, res: express.Response) => {
2024-12-25 16:13:48 -06:00
res.json(this.rhizomeNode.deltaStream.deltasAccepted.length);
});
2024-12-23 17:29:38 -06:00
2024-12-25 16:13:48 -06:00
// Get the list of peers seen by this node (including itself)
2024-12-25 19:27:36 -06:00
this.router.get("/peers", (_req: express.Request, res: express.Response) => {
2024-12-25 16:13:48 -06:00
res.json(this.rhizomeNode.peers.peers.map(({reqAddr, publishAddr, isSelf, isSeedPeer}) => {
const deltasAcceptedCount = this.rhizomeNode.deltaStream.deltasAccepted
.filter((delta: Delta) => {
return delta.receivedFrom?.addr == reqAddr.addr &&
delta.receivedFrom?.port == reqAddr.port;
})
.length;
const peerInfo = {
reqAddr: reqAddr.toAddrString(),
publishAddr: publishAddr?.toAddrString(),
isSelf,
isSeedPeer,
deltaCount: {
accepted: deltasAcceptedCount
}
};
return peerInfo;
}));
});
// Get the number of peers seen by this node (including itself)
2024-12-25 19:27:36 -06:00
this.router.get("/peers/count", (_req: express.Request, res: express.Response) => {
2024-12-25 16:13:48 -06:00
res.json(this.rhizomeNode.peers.peers.length);
});
const {httpAddr, httpPort} = this.rhizomeNode.config;
2024-12-25 19:27:36 -06:00
this.server = this.app.listen({
port: httpPort,
host: httpAddr,
exclusive: true
}, () => {
2024-12-25 16:13:48 -06:00
debug(`HTTP API bound to ${httpAddr}:${httpPort}`);
});
2024-12-23 17:29:38 -06:00
}
2024-12-25 16:13:48 -06:00
serveCollection(collection: Collection) {
const {name} = collection;
// Get the ID of all domain entities
2024-12-25 19:27:36 -06:00
this.router.get(`/${name}/ids`, (_req: express.Request, res: express.Response) => {
2024-12-25 16:13:48 -06:00
res.json({ids: collection.getIds()});
});
// Get a single domain entity by ID
2024-12-25 19:27:36 -06:00
this.router.get(`/${name}/:id`, (req: express.Request, res: express.Response) => {
2024-12-25 16:13:48 -06:00
const {params: {id}} = req;
const ent = collection.get(id);
2024-12-25 19:27:36 -06:00
if (!ent) {
res.status(404).send({error: "Not Found"});
return;
}
2024-12-25 16:13:48 -06:00
res.json(ent);
});
// Add a new domain entity
// TODO: schema validation
2024-12-25 19:27:36 -06:00
this.router.put(`/${name}`, (req: express.Request, res: express.Response) => {
const {body: {id, properties}} = req;
const ent = collection.put(id, properties);
2024-12-25 16:13:48 -06:00
res.json(ent);
});
// Update a domain entity
2024-12-25 19:27:36 -06:00
this.router.put(`/${name}/:id`, (req: express.Request, res: express.Response) => {
2024-12-25 16:13:48 -06:00
const {body: properties, params: {id}} = req;
if (properties.id && properties.id !== id) {
res.status(400).json({error: "ID Mismatch", param: id, property: properties.id});
return;
}
const ent = collection.put(id, properties);
res.json(ent);
});
}
async stop() {
this.server?.close();
this.mdFiles.close();
}
2024-12-23 17:29:38 -06:00
}