From b76eb76e1aa0444d08db14b9160bd62f03a8b5be Mon Sep 17 00:00:00 2001 From: Gordon Pedersen Date: Wed, 27 Sep 2023 14:13:10 +1000 Subject: [PATCH] Utilize 11ty to build static pages and modify server to serve them --- .eleventy.js | 10 ++++++++++ .gitignore | 3 ++- bun.lockb | Bin 6098 -> 79902 bytes package.json | 1 + src/activitypub.ts | 13 +++++++------ src/admin.ts | 9 ++++++++- src/db.ts | 37 ++++++++++++++++++++----------------- src/env.ts | 23 +++++++++++++++++------ src/index.ts | 33 +++++++++++++++++++++++++++++++-- src/outbox.ts | 2 +- 10 files changed, 97 insertions(+), 34 deletions(-) create mode 100644 .eleventy.js diff --git a/.eleventy.js b/.eleventy.js new file mode 100644 index 0000000..e74cd36 --- /dev/null +++ b/.eleventy.js @@ -0,0 +1,10 @@ +module.exports = function(eleventyConfig) { + eleventyConfig.setUseGitIgnore(false) + // Return your Object options: + return { + dir: { + input: "_content", + output: "_site" + } + } +}; \ No newline at end of file diff --git a/.gitignore b/.gitignore index 8dddefc..06100bd 100644 --- a/.gitignore +++ b/.gitignore @@ -169,4 +169,5 @@ dist .pnp.\* # My custom ignores -_content \ No newline at end of file +_content +_site diff --git a/bun.lockb b/bun.lockb index 97de569746978c028abd9852a04dca55dff7060c..62950fad4c88159ed3c93e65c2656f5a74c307d9 100755 GIT binary patch literal 79902 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 z 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 delta 990 zcmcIjUr5tY6u$SjKW@uyzb0Z^Q4o7Dp|u#fnzq$eOsR0%9|a>UbYYW<>`4$;L^Lhf zI1fn^Z0}h+>Md zpcB7pkZs7Tkdw$F@-(szInaH&bMqNL>){2R;gPBJCbO)ts|Hu8ag`ByHS#AJILiF+ zS=+%Ipi$@Imms8bDbiSO0Ij}`SA$ybQukrAT3oJ#h|bRA5W*Z67;|HeL1gWAP>U}0 zBxY!p93px<{{kV*S#WSY<~Z$CpF-s*DdDyZ20cz;Sq5_+$AAK7QV@vc7`OjC$yZmREZ4a`d?534z!H7orvXAD^ny4&XX#A{o0UXoia>#PlG% zahZPQ3!rvn*Slg_6v6R!qnlhJNAba=X;dY1lo(|gDhzU8i$SkLE$CHt6|D`5Ft<>H zcl^0v*HJNEEcgu2k_u?3w*(t3rHtGO+0=n;)0m-a&cwsv#miq+0zbCZVzpQqOJW5Z z1W#yZk3RJ5eekE!1N~{6kl`?q-p|dDPa6gNQAe|DG-HOsgtP%3XWdOCx_ZwZKiAvE zUbvl8S#SH3D|ifQ`+RUB_te)sK8%|FTp!L~>=rv47jL4jKz%3BKIHfAUMHd;II0*7 R<=PV8b1ky_OSOC_{|zgx!)O2i diff --git a/package.json b/package.json index 45375fc..55d599b 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "typescript": "^5.0.0" }, "dependencies": { + "@11ty/eleventy": "^2.0.1", "gray-matter": "^4.0.3", "node-forge": "^1.3.1" } diff --git a/src/activitypub.ts b/src/activitypub.ts index f86877c..af71645 100644 --- a/src/activitypub.ts +++ b/src/activitypub.ts @@ -8,7 +8,7 @@ export default (req: Request): Response | Promise | undefined => { const url = new URL(req.url) let match - if(req.method === "GET" && url.pathname === "/test") return new Response("", { status: 204 }) + if(!reqIsActivityPub(req)) return undefined // else if(req.method == "GET" && (match = url.pathname.match(/^\/([^\/]+)\/?$/i))) return getActor(req, match[1]) // else if(req.method == "GET" && (match = url.pathname.match(/^\/([^\/]+)\/outbox\/?$/i))) return getOutbox(req, match[1]) @@ -26,7 +26,8 @@ export default (req: Request): Response | Promise | undefined => { else if(req.method == "GET" && (match = url.pathname.match(/^\/followers\/?$/i))) return getFollowers(req, ACCOUNT) else if(req.method == "GET" && (match = url.pathname.match(/^\/following\/?$/i))) return getFollowing(req, ACCOUNT) else if(req.method == "GET" && (match = url.pathname.match(/^\/posts\/([^\/]+)\/?$/i))) return getPost(req, ACCOUNT, match[1]) - else if(req.method == "GET" && (match = url.pathname.match(/^\/posts\/([^\/]+)\/activity\/?$/i))) return getActivity(req, ACCOUNT, match[1]) + else if(req.method == "GET" && (match = url.pathname.match(/^\/posts\/([^\/]+)\/activity\/?$/i))) return getOutboxActivity(req, ACCOUNT, match[1]) + else if(req.method == "GET" && (match = url.pathname.match(/^\/outbox\/([^\/]+)\/?$/i))) return getOutboxActivity(req, ACCOUNT, match[1]) return undefined } @@ -190,13 +191,13 @@ const getPost = async (req:Request, account:string, id:string):Promise console.log("GetPost", account, id) if (ACCOUNT !== account) return new Response("", { status: 404 }) - if(reqIsActivityPub(req)) return Response.json((await db.getActivity(id)).object, { headers: { "Content-Type": "application/activity+json"}}) + if(reqIsActivityPub(req)) return Response.json((await db.getOutboxActivity(id)).object, { headers: { "Content-Type": "application/activity+json"}}) else return Response.json(await db.getPost(id)) } -const getActivity = async (req:Request, account:string, id:string):Promise => { - console.log("GetActivity", account, id) +const getOutboxActivity = async (req:Request, account:string, id:string):Promise => { + console.log("GetOutboxActivity", account, id) if (ACCOUNT !== account) return new Response("", { status: 404 }) - return Response.json((await db.getActivity(id)), { headers: { "Content-Type": "application/activity+json"}}) + return Response.json((await db.getOutboxActivity(id)), { headers: { "Content-Type": "application/activity+json"}}) } \ No newline at end of file diff --git a/src/admin.ts b/src/admin.ts index 6d3ac53..3df6a4a 100644 --- a/src/admin.ts +++ b/src/admin.ts @@ -1,6 +1,6 @@ import { idsFromValue } from "./activitypub" import * as db from "./db" -import { ACTOR, ADMIN_PASSWORD, ADMIN_USERNAME, BASE_URL } from "./env" +import { ACTOR, ADMIN_PASSWORD, ADMIN_USERNAME, BASE_URL, CONTENT_PATH, STATIC_PATH } from "./env" import outbox from "./outbox" import { activityMimeTypes, fetchObject } from "./request" @@ -14,6 +14,7 @@ export default (req: Request): Response | Promise | undefined => { let match if(req.method === "GET" && (match = url.pathname.match(/^\/test\/?$/i))) return new Response("", { status: 204 }) + else if(req.method == "POST" && (match = url.pathname.match(/^\/rebuild\/?$/i))) return rebuild(req) else if(req.method == "POST" && (match = url.pathname.match(/^\/create\/?$/i))) return create(req) else if(req.method == "POST" && (match = url.pathname.match(/^\/follow\/([^\/]+)\/?$/i))) return follow(req, match[1]) else if(req.method == "DELETE" && (match = url.pathname.match(/^\/follow\/([^\/]+)\/?$/i))) return unfollow(req, match[1]) @@ -41,6 +42,12 @@ const checkAuth = (headers: Headers): Boolean => { return username === ADMIN_USERNAME && password === ADMIN_PASSWORD } +// rebuild the 11ty static pages +export const rebuild = async(req:Request):Promise => { + await db.rebuild() + return new Response("", { status: 201 }) +} + // create an activity const create = async (req:Request, inReplyTo:string|null = null):Promise => { const body = await req.json() diff --git a/src/db.ts b/src/db.ts index da4f115..722ec9f 100644 --- a/src/db.ts +++ b/src/db.ts @@ -1,21 +1,16 @@ -import { ACTIVITY_INBOX_PATH, ACTIVITY_OUTBOX_PATH, ACTIVITY_PATH, ACTOR, BASE_URL, DATA_PATH, POSTS_PATH } from "./env"; +import { ACTIVITY_INBOX_PATH, ACTIVITY_OUTBOX_PATH, ACTOR, CONTENT_PATH, DATA_PATH, POSTS_PATH, STATIC_PATH } from "./env"; import path from "path" import { readdir } from "fs/promises" import { unlinkSync } from "node:fs" import { fetchObject } from "./request"; import { idsFromValue } from "./activitypub"; const matter = require('gray-matter') +const Eleventy = require("@11ty/eleventy") -export async function doActivity(activity:any, object_id:string|null|undefined) { - if(activity.type === "Create" && activity.object) { - if(!object_id) object_id = new Date(activity.object.published).getTime().toString(16) - const file = Bun.file(path.join(POSTS_PATH, `${object_id}.md`)) - const { content, published, id, attributedTo } = activity.object - //TODO: add appropriate content for different types (e.g. like-of, etc) - await Bun.write(file, matter.stringify(content || "", { id, published, attributedTo })) - const activityFile = Bun.file(path.join(ACTIVITY_PATH, `${object_id}.activity.json`)) - await Bun.write(activityFile, JSON.stringify(activity)) - } +// rebuild the 11ty static pages +export async function rebuild() { + console.info(`Building 11ty from ${CONTENT_PATH}, to ${STATIC_PATH}`) + await new Eleventy(CONTENT_PATH, STATIC_PATH, { configPath: '.eleventy.js' }).write() } export async function createInboxActivity(activity:any, object_id:any) { @@ -82,6 +77,7 @@ export async function createPost(post_object:any, object_id:string) { if(inReplyTo) data.inReplyTo = idsFromValue(inReplyTo).at(0) await Bun.write(file, matter.stringify((reply_content || "") + (content || ""), data)) } + rebuild() } export async function getPost(id:string) { @@ -95,7 +91,7 @@ export async function getPost(id:string) { } export async function getPostByURL(url_id:string) { - if(!url_id || !url_id.startsWith(ACTOR + '/post/')) return null + if(!url_id || !url_id.startsWith(ACTOR + '/posts/')) return null const match = url_id.match(/\/([0-9a-f]+)\/?$/) const local_id = match ? match[1] : url_id return await getPost(local_id) @@ -103,28 +99,26 @@ export async function getPostByURL(url_id:string) { export async function deletePost(id:string) { unlinkSync(path.join(POSTS_PATH, id + '.md')) + rebuild() } export async function listPosts() { return await Promise.all((await readdir(POSTS_PATH)).filter(v => v.endsWith('.md')).map(async filename => await getPost(filename.slice(0, -3)))) } -export async function getActivity(id:string) { - const file = Bun.file(path.join(ACTIVITY_PATH, `${id}.activity.json`)) - return await file.json() -} - export async function createFollowing(handle:string, id:string) { const file = Bun.file(path.join(DATA_PATH, `following.json`)) const following_list = await file.json() as Array if(!following_list.find(v => v.id === id || v.handle === handle)) following_list.push({id, handle, createdAt: new Date().toISOString()}) await Bun.write(file, JSON.stringify(following_list)) + rebuild() } export async function deleteFollowing(handle:string) { const file = Bun.file(path.join(DATA_PATH, `following.json`)) const following_list = await file.json() as Array await Bun.write(file, JSON.stringify(following_list.filter(v => v.handle !== handle))) + rebuild() } export async function getFollowing(handle:string) { @@ -144,6 +138,7 @@ export async function acceptFollowing(handle:string) { const following = following_list.find(v => v.handle === handle) if(following) following.accepted = new Date().toISOString() await Bun.write(file, JSON.stringify(following_list)) + rebuild() } export async function createFollower(actor:string, id:string) { @@ -151,12 +146,14 @@ export async function createFollower(actor:string, id:string) { const followers_list = await file.json() as Array if(!followers_list.find(v => v.id === id || v.actor === actor)) followers_list.push({id, actor, createdAt: new Date().toISOString()}) await Bun.write(file, JSON.stringify(followers_list)) + rebuild() } export async function deleteFollower(actor:string) { const file = Bun.file(path.join(DATA_PATH, `followers.json`)) const followers_list = await file.json() as Array await Bun.write(file, JSON.stringify(followers_list.filter(v => v.actor !== actor))) + rebuild() } export async function getFollower(actor:string) { @@ -175,12 +172,14 @@ export async function createLiked(object_id:string, id:string) { const liked_list = await file.json() as Array if(!liked_list.find(v => v.object_id === object_id)) liked_list.push({id, object_id, createdAt: new Date().toISOString()}) await Bun.write(file, JSON.stringify(liked_list)) + rebuild() } export async function deleteLiked(object_id:string) { const file = Bun.file(path.join(DATA_PATH, `liked.json`)) const liked_list = await file.json() as Array await Bun.write(file, JSON.stringify(liked_list.filter(v => v.object_id !== object_id))) + rebuild() } export async function listLiked() { @@ -193,12 +192,14 @@ export async function createDisliked(object_id:string, id:string) { const disliked_list = await file.json() as Array if(!disliked_list.find(v => v.object_id === object_id)) disliked_list.push({id, object_id, createdAt: new Date().toISOString()}) await Bun.write(file, JSON.stringify(disliked_list)) + rebuild() } export async function deleteDisliked(object_id:string) { const file = Bun.file(path.join(DATA_PATH, `disliked.json`)) const disliked_list = await file.json() as Array await Bun.write(file, JSON.stringify(disliked_list.filter(v => v.object_id !== object_id))) + rebuild() } export async function listDisliked() { @@ -211,12 +212,14 @@ export async function createShared(object_id:string, id:string) { const shared_list = await file.json() as Array if(!shared_list.find(v => v.object_id === object_id)) shared_list.push({id, object_id, createdAt: new Date().toISOString()}) await Bun.write(file, JSON.stringify(shared_list)) + rebuild() } export async function deleteShared(object_id:string) { const file = Bun.file(path.join(DATA_PATH, `shared.json`)) const shared_list = await file.json() as Array await Bun.write(file, JSON.stringify(shared_list.filter(v => v.object_id !== object_id))) + rebuild() } export async function listShared() { diff --git a/src/env.ts b/src/env.ts index 97e9200..6b30c42 100644 --- a/src/env.ts +++ b/src/env.ts @@ -2,16 +2,16 @@ import forge from "node-forge" // import crypto from "node:crypto" import path from "path" // change "activitypub" to whatever you want your account name to be -export const ACCOUNT = Bun.env.ACCOUNT || "activitypub" +export const ACCOUNT = process.env.ACCOUNT || "activitypub" // set up username and password for admin actions export const ADMIN_USERNAME = process.env.ADMIN_USERNAME || ""; export const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || ""; // get the hostname (`PROJECT_DOMAIN` is set via glitch, but since we're using Bun now, this won't matter) -export const HOSTNAME = /*(Bun.env.PROJECT_DOMAIN && `${Bun.env.PROJECT_DOMAIN}.glitch.me`) ||*/ Bun.env.HOSTNAME || "localhost" -export const NODE_ENV = Bun.env.NODE_ENV || "development" -export const PORT = Bun.env.PORT || "3000" +export const HOSTNAME = /*(process.env.PROJECT_DOMAIN && `${process.env.PROJECT_DOMAIN}.glitch.me`) ||*/ process.env.HOSTNAME || "localhost" +export const NODE_ENV = process.env.NODE_ENV || "development" +export const PORT = process.env.PORT || "3000" export const BASE_URL = (HOSTNAME === "localhost" ? "http://" : "https://") + HOSTNAME @@ -32,9 +32,20 @@ export const PRIVATE_KEY = (keypair && forge.pki.privateKeyToPem(keypair.privateKey)) || //keypair?.privateKey.export({ type: "pkcs8", format: "pem" }) || "" +export const STATIC_PATH = path.join('.', '_site') export const CONTENT_PATH = path.join('.', '_content') export const POSTS_PATH = path.join(CONTENT_PATH, "posts") -export const ACTIVITY_PATH = path.join(CONTENT_PATH, "posts") export const DATA_PATH = path.join(CONTENT_PATH, "_data") export const ACTIVITY_INBOX_PATH = path.join(DATA_PATH, "_inbox") -export const ACTIVITY_OUTBOX_PATH = path.join(DATA_PATH, "_outbox") \ No newline at end of file +export const ACTIVITY_OUTBOX_PATH = path.join(DATA_PATH, "_outbox") + +export const DEFAULT_DOCUMENTS = process.env.DEFAULT_DOCUMENTS || [ + 'index.html', + 'index.shtml', + 'index.htm', + 'Index.html', + 'Index.shtml', + 'Index.htm', + 'default.html', + 'default.htm' +] \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index d2fb3f9..c7758e8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,10 @@ -import { ACCOUNT, ACTOR, HOSTNAME, PORT } from "./env"; +import { ACCOUNT, ACTOR, DEFAULT_DOCUMENTS, HOSTNAME, PORT, STATIC_PATH } from "./env"; import admin from './admin' import activitypub from "./activitypub"; import { fetchObject } from "./request"; +import path from "path" +import { BunFile } from "bun"; +const { stat } = require("fs").promises const server = Bun.serve({ port: 3000, @@ -20,7 +23,7 @@ const server = Bun.serve({ return fetchObject(ACTOR, object_url) } - return admin(req) || activitypub(req) || new Response("How did we get here?", { status: 404 }) + return admin(req) || activitypub(req) || staticFile(req) }, }); @@ -45,5 +48,31 @@ const webfinger = async (req: Request, resource: string | null) => { ], }, { headers: { "content-type": "application/activity+json" }}) } + +const getDefaultDocument = async(base_path: string) => { + for(const d of DEFAULT_DOCUMENTS){ + const filePath = path.join(base_path, d) + const file = Bun.file(filePath) + if(await file.exists()) return file + } +} + +const staticFile = async (req:Request): Promise => { + try{ + const url = new URL(req.url) + const filePath = path.join(STATIC_PATH, url.pathname) + let file:BunFile|undefined = Bun.file(filePath) + // if the file doesn't exist, attempt to get the default document for the path + if(!(await file.exists())) file = await getDefaultDocument(filePath) + + if(file && await file.exists()) return new Response(file) + // if the file still doesn't exist, just return a 404 + else return new Response("", { status: 404 }) + } + catch(err) { + console.error(err) + return new Response("", { status: 404 }) + } +} console.log(`Listening on http://localhost:${server.port} ...`); \ No newline at end of file diff --git a/src/outbox.ts b/src/outbox.ts index cdeccb8..5217614 100644 --- a/src/outbox.ts +++ b/src/outbox.ts @@ -68,7 +68,7 @@ export default async function outbox(activity:any):Promise { } async function create(activity:any, id:string) { - activity.object.id = activity.object.url = `${ACTOR}/post/${id}` + activity.object.id = activity.object.url = `${ACTOR}/posts/${id}` await db.createPost(activity.object, id) return true }