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; export const get = async (nickname: string) => db("users") .where("nickname", nickname) .first() ; export const getById = async (id: number) => db("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 => db("users") .select("nickname") .where("id", userId) .first() .then((rec) => !!rec ? rec.nickname : null) ; export const getId = async (nickname: string): Promise => 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 => { 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 => getFollowers(userId).then((followers) => { const inboxes: Set = 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";