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(); } } 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 }; } }