225 lines
5.3 KiB
TypeScript
225 lines
5.3 KiB
TypeScript
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<SuperAdminWithPassword | null> {
|
|
const [rows] = await pool.execute<SuperAdminRow[]>(
|
|
`
|
|
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<SuperAdminWithPassword | null> {
|
|
const [rows] = await pool.execute<SuperAdminRow[]>(
|
|
`
|
|
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<void> {
|
|
await pool.execute(
|
|
`
|
|
UPDATE super_admins
|
|
SET last_login_at = CURRENT_TIMESTAMP(3)
|
|
WHERE id = ?
|
|
`,
|
|
[id],
|
|
);
|
|
},
|
|
|
|
async findActiveEmployeeByPhone(
|
|
phone: string,
|
|
): Promise<EmployeeLoginAccount | null> {
|
|
const [rows] = await pool.execute<EmployeeLoginRow[]>(
|
|
`
|
|
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<EmployeeLoginAccount | null> {
|
|
const [rows] = await pool.execute<EmployeeLoginRow[]>(
|
|
`
|
|
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<void> {
|
|
await pool.execute(
|
|
`
|
|
UPDATE employees
|
|
SET last_login_at = CURRENT_TIMESTAMP(3)
|
|
WHERE id = ?
|
|
`,
|
|
[id],
|
|
);
|
|
},
|
|
|
|
async findRolesByEmployeeIds(
|
|
employeeIds: number[],
|
|
): Promise<Map<number, EmployeeLoginAccount["roles"]>> {
|
|
const result = new Map<number, EmployeeLoginAccount["roles"]>();
|
|
|
|
if (employeeIds.length === 0) {
|
|
return result;
|
|
}
|
|
|
|
const placeholders = employeeIds.map(() => "?").join(", ");
|
|
const [rows] = await pool.execute<EmployeeRoleRow[]>(
|
|
`
|
|
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,
|
|
};
|
|
}
|