the best lightweight web dev stack built on bun
at main 3.5 kB view raw
1import { eq } from "drizzle-orm"; 2import db from "../db/db"; 3import { users, sessions, type User } from "../db/schema"; 4 5const SESSION_DURATION = 7 * 24 * 60 * 60; // 7 days in seconds 6 7export interface User { 8 id: number; 9 username: string; 10 name: string | null; 11 avatar: string; 12 created_at: number; 13} 14 15export interface Session { 16 id: string; 17 user_id: number; 18 ip_address: string | null; 19 user_agent: string | null; 20 created_at: number; 21 expires_at: number; 22} 23 24export function createSession( 25 userId: number, 26 ipAddress?: string, 27 userAgent?: string, 28): string { 29 const sessionId = crypto.randomUUID(); 30 const expiresAt = Math.floor(Date.now() / 1000) + SESSION_DURATION; 31 32 db.insert(sessions) 33 .values({ 34 id: sessionId, 35 user_id: userId, 36 ip_address: ipAddress ?? null, 37 user_agent: userAgent ?? null, 38 expires_at: new Date(expiresAt * 1000), 39 }) 40 .run(); 41 42 return sessionId; 43} 44 45export function getSession(sessionId: string): Session | null { 46 const now = Math.floor(Date.now() / 1000); 47 48 const session = db 49 .select() 50 .from(sessions) 51 .where(eq(sessions.id, sessionId)) 52 .get(); 53 54 if (!session || Math.floor(session.expires_at.getTime() / 1000) <= now) { 55 return null; 56 } 57 58 return { 59 id: session.id, 60 user_id: session.user_id, 61 ip_address: session.ip_address, 62 user_agent: session.user_agent, 63 created_at: Math.floor(session.created_at.getTime() / 1000), 64 expires_at: Math.floor(session.expires_at.getTime() / 1000), 65 }; 66} 67 68export function getUserBySession(sessionId: string): User | null { 69 const session = getSession(sessionId); 70 if (!session) return null; 71 72 const user = db 73 .select() 74 .from(users) 75 .where(eq(users.id, session.user_id)) 76 .get(); 77 78 if (!user) return null; 79 80 return { 81 id: user.id, 82 username: user.username, 83 name: user.name, 84 avatar: user.avatar, 85 created_at: Math.floor(user.created_at.getTime() / 1000), 86 }; 87} 88 89export function getUserByUsername(username: string): User | null { 90 const user = db 91 .select() 92 .from(users) 93 .where(eq(users.username, username)) 94 .get(); 95 96 if (!user) return null; 97 98 return { 99 id: user.id, 100 username: user.username, 101 name: user.name, 102 avatar: user.avatar, 103 created_at: Math.floor(user.created_at.getTime() / 1000), 104 }; 105} 106 107export function deleteSession(sessionId: string): void { 108 db.delete(sessions).where(eq(sessions.id, sessionId)).run(); 109} 110 111export async function createUser( 112 username: string, 113 name?: string, 114): Promise<User> { 115 // Generate deterministic avatar from username 116 const encoder = new TextEncoder(); 117 const data = encoder.encode(username.toLowerCase()); 118 const hashBuffer = await crypto.subtle.digest("SHA-256", data); 119 const hashArray = Array.from(new Uint8Array(hashBuffer)); 120 const avatar = hashArray 121 .map((b) => b.toString(16).padStart(2, "0")) 122 .join("") 123 .substring(0, 16); 124 125 const result = db 126 .insert(users) 127 .values({ 128 username, 129 name: name ?? null, 130 avatar, 131 }) 132 .run(); 133 134 const user = db 135 .select() 136 .from(users) 137 .where(eq(users.id, Number(result.lastInsertRowid))) 138 .get(); 139 140 if (!user) { 141 throw new Error("Failed to create user"); 142 } 143 144 return { 145 id: user.id, 146 username: user.username, 147 name: user.name, 148 avatar: user.avatar, 149 created_at: Math.floor(user.created_at.getTime() / 1000), 150 }; 151} 152 153export function getSessionFromRequest(req: Request): string | null { 154 const cookie = req.headers.get("cookie"); 155 if (!cookie) return null; 156 157 const sessionMatch = cookie.match(/session=([^;]+)/); 158 return sessionMatch ? sessionMatch[1] : null; 159}