返回 DeepSeek-Reasonix
users.ts
根目录 / workers / accounts / src / db / users.ts
1 import type { Role, UserRow } from "../types";
2
3 export interface NewUser {
4 handle: string;
5 email: string; // already lowercased
6 passwordHash: string;
7 displayName: string;
8 role: Role;
9 }
10
11 export interface ProfilePatch {
12 handle?: string;
13 displayName?: string;
14 bio?: string;
15 avatarUrl?: string;
16 }
17
18 export class UserRepo {
19 constructor(private readonly db: D1Database) {}
20
21 byId(id: number): Promise<UserRow | null> {
22 return this.db.prepare("SELECT * FROM users WHERE id = ?1").bind(id).first<UserRow>();
23 }
24
25 byEmail(email: string): Promise<UserRow | null> {
26 return this.db.prepare("SELECT * FROM users WHERE email = ?1").bind(email).first<UserRow>();
27 }
28
29 byHandle(handle: string): Promise<UserRow | null> {
30 return this.db.prepare("SELECT * FROM users WHERE handle = ?1").bind(handle).first<UserRow>();
31 }
32
33 async handleTaken(handle: string): Promise<boolean> {
34 const row = await this.db.prepare("SELECT 1 AS x FROM users WHERE handle = ?1 LIMIT 1").bind(handle).first<{ x: number }>();
35 return row !== null;
36 }
37
38 async create(input: NewUser): Promise<UserRow> {
39 const now = new Date().toISOString();
40 const res = await this.db
41 .prepare(
42 `INSERT INTO users (handle, email, email_verified, password_hash, display_name, avatar_url, bio, role, status, created_at, updated_at)
43 VALUES (?1, ?2, 0, ?3, ?4, '', '', ?5, 'active', ?6, ?6)`,
44 )
45 .bind(input.handle, input.email, input.passwordHash, input.displayName, input.role, now)
46 .run();
47 const row = await this.byId(Number(res.meta.last_row_id));
48 if (!row) throw new Error("user row missing right after insert");
49 return row;
50 }
51
52 async markEmailVerified(id: number): Promise<void> {
53 await this.db
54 .prepare("UPDATE users SET email_verified = 1, updated_at = ?2 WHERE id = ?1")
55 .bind(id, new Date().toISOString())
56 .run();
57 }
58
59 async updatePassword(id: number, passwordHash: string): Promise<void> {
60 await this.db
61 .prepare("UPDATE users SET password_hash = ?2, updated_at = ?3 WHERE id = ?1")
62 .bind(id, passwordHash, new Date().toISOString())
63 .run();
64 }
65
66 async updateProfile(id: number, patch: ProfilePatch): Promise<UserRow | null> {
67 const sets: string[] = [];
68 const binds: unknown[] = [];
69 const add = (col: string, value: unknown): void => {
70 binds.push(value);
71 sets.push(`${col} = ?${binds.length}`);
72 };
73 if (patch.handle !== undefined) add("handle", patch.handle);
74 if (patch.displayName !== undefined) add("display_name", patch.displayName);
75 if (patch.bio !== undefined) add("bio", patch.bio);
76 if (patch.avatarUrl !== undefined) add("avatar_url", patch.avatarUrl);
77 add("updated_at", new Date().toISOString());
78 binds.push(id);
79 const idParam = binds.length;
80 let where = `id = ?${idParam}`;
81 if (patch.handle !== undefined) {
82 binds.push(patch.handle);
83 where += ` AND NOT EXISTS (SELECT 1 FROM users other WHERE other.handle = ?${binds.length} AND other.id != ?${idParam})`;
84 }
85 const result = await this.db.prepare(`UPDATE users SET ${sets.join(", ")} WHERE ${where}`).bind(...binds).run();
86 if (result.meta.changes === 0) return null;
87 const row = await this.byId(id);
88 if (!row) throw new Error("user row missing after profile update");
89 return row;
90 }
91
92 async softDelete(id: number): Promise<void> {
93 await this.db
94 .prepare("UPDATE users SET status = 'deleted', updated_at = ?2 WHERE id = ?1")
95 .bind(id, new Date().toISOString())
96 .run();
97 }
98 }
99
99 lines TYPESCRIPT