activitypress/src/index.ts

162 lines
4.3 KiB
TypeScript

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("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll("\"", "&quot;")
.replace("'", "&#039;");
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) => `<li><a href="/${a.slug}.html">${escapeHtml(a.title)}</a></li>`).join("");
const body = `<!DOCTYPE html>
<html>
username: ${escapeHtml(actor.nickname)}
<br>
Name: ${escapeHtml(actor.name)}
<p>
Bio: ${escapeHtml(actor.bio)}
<p>
Posts:<br>
<ul>
${posts}
</ul>
`;
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);