activitypress/src/user.ts

118 lines
3.2 KiB
TypeScript

import { generateKeyPair } from "node:crypto";
import { z } from "zod";
import db from "./db.js";
import { getFollowers } from "./follower.js";
import { fillRoute } from "./router.js";
export const nicknameRegex = /^[a-zA-Z0-9_]+$/;
const ACTOR_TYPES = [
"Application",
"Service",
"Person"
] as const;
export const zUser = z.object({
id: z.number().min(0),
actor_type: z.enum(ACTOR_TYPES),
name: z.string().min(1),
nickname: z.string().regex(nicknameRegex),
bio: z.string(),
public_key: z.string(),
private_key: z.string(),
deleted: z.boolean(),
created_at: z.date(),
updated_at: z.union([z.date(), z.null()])
});
export type User = z.infer<typeof zUser>;
export const get = async (nickname: string) =>
db<User>("users")
.where("nickname", nickname)
.first()
;
export const getById = async (id: number) =>
db<User>("users")
.where("id", id)
.first()
;
const EXTRACT_NICKNAME = new RegExp(fillRoute("actor", "(.+)").replaceAll("/", "\\/"));
export const getByActor = async (actor: string) => {
const matchArray = actor.match(EXTRACT_NICKNAME);
if (matchArray) {
const nickname = matchArray[1];
return get(nickname);
}
else return null;
};
export const getNickname = async (userId: number): Promise<string | null> =>
db("users")
.select("nickname")
.where("id", userId)
.first()
.then((rec) => !!rec ? rec.nickname : null)
;
export const getId = async (nickname: string): Promise<number | null> =>
db("users")
.select("id")
.where("nickname", nickname)
.first()
.then((rec) => !!rec ? rec.id : null)
;
export const newUser = async (nickname: string, name: string, bio: string, actorType = "Person", id?: number): Promise<number> => {
const { public_key, private_key } = await (new Promise((resolve, reject) => {
generateKeyPair("rsa", {
modulusLength: 2048,
publicKeyEncoding: {
type: "spki",
format: "pem"
},
privateKeyEncoding: {
type: "pkcs8",
format: "pem"
}
}, (err, public_key, private_key) => {
if (err) reject(err);
else resolve({ public_key, private_key });
})
}) as Promise<{ public_key: string, private_key: string }>);
return db("users")
.insert({
...(typeof id === "undefined" ? {} : { id }),
actor_type: actorType,
name,
nickname,
bio,
public_key,
private_key,
deleted: false,
created_at: new Date()
})
.returning("id")
.then(([{ id: id }]: { id: number }[]) => id)
;
};
export const getFollowerInboxes = async (userId: number): Promise<string[]> =>
getFollowers(userId).then((followers) => {
const inboxes: Set<string> = new Set();
for (const { inbox, shared_inbox } of followers) {
const res = shared_inbox || inbox;
if (res) inboxes.add(res);
}
return [...inboxes];
});
export const keyIdFromNickname = (nickname: string) => fillRoute("actor", nickname) + "#main-key";