import { pool } from '../config/database.js'; import { User, CreateUserRequest, UpdateUserRequest, UserResponse } from '../models/User.js'; import { $log } from '@tsed/logger'; import bcrypt from 'bcryptjs'; import { randomUUID } from 'crypto'; export class UserService { async createUser(userData: CreateUserRequest): Promise { const connection = await pool.getConnection(); try { // Check if user already exists const [existingUsers] = await connection.execute( 'SELECT id FROM users WHERE email = ? AND deleted_at IS NULL', [userData.email] ); if (Array.isArray(existingUsers) && existingUsers.length > 0) { throw new Error('User with this email already exists'); } // Hash password const password_hash = await bcrypt.hash(userData.password, 10); // Generate UUID for user ID const userId = randomUUID(); // Insert user const [result] = await connection.execute( `INSERT INTO users (id, email, password_hash, first_name, last_name, role, company_name, is_active, email_verified_at, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW(), NOW())`, [ userId, userData.email, password_hash, userData.first_name, userData.last_name, userData.role || 'recruiter', userData.company_name || null, true ] ); // Get the created user const user = await this.getUserById(userId); if (!user) { throw new Error('Failed to create user'); } return this.mapUserToResponse(user); } catch (error) { $log.error('Error creating user:', error); throw error; } finally { connection.release(); } } async getUserByEmail(email: string): Promise { const connection = await pool.getConnection(); try { const [rows] = await connection.execute( 'SELECT * FROM users WHERE email = ? AND deleted_at IS NULL', [email] ); if (Array.isArray(rows) && rows.length > 0) { return rows[0] as User; } return null; } catch (error) { $log.error('Error getting user by email:', error); throw error; } finally { connection.release(); } } async getUserById(id: string): Promise { const connection = await pool.getConnection(); try { const [rows] = await connection.execute( 'SELECT * FROM users WHERE id = ? AND deleted_at IS NULL', [id] ); if (Array.isArray(rows) && rows.length > 0) { return rows[0] as User; } return null; } catch (error) { $log.error('Error getting user by ID:', error); throw error; } finally { connection.release(); } } async updateUser(id: string, userData: UpdateUserRequest): Promise { const connection = await pool.getConnection(); try { const updateFields = []; const values = []; if (userData.first_name) { updateFields.push('first_name = ?'); values.push(userData.first_name); } if (userData.last_name) { updateFields.push('last_name = ?'); values.push(userData.last_name); } if (userData.company_name !== undefined) { updateFields.push('company_name = ?'); values.push(userData.company_name); } if (userData.avatar_url !== undefined) { updateFields.push('avatar_url = ?'); values.push(userData.avatar_url); } if (userData.is_active !== undefined) { updateFields.push('is_active = ?'); values.push(userData.is_active); } if (updateFields.length === 0) { throw new Error('No fields to update'); } updateFields.push('updated_at = NOW()'); values.push(id); await connection.execute( `UPDATE users SET ${updateFields.join(', ')} WHERE id = ? AND deleted_at IS NULL`, values ); const user = await this.getUserById(id); return user ? this.mapUserToResponse(user) : null; } catch (error) { $log.error('Error updating user:', error); throw error; } finally { connection.release(); } } async updateLastLogin(id: string): Promise { const connection = await pool.getConnection(); try { await connection.execute( 'UPDATE users SET last_login_at = NOW(), updated_at = NOW() WHERE id = ? AND deleted_at IS NULL', [id] ); } catch (error) { $log.error('Error updating last login:', error); throw error; } finally { connection.release(); } } async verifyPassword(user: User, password: string): Promise { return await bcrypt.compare(password, user.password_hash); } async changePassword(id: string, newPassword: string): Promise { const connection = await pool.getConnection(); try { const password_hash = await bcrypt.hash(newPassword, 10); await connection.execute( 'UPDATE users SET password_hash = ?, updated_at = NOW() WHERE id = ? AND deleted_at IS NULL', [password_hash, id] ); } catch (error) { $log.error('Error changing password:', error); throw error; } finally { connection.release(); } } async softDeleteUser(id: string): Promise { const connection = await pool.getConnection(); try { await connection.execute( 'UPDATE users SET deleted_at = NOW(), is_active = FALSE, updated_at = NOW() WHERE id = ? AND deleted_at IS NULL', [id] ); } catch (error) { $log.error('Error soft deleting user:', error); throw error; } finally { connection.release(); } } /** * Get user payment history */ async getUserPaymentHistory(userId: string): Promise { const connection = await pool.getConnection(); try { const [rows] = await connection.execute(` SELECT pr.*, tp.name as package_name FROM payment_records pr LEFT JOIN token_packages tp ON pr.token_package_id = tp.id WHERE pr.user_id = ? ORDER BY pr.created_at DESC `, [userId]); return Array.isArray(rows) ? rows : []; } catch (error) { $log.error('Error getting user payment history:', error); throw error; } finally { connection.release(); } } /** * Get user by Stripe customer ID */ async getUserByStripeCustomerId(stripeCustomerId: string): Promise { const connection = await pool.getConnection(); try { const [rows] = await connection.execute( 'SELECT * FROM users WHERE stripe_customer_id = ? AND deleted_at IS NULL', [stripeCustomerId] ); if (Array.isArray(rows) && rows.length > 0) { return rows[0] as User; } return null; } catch (error) { $log.error('Error getting user by Stripe customer ID:', error); throw error; } finally { connection.release(); } } /** * Update user's Stripe customer ID */ async updateUserStripeCustomerId(userId: string, stripeCustomerId: string): Promise { const connection = await pool.getConnection(); try { await connection.execute( 'UPDATE users SET stripe_customer_id = ?, updated_at = NOW() WHERE id = ?', [stripeCustomerId, userId] ); $log.info(`Updated Stripe customer ID for user: ${userId}`); } catch (error) { $log.error('Error updating user Stripe customer ID:', error); throw error; } finally { connection.release(); } } /** * Get user payment statistics */ async getUserPaymentStatistics(userId: string): Promise<{ totalSpent: number; totalPayments: number; averagePaymentValue: number; lastPaymentDate: string | null; }> { const connection = await pool.getConnection(); try { const [rows] = await connection.execute(` SELECT COUNT(*) as total_payments, SUM(CASE WHEN status = 'paid' THEN amount ELSE 0 END) as total_spent, AVG(CASE WHEN status = 'paid' THEN amount ELSE NULL END) as avg_payment_value, MAX(CASE WHEN status = 'paid' THEN paid_at ELSE NULL END) as last_payment_date FROM payment_records WHERE user_id = ? `, [userId]); const stats = Array.isArray(rows) ? rows[0] as any : {}; return { totalSpent: stats.total_spent || 0, totalPayments: stats.total_payments || 0, averagePaymentValue: stats.avg_payment_value || 0, lastPaymentDate: stats.last_payment_date || null, }; } catch (error) { $log.error('Error getting user payment statistics:', error); throw error; } finally { connection.release(); } } private mapUserToResponse(user: User): UserResponse { return { id: user.id, email: user.email, first_name: user.first_name, last_name: user.last_name, role: user.role, company_name: user.company_name, avatar_url: user.avatar_url, is_active: user.is_active, last_login_at: user.last_login_at, email_verified_at: user.email_verified_at, created_at: user.created_at, updated_at: user.updated_at }; } }