118 lines
3.2 KiB
TypeScript
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";
|