-
Notifications
You must be signed in to change notification settings - Fork 0
/
user.server.ts
146 lines (113 loc) · 3.95 KB
/
user.server.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
import type { z } from "zod";
import bcrypt from "bcryptjs";
import cuid from "cuid";
import { DocType, UserIdSchema } from "./models/model";
import { ConflictError, NotFoundError, UnauthorizedError } from "./models/err";
import type { PasswordDoc } from "./models/password";
import { PasswordDocSchema, PasswordObjSchema, PasswordSchema } from "./models/password";
import type { User, UserDoc } from "./models/user";
import { EmailSchema, UserDocSchema, UserSchema } from "./models/user";
import { fromHyper, toHyper } from "./hyper";
import type { ServerContext, UserServer } from "./types";
export const UserServerFactory = (env: ServerContext): UserServer => {
async function getUserById(id: string): ReturnType<UserServer["getUserById"]> {
const { hyper } = env;
id = UserIdSchema.parse(id);
const res = await hyper.data.get(id);
if (!res.ok && res.status === 404) {
return null;
}
return fromHyper.as(UserSchema)(res);
}
async function getUserByEmail(email: string): ReturnType<UserServer["getUserByEmail"]> {
const { hyper } = env;
email = EmailSchema.parse(email);
const res = await hyper.data.query<UserDoc>({ type: "user", email });
if (!res.ok) {
throw new Error(res.msg);
}
const { docs } = res;
if (!docs.length) {
return null;
}
const user = docs.pop() as UserDoc;
return user && fromHyper.as(UserSchema)(user);
}
async function getUserAndPassword(
email: string
): Promise<{ user: User; password: z.infer<typeof PasswordObjSchema> }> {
const { hyper } = env;
email = EmailSchema.parse(email);
const user = await getUserByEmail(email);
if (!user) {
throw new NotFoundError(`user with email ${email} not found`);
}
const res = await hyper.data.query<PasswordDoc>({
type: "password",
parent: user.id,
});
if (!res.ok) {
throw new Error(res.msg);
}
const { docs } = res;
if (!docs.length) {
// TODO: use hyper queue to create job to send alert
console.error("A user without a password. Send an alert to a monitoring system...");
throw new NotFoundError(`password not found for user with email ${email}`);
}
return {
user,
password: fromHyper.as(PasswordObjSchema)(docs.pop()),
};
}
async function createUser(email: string, password: string): ReturnType<UserServer["createUser"]> {
const { hyper } = env;
email = EmailSchema.parse(email);
const exists = await getUserByEmail(email);
if (exists) {
throw new ConflictError(`user with email ${email} already exists`);
}
const userId = `user-${cuid()}`;
const user = UserSchema.parse({ id: userId, email });
const pw = PasswordSchema.parse(password.trim());
await hyper.data.bulk([
toHyper.as(UserDocSchema)({ ...user, type: DocType.enum.user }),
toHyper.as(PasswordDocSchema)({
id: `password-${cuid()}`,
parent: userId,
hash: await bcrypt.hash(pw, 10),
type: DocType.enum.password,
}),
]);
return user;
}
async function deleteUser(email: string): ReturnType<UserServer["deleteUser"]> {
const { hyper, NoteServer } = env;
const { user, password } = await getUserAndPassword(email);
const notes = await NoteServer.getNotesByParent({ parent: user.id });
// delete all user's notes docs
await Promise.all(notes.map((n) => NoteServer.deleteNote(n)));
// delete the password doc
await hyper.data.remove(password.id);
// delete user doc
await hyper.data.remove(user.id);
}
async function verifyLogin(
email: string,
password: string
): ReturnType<UserServer["verifyLogin"]> {
const { user, password: pw } = await getUserAndPassword(email);
const isValid = await bcrypt.compare(password, pw.hash);
if (!isValid) {
throw new UnauthorizedError();
}
return user;
}
return {
getUserById,
getUserByEmail,
createUser,
deleteUser,
verifyLogin,
};
};