2024-12-24 13:41:31 -06:00
|
|
|
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";
|
2024-12-24 13:41:31 -06:00
|
|
|
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() {
|
2024-12-26 15:59:03 -06:00
|
|
|
// --------------- deltas ----------------
|
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-26 15:59:03 -06:00
|
|
|
// --------------- peers ----------------
|
|
|
|
|
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);
|
|
|
|
});
|
|
|
|
|
2024-12-26 15:59:03 -06:00
|
|
|
// ----------------- html ---------------------
|
|
|
|
|
|
|
|
// Scan and watch for markdown files
|
|
|
|
this.mdFiles.readDir();
|
|
|
|
this.mdFiles.readReadme();
|
|
|
|
this.mdFiles.watchDir();
|
|
|
|
this.mdFiles.watchReadme();
|
|
|
|
|
|
|
|
// Serve README
|
|
|
|
this.router.get('/html/README', (_req: express.Request, res: express.Response) => {
|
|
|
|
const html = this.mdFiles.getReadmeHTML();
|
|
|
|
res.setHeader('content-type', 'text/html').send(html);
|
|
|
|
});
|
|
|
|
|
|
|
|
// Serve markdown files as html
|
|
|
|
this.router.get('/html/:name', (req: express.Request, res: express.Response) => {
|
|
|
|
const {name} = req.params;
|
|
|
|
let html = this.mdFiles.getHtml(name);
|
|
|
|
if (!html) {
|
|
|
|
res.status(404);
|
|
|
|
html = htmlDocFromMarkdown(`# 404 Not Found: ${name}\n\n ## [Index](/html)`);
|
|
|
|
}
|
|
|
|
res.setHeader('content-type', 'text/html');
|
|
|
|
res.send(html);
|
|
|
|
});
|
|
|
|
|
|
|
|
// Serve index
|
|
|
|
{
|
|
|
|
const html = this.mdFiles.generateIndex();
|
|
|
|
|
|
|
|
this.router.get('/html', (_req: express.Request, res: express.Response) => {
|
2024-12-26 16:52:46 -06:00
|
|
|
res.setHeader('content-type', 'text/html').send(this.mdFiles.indexHtml);
|
2024-12-26 15:59:03 -06:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// ------------------- server ---------------------
|
|
|
|
|
2024-12-25 16:13:48 -06:00
|
|
|
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
|
|
|
}
|