import { pgPool } from '../config/database-factory'; import { User } from '@gaplace/shared'; import { v4 as uuidv4 } from 'uuid'; import bcrypt from 'bcryptjs'; import jwt from 'jsonwebtoken'; import { config } from '../config/env'; export class UserService { async createGuestUser(): Promise { const client = await pgPool.connect(); try { const guestId = uuidv4(); const username = `Guest_${guestId.slice(0, 8)}`; const result = await client.query(` INSERT INTO users (id, username, is_guest) VALUES ($1, $2, true) RETURNING * `, [guestId, username]); const row = result.rows[0]; return { id: row.id, username: row.username, email: row.email, avatar: row.avatar_url, isGuest: true, createdAt: new Date(row.created_at).getTime(), lastSeen: new Date(row.last_seen).getTime(), }; } finally { client.release(); } } async createUser(username: string, email: string, password: string): Promise { const client = await pgPool.connect(); try { // Check if username or email already exists const existingUser = await client.query(` SELECT id FROM users WHERE username = $1 OR email = $2 `, [username, email]); if (existingUser.rows.length > 0) { throw new Error('Username or email already exists'); } // Hash password const passwordHash = await bcrypt.hash(password, 12); const result = await client.query(` INSERT INTO users (username, email, password_hash, is_guest) VALUES ($1, $2, $3, false) RETURNING * `, [username, email, passwordHash]); const row = result.rows[0]; return { id: row.id, username: row.username, email: row.email, avatar: row.avatar_url, isGuest: false, createdAt: new Date(row.created_at).getTime(), lastSeen: new Date(row.last_seen).getTime(), }; } finally { client.release(); } } async authenticateUser(usernameOrEmail: string, password: string): Promise { const client = await pgPool.connect(); try { const result = await client.query(` SELECT * FROM users WHERE (username = $1 OR email = $1) AND is_guest = false `, [usernameOrEmail]); if (result.rows.length === 0) { return null; } const row = result.rows[0]; const isValid = await bcrypt.compare(password, row.password_hash); if (!isValid) { return null; } // Update last seen await client.query(` UPDATE users SET last_seen = NOW() WHERE id = $1 `, [row.id]); return { id: row.id, username: row.username, email: row.email, avatar: row.avatar_url, isGuest: false, createdAt: new Date(row.created_at).getTime(), lastSeen: Date.now(), }; } finally { client.release(); } } async getUser(userId: string): Promise { const client = await pgPool.connect(); try { const result = await client.query(` SELECT * FROM users WHERE id = $1 `, [userId]); if (result.rows.length === 0) { return null; } const row = result.rows[0]; return { id: row.id, username: row.username, email: row.email, avatar: row.avatar_url, isGuest: row.is_guest, createdAt: new Date(row.created_at).getTime(), lastSeen: new Date(row.last_seen).getTime(), }; } finally { client.release(); } } async updateUserLastSeen(userId: string): Promise { const client = await pgPool.connect(); try { await client.query(` UPDATE users SET last_seen = NOW() WHERE id = $1 `, [userId]); } finally { client.release(); } } generateJWT(user: User): string { return jwt.sign( { userId: user.id, username: user.username, isGuest: user.isGuest }, config.jwtSecret, { expiresIn: '7d' } ); } verifyJWT(token: string): { userId: string; username: string; isGuest: boolean } | null { try { return jwt.verify(token, config.jwtSecret) as any; } catch (error) { return null; } } async getUserStats(userId: string): Promise<{ totalPixels: number; joinedCanvases: number; accountAge: number; }> { const client = await pgPool.connect(); try { const [userResult, canvasResult] = await Promise.all([ client.query(` SELECT created_at FROM users WHERE id = $1 `, [userId]), client.query(` SELECT COUNT(DISTINCT canvas_id) as canvas_count FROM user_sessions WHERE user_id = $1 `, [userId]) ]); const user = userResult.rows[0]; const canvasCount = canvasResult.rows[0]?.canvas_count || 0; return { totalPixels: 0, // Will be fetched from Redis via RateLimitService joinedCanvases: parseInt(canvasCount), accountAge: user ? Date.now() - new Date(user.created_at).getTime() : 0, }; } finally { client.release(); } } }