import type { RowDataPacket } from "mysql2/promise"; import { pool } from "../../db/pool"; import type { EmployeeLoginAccount, SuperAdminStatus, SuperAdminWithPassword, } from "./auth.types"; interface SuperAdminRow extends RowDataPacket { id: number; username: string; password_hash: string; display_name: string; status: SuperAdminStatus; last_login_at: Date | null; created_at: Date; updated_at: Date; } interface EmployeeLoginRow extends RowDataPacket { id: number; username: string; password_hash: string; display_name: string; store_id: number; store_name: string; } interface EmployeeRoleRow extends RowDataPacket { employee_id: number; id: number; code: string; name: string; } function toIso(value: Date | null): string | null { return value ? value.toISOString() : null; } function toSuperAdmin(row: SuperAdminRow): SuperAdminWithPassword { return { id: row.id, username: row.username, passwordHash: row.password_hash, displayName: row.display_name, status: row.status, lastLoginAt: toIso(row.last_login_at), createdAt: toIso(row.created_at) ?? "", updatedAt: toIso(row.updated_at) ?? "", }; } export const authRepository = { async findActiveByUsername( username: string, ): Promise { const [rows] = await pool.execute( ` SELECT id, username, password_hash, display_name, status, last_login_at, created_at, updated_at FROM super_admins WHERE username = ? AND status = 'ACTIVE' LIMIT 1 `, [username], ); return rows[0] ? toSuperAdmin(rows[0]) : null; }, async findActiveById(id: number): Promise { const [rows] = await pool.execute( ` SELECT id, username, password_hash, display_name, status, last_login_at, created_at, updated_at FROM super_admins WHERE id = ? AND status = 'ACTIVE' LIMIT 1 `, [id], ); return rows[0] ? toSuperAdmin(rows[0]) : null; }, async updateLastLoginAt(id: number): Promise { await pool.execute( ` UPDATE super_admins SET last_login_at = CURRENT_TIMESTAMP(3) WHERE id = ? `, [id], ); }, async findActiveEmployeeByPhone( phone: string, ): Promise { const [rows] = await pool.execute( ` SELECT e.id, e.phone AS username, e.password_hash, e.name AS display_name, e.store_id, s.name AS store_name FROM employees e INNER JOIN stores s ON s.id = e.store_id WHERE e.phone = ? AND e.status = 'ACTIVE' AND e.deleted_at IS NULL AND s.status = 'ACTIVE' AND s.deleted_at IS NULL LIMIT 1 `, [phone], ); if (!rows[0]) { return null; } const rolesByEmployeeId = await this.findRolesByEmployeeIds([rows[0].id]); return toEmployeeLoginAccount( rows[0], rolesByEmployeeId.get(rows[0].id) ?? [], ); }, async findActiveEmployeeById( id: number, ): Promise { const [rows] = await pool.execute( ` SELECT e.id, e.phone AS username, e.password_hash, e.name AS display_name, e.store_id, s.name AS store_name FROM employees e INNER JOIN stores s ON s.id = e.store_id WHERE e.id = ? AND e.status = 'ACTIVE' AND e.deleted_at IS NULL AND s.status = 'ACTIVE' AND s.deleted_at IS NULL LIMIT 1 `, [id], ); if (!rows[0]) { return null; } const rolesByEmployeeId = await this.findRolesByEmployeeIds([rows[0].id]); return toEmployeeLoginAccount( rows[0], rolesByEmployeeId.get(rows[0].id) ?? [], ); }, async updateEmployeeLastLoginAt(id: number): Promise { await pool.execute( ` UPDATE employees SET last_login_at = CURRENT_TIMESTAMP(3) WHERE id = ? `, [id], ); }, async findRolesByEmployeeIds( employeeIds: number[], ): Promise> { const result = new Map(); if (employeeIds.length === 0) { return result; } const placeholders = employeeIds.map(() => "?").join(", "); const [rows] = await pool.execute( ` SELECT er.employee_id, r.id, r.code, r.name FROM employee_roles er INNER JOIN roles r ON r.id = er.role_id WHERE er.employee_id IN (${placeholders}) ORDER BY r.id ASC `, employeeIds, ); for (const row of rows) { const roles = result.get(row.employee_id) ?? []; roles.push({ id: row.id, code: row.code, name: row.name, }); result.set(row.employee_id, roles); } return result; }, }; function toEmployeeLoginAccount( row: EmployeeLoginRow, roles: EmployeeLoginAccount["roles"], ): EmployeeLoginAccount { return { id: row.id, username: row.username, passwordHash: row.password_hash, displayName: row.display_name, storeId: row.store_id, storeName: row.store_name, roles, }; }