activitypress/src/user.ts

118 lines
3.2 KiB
TypeScript
Raw Normal View History

2023-12-26 02:33:18 +00:00
import { generateKeyPair } from "node:crypto";
2023-12-25 18:49:16 +00:00
import { z } from "zod";
2023-12-26 02:33:18 +00:00
import db from "./db.js";
2023-12-27 14:18:26 +00:00
import { getFollowers } from "./follower.js";
2023-12-26 13:06:38 +00:00
import { fillRoute } from "./router.js";
2023-12-25 18:49:16 +00:00
export const nicknameRegex = /^[a-zA-Z0-9_]+$/;
2024-01-01 12:55:57 +00:00
const ACTOR_TYPES = [
"Application",
"Service",
"Person"
] as const;
2023-12-25 18:49:16 +00:00
export const zUser = z.object({
id: z.number().min(0),
2024-01-01 12:55:57 +00:00
actor_type: z.enum(ACTOR_TYPES),
2023-12-25 18:49:16 +00:00
name: z.string().min(1),
nickname: z.string().regex(nicknameRegex),
bio: z.string(),
2023-12-26 02:33:18 +00:00
public_key: z.string(),
private_key: z.string(),
2023-12-25 18:49:16 +00:00
deleted: z.boolean(),
created_at: z.date(),
updated_at: z.union([z.date(), z.null()])
});
export type User = z.infer<typeof zUser>;
2023-12-26 02:33:18 +00:00
export const get = async (nickname: string) =>
db<User>("users")
.where("nickname", nickname)
.first()
;
2023-12-27 14:18:26 +00:00
export const getById = async (id: number) =>
db<User>("users")
.where("id", id)
.first()
;
2023-12-27 16:21:16 +00:00
const EXTRACT_NICKNAME = new RegExp(fillRoute("actor", "(.+)").replaceAll("/", "\\/"));
2023-12-26 13:06:38 +00:00
export const getByActor = async (actor: string) => {
const matchArray = actor.match(EXTRACT_NICKNAME);
if (matchArray) {
const nickname = matchArray[1];
return get(nickname);
}
else return null;
};
2023-12-25 18:49:16 +00:00
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)
;
2024-01-01 12:44:58 +00:00
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) => {
2023-12-26 02:33:18 +00:00
generateKeyPair("rsa", {
modulusLength: 2048,
publicKeyEncoding: {
type: "spki",
format: "pem"
},
privateKeyEncoding: {
type: "pkcs8",
format: "pem"
}
2024-01-01 12:44:58 +00:00
}, (err, public_key, private_key) => {
2023-12-26 02:33:18 +00:00
if (err) reject(err);
2024-01-01 12:44:58 +00:00
else resolve({ public_key, private_key });
2023-12-26 02:33:18 +00:00
})
2024-01-01 12:44:58 +00:00
}) as Promise<{ public_key: string, private_key: string }>);
2023-12-26 02:33:18 +00:00
2023-12-25 18:49:16 +00:00
return db("users")
.insert({
2024-01-01 12:44:58 +00:00
...(typeof id === "undefined" ? {} : { id }),
actor_type: actorType,
2023-12-25 18:49:16 +00:00
name,
nickname,
bio,
2024-01-01 12:44:58 +00:00
public_key,
private_key,
2023-12-25 18:49:16 +00:00
deleted: false,
created_at: new Date()
})
.returning("id")
.then(([{ id: id }]: { id: number }[]) => id)
;
};
2023-12-26 02:33:18 +00:00
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];
});
2023-12-27 14:18:26 +00:00
2024-01-01 12:48:55 +00:00
export const keyIdFromNickname = (nickname: string) => fillRoute("actor", nickname) + "#main-key";