馃 distributed transcription service thistle.dunkirk.sh
1import db from "../db/schema"; 2 3const SESSION_DURATION = 7 * 24 * 60 * 60; // 7 days in seconds 4 5export interface User { 6 id: number; 7 email: string; 8 name: string | null; 9 avatar: string; 10 created_at: number; 11} 12 13export interface Session { 14 id: string; 15 user_id: number; 16 ip_address: string | null; 17 user_agent: string | null; 18 created_at: number; 19 expires_at: number; 20} 21 22export async function hashPassword(password: string): Promise<string> { 23 return await Bun.password.hash(password, { 24 algorithm: "argon2id", 25 memoryCost: 19456, 26 timeCost: 2, 27 }); 28} 29 30export async function verifyPassword( 31 password: string, 32 hash: string, 33): Promise<boolean> { 34 return await Bun.password.verify(password, hash, "argon2id"); 35} 36 37export function createSession( 38 userId: number, 39 ipAddress?: string, 40 userAgent?: string, 41): string { 42 const sessionId = crypto.randomUUID(); 43 const expiresAt = Math.floor(Date.now() / 1000) + SESSION_DURATION; 44 45 db.run( 46 "INSERT INTO sessions (id, user_id, ip_address, user_agent, expires_at) VALUES (?, ?, ?, ?, ?)", 47 [sessionId, userId, ipAddress ?? null, userAgent ?? null, expiresAt], 48 ); 49 50 return sessionId; 51} 52 53export function getSession(sessionId: string): Session | null { 54 const now = Math.floor(Date.now() / 1000); 55 56 const session = db 57 .query<Session, [string, number]>( 58 "SELECT id, user_id, ip_address, user_agent, created_at, expires_at FROM sessions WHERE id = ? AND expires_at > ?", 59 ) 60 .get(sessionId, now); 61 62 return session ?? null; 63} 64 65export function getUserBySession(sessionId: string): User | null { 66 const session = getSession(sessionId); 67 if (!session) return null; 68 69 const user = db 70 .query<User, [number]>( 71 "SELECT id, email, name, avatar, created_at FROM users WHERE id = ?", 72 ) 73 .get(session.user_id); 74 75 return user ?? null; 76} 77 78export function deleteSession(sessionId: string): void { 79 db.run("DELETE FROM sessions WHERE id = ?", [sessionId]); 80} 81 82export function cleanupExpiredSessions(): void { 83 const now = Math.floor(Date.now() / 1000); 84 db.run("DELETE FROM sessions WHERE expires_at <= ?", [now]); 85} 86 87export async function createUser( 88 email: string, 89 password: string, 90 name?: string, 91): Promise<User> { 92 const passwordHash = await hashPassword(password); 93 94 const result = db.run( 95 "INSERT INTO users (email, password_hash, name) VALUES (?, ?, ?)", 96 [email, passwordHash, name ?? null], 97 ); 98 99 const user = db 100 .query<User, [number]>("SELECT id, email, name, avatar, created_at FROM users WHERE id = ?") 101 .get(Number(result.lastInsertRowid)); 102 103 if (!user) { 104 throw new Error("Failed to create user"); 105 } 106 107 return user; 108} 109 110export async function authenticateUser( 111 email: string, 112 password: string, 113): Promise<User | null> { 114 const result = db 115 .query<{ id: number; email: string; name: string | null; password_hash: string; created_at: number }, [string]>( 116 "SELECT id, email, name, avatar, password_hash, created_at FROM users WHERE email = ?", 117 ) 118 .get(email); 119 120 if (!result) return null; 121 122 const isValid = await verifyPassword(password, result.password_hash); 123 if (!isValid) return null; 124 125 return { 126 id: result.id, 127 email: result.email, 128 name: result.name, 129 avatar: result.avatar, 130 created_at: result.created_at, 131 }; 132} 133 134export function getUserSessionsForUser(userId: number): Session[] { 135 const now = Math.floor(Date.now() / 1000); 136 137 const sessions = db 138 .query<Session, [number, number]>( 139 "SELECT id, user_id, ip_address, user_agent, created_at, expires_at FROM sessions WHERE user_id = ? AND expires_at > ? ORDER BY created_at DESC", 140 ) 141 .all(userId, now); 142 143 return sessions; 144} 145 146export function getSessionFromRequest(req: Request): string | null { 147 const cookie = req.headers.get("cookie"); 148 if (!cookie) return null; 149 150 const match = cookie.match(/session=([^;]+)/); 151 return match?.[1] ?? null; 152} 153 154export function deleteUser(userId: number): void { 155 db.run("DELETE FROM users WHERE id = ?", [userId]); 156} 157 158export function updateUserEmail(userId: number, newEmail: string): void { 159 db.run("UPDATE users SET email = ? WHERE id = ?", [newEmail, userId]); 160} 161 162export function updateUserName(userId: number, newName: string): void { 163 db.run("UPDATE users SET name = ? WHERE id = ?", [newName, userId]); 164} 165 166export function updateUserAvatar(userId: number, avatar: string): void { 167 db.run("UPDATE users SET avatar = ? WHERE id = ?", [avatar, userId]); 168} 169 170export async function updateUserPassword( 171 userId: number, 172 newPassword: string, 173): Promise<void> { 174 const hash = await hashPassword(newPassword); 175 db.run("UPDATE users SET password_hash = ? WHERE id = ?", [hash, userId]); 176}