import express, { Request } from "express"; import bodyParser from "body-parser"; import { toCollection as getOutbox } from "./outbox.js"; import { getById as getUserById, getNickname, get as getUserByNickname, getId as getUserId, User } from "./user.js"; import { Routes } from "./router.js"; import { getBySlug as getArticleBySlug, getById, getByUserId } from "./article.js"; import { userToPerson, TYPE as ACTIVITYPUB_TYPE, handleInboxPost, createArticleObject, CONTEXT, createArticleActivity } from "./activity.js"; import { handleFollowerGet } from "./follower.js"; import { handleWebfingerGet } from "./net.js"; const escapeHtml = (unsafe: string) => unsafe .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll("\"", """) .replace("'", "'"); const port = parseInt(process.env.port || "8080"); const app = express(); app.use( bodyParser.text({ type: "*/*" }) ); app.post(Routes.inbox, handleInboxPost); app.get(Routes.followers, handleFollowerGet); app.get(Routes.object, async (req, res) => { const article = await getById(parseInt(req.params.id)); if (article) { if (req.accepts("html")) { res.redirect(`/${article.slug}.html`); } else if (req.accepts(["text/plain", "text/markdown"])) { res.redirect(`/${article.slug}.md`); } else { const nickname = await getNickname(article.users_id) as string; const obj = createArticleObject(article, nickname); obj["@context"] = CONTEXT; res.append("Content-Type", ACTIVITYPUB_TYPE); res.send(JSON.stringify(obj, null, 4)); } } else { res.status(404).end(); } }); app.get(Routes.activity, async (req, res) => { let id = parseInt(req.params.id); if (id >= 1_000_000_000) { // it's a delete. TODO: implement. res.status(501).end(); } else { const article = await getById(id); if (article) { if (req.accepts("html")) { res.redirect(`/${article.slug}.html`); } else if (req.accepts(["text/plain", "text/markdown"])) { res.redirect(`/${article.slug}.md`); } else { const user = await getUserById(article.users_id) as User; const activity = createArticleActivity(article, user); res.append("Content-Type", ACTIVITYPUB_TYPE); res.send(JSON.stringify(activity, null, 4)); } } else { res.status(404).end(); } } }); app.get("/.well-known/webfinger", handleWebfingerGet); // app.get("/.well-known/nodeinfo", SITE.net.nodeInfoLocation.get); // app.get("/nodeinfo/:version", SITE.net.nodeInfo.get); app.get(Routes.outbox, async (req: Request<{ actor: string }>, res) => { const body = await getOutbox(req.params.actor); if (body) { res.append("Content-Type", ACTIVITYPUB_TYPE); res.send(body); } else { res.status(404).send("not found"); } }); app.get(Routes.actor, async (req, res) => { const nickname = req.params.actor; const actor = await getUserByNickname(nickname); if (actor) { if (req.accepts("html")) { const posts = (await getByUserId(actor.id)).map((a) => `
  • ${escapeHtml(a.title)}
  • `).join(""); const body = ` username: ${escapeHtml(actor.nickname)}
    Name: ${escapeHtml(actor.name)}

    Bio: ${escapeHtml(actor.bio)}

    Posts:

    `; res.set("Content-Type", "text/html; charset=utf-8"); res.send(body); } else { const obj = userToPerson(actor); const body = JSON.stringify(obj, null, 4); res.set("Content-Type", ACTIVITYPUB_TYPE); res.send(body); } } else { res.status(404).send("actor not found"); } }); app.get("/:slug.html", async (req, res) => { const article = await getArticleBySlug(req.params.slug); if (!article || article.deleted) { res.status(404).send("not found"); } else { const filename = article.file.replace(/\.md$/, ".html"); res.sendFile(process.env.base_dir + "/" + filename); } }); app.get("/:slug.md", async (req, res) => { const article = await getArticleBySlug(req.params.slug); if (!article || article.deleted) { res.status(404).send("not found"); } else { res.sendFile(process.env.base_dir + "/" + article.file); } }); const SERVER = app.listen(port);