103 lines
3.9 KiB
TypeScript
103 lines
3.9 KiB
TypeScript
import { readFileSync } from "node:fs";
|
|
import db from "./db.js";
|
|
import { z } from "zod";
|
|
import { getId as getUserId, getActor } from "./user.js";
|
|
import { routes } from "./index.js";
|
|
import { getById as getArticleById } from "./article.js";
|
|
|
|
|
|
export const zRawOutboxRecord = z.object({
|
|
id: z.number().min(0),
|
|
users_id: z.number().min(0),
|
|
verb: z.string().regex(/^[a-zA-Z]+$/).min(1),
|
|
articles_id: z.number().min(0),
|
|
created_at: z.date(),
|
|
updated_at: z.union([z.date(), z.null()]),
|
|
slug: z.optional(z.string()),
|
|
file: z.optional(z.string()),
|
|
title: z.optional(z.string())
|
|
});
|
|
|
|
export type RawOutboxRecord = z.infer<typeof zRawOutboxRecord>;
|
|
|
|
export const get = async (nickname: string): Promise<Record<string, any> | null> => {
|
|
const userId = await getUserId(nickname);
|
|
|
|
if (userId) {
|
|
const actor = getActor(nickname);
|
|
|
|
const ret = db<RawOutboxRecord>("outboxes")
|
|
.select("outboxes.*", "slug", "file", "title")
|
|
.join("articles", "outboxes.articles_id", "=", "articles.id")
|
|
.where("outboxes.users_id", userId)
|
|
.orderBy("created_at", "desc")
|
|
.then((recs: RawOutboxRecord[]) => {
|
|
const activities: Record<string, any>[] = recs.map((rec) => {
|
|
const published = typeof rec.created_at === "number" ? new Date(rec.created_at) : rec.created_at;
|
|
const canonicalUrl = `https://${process.env.blog_host}/${rec.slug}.html`;
|
|
const context = `https://${process.env.blog_host}/c/${rec.articles_id}`;
|
|
const followers = `https://${process.env.blog_host}${routes.followers.replace(":actor", nickname)}`;
|
|
const activity: Record<string, any> = {
|
|
id: `https://${process.env.blog_host}${routes.activity.replace(":id", rec.id.toString())}`,
|
|
"@context": "https://www.w3.org/ns/activitystreams",
|
|
type: rec.verb,
|
|
actor,
|
|
context,
|
|
to: ["https://www.w3.org/ns/activitystreams#Public"],
|
|
cc: [followers],
|
|
published
|
|
};
|
|
|
|
const objectId = `https://${process.env.blog_host}${routes.object.replace(":id", rec.articles_id.toString())}`;
|
|
const content = readFileSync(rec.file as string, "utf-8");
|
|
activity.object = {
|
|
id: objectId,
|
|
actor,
|
|
attributedTo: actor,
|
|
type: "Article",
|
|
context,
|
|
content,
|
|
to: ["https://www.w3.org/ns/activitystreams#Public"],
|
|
cc: [followers],
|
|
url: canonicalUrl,
|
|
mediaType: "text/markdown",
|
|
published
|
|
};
|
|
|
|
return activity;
|
|
});
|
|
|
|
const collection = {
|
|
type: "OrderedCollectionPage",
|
|
context: "https://www.w3.org/ns/activitystreams",
|
|
id: `https://${process.env.blog_host}${routes.outbox.replace(":actor", nickname)}`,
|
|
orderedItems: activities
|
|
};
|
|
|
|
return collection;
|
|
});
|
|
|
|
return ret;
|
|
}
|
|
else {
|
|
return null;
|
|
}
|
|
};
|
|
|
|
export const add = async (articleId: number): Promise<number> => {
|
|
const article = await getArticleById(articleId);
|
|
|
|
if (article) {
|
|
return await db("outboxes").insert({
|
|
users_id: article.id,
|
|
verb: "Create",
|
|
articles_id: articleId,
|
|
created_at: article.created_at
|
|
})
|
|
.returning("id")
|
|
.then(([{ id: id }]: { id: number }[]) => id)
|
|
|
|
}
|
|
else throw "failed to add to outbox";
|
|
};
|