Files
5why-analyzer/models/User.js
donald f9ee43b73c feat: Add 5Why_ prefix to all database tables
- Rename all tables with 5Why_ prefix for namespace isolation
- Update models: User.js, Analysis.js, AuditLog.js
- Update routes: llmConfig.js
- Update scripts: seed-test-users.js, add-deepseek-config.js, add-ollama-config.js
- Add migrate-table-prefix.js script for database migration
- Update db_schema.sql with new table names
- Update views: 5Why_user_analysis_stats, 5Why_recent_analyses

Tables renamed:
- users -> 5Why_users
- analyses -> 5Why_analyses
- analysis_perspectives -> 5Why_analysis_perspectives
- analysis_whys -> 5Why_analysis_whys
- llm_configs -> 5Why_llm_configs
- system_settings -> 5Why_system_settings
- audit_logs -> 5Why_audit_logs
- sessions -> 5Why_sessions

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-09 18:19:53 +08:00

232 lines
6.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { pool } from '../config.js';
import bcrypt from 'bcryptjs';
/**
* User Model
* 處理使用者相關的資料庫操作
*/
class User {
/**
* 根據 ID 取得使用者
*/
static async findById(id) {
try {
const [rows] = await pool.execute(
'SELECT id, employee_id, username, email, role, department, position, is_active, created_at, last_login_at FROM 5Why_users WHERE id = ?',
[id]
);
return rows[0] || null;
} catch (error) {
throw new Error(`Error finding user by ID: ${error.message}`);
}
}
/**
* 根據 Email 取得使用者(含密碼,用於登入驗證)
*/
static async findByEmail(email) {
try {
const [rows] = await pool.execute(
'SELECT * FROM 5Why_users WHERE email = ? AND is_active = 1',
[email]
);
return rows[0] || null;
} catch (error) {
throw new Error(`Error finding user by email: ${error.message}`);
}
}
/**
* 根據工號取得使用者
*/
static async findByEmployeeId(employeeId) {
try {
const [rows] = await pool.execute(
'SELECT * FROM 5Why_users WHERE employee_id = ? AND is_active = 1',
[employeeId]
);
return rows[0] || null;
} catch (error) {
throw new Error(`Error finding user by employee ID: ${error.message}`);
}
}
/**
* 驗證密碼
*/
static async verifyPassword(plainPassword, hashedPassword) {
return await bcrypt.compare(plainPassword, hashedPassword);
}
/**
* 建立新使用者
*/
static async create(userData) {
const { employee_id, username, email, password, role = 'user', department, position } = userData;
try {
// 加密密碼
const passwordHash = await bcrypt.hash(password, 10);
const [result] = await pool.execute(
`INSERT INTO 5Why_users (employee_id, username, email, password_hash, role, department, position)
VALUES (?, ?, ?, ?, ?, ?, ?)`,
[employee_id, username, email, passwordHash, role, department, position]
);
return await this.findById(result.insertId);
} catch (error) {
if (error.code === 'ER_DUP_ENTRY') {
throw new Error('工號或 Email 已存在');
}
throw new Error(`Error creating user: ${error.message}`);
}
}
/**
* 更新使用者資料
*/
static async update(id, userData) {
const { username, email, role, department, position, is_active } = userData;
try {
await pool.execute(
`UPDATE 5Why_users
SET username = ?, email = ?, role = ?, department = ?, position = ?, is_active = ?
WHERE id = ?`,
[username, email, role, department, position, is_active, id]
);
return await this.findById(id);
} catch (error) {
throw new Error(`Error updating user: ${error.message}`);
}
}
/**
* 更新密碼
*/
static async updatePassword(id, newPassword) {
try {
const passwordHash = await bcrypt.hash(newPassword, 10);
await pool.execute(
'UPDATE 5Why_users SET password_hash = ? WHERE id = ?',
[passwordHash, id]
);
return true;
} catch (error) {
throw new Error(`Error updating password: ${error.message}`);
}
}
/**
* 更新最後登入時間
*/
static async updateLastLogin(id) {
try {
await pool.execute(
'UPDATE 5Why_users SET last_login_at = CURRENT_TIMESTAMP WHERE id = ?',
[id]
);
} catch (error) {
console.error('Error updating last login:', error);
}
}
/**
* 取得所有使用者(分頁)
*/
static async getAll(page = 1, limit = 10, filters = {}) {
const offset = (page - 1) * limit;
let query = 'SELECT id, employee_id, username, email, role, department, position, is_active, created_at FROM 5Why_users';
let countQuery = 'SELECT COUNT(*) as total FROM 5Why_users';
const whereParams = [];
const whereClauses = [];
// 篩選條件
if (filters.role) {
whereClauses.push('role = ?');
whereParams.push(filters.role);
}
if (filters.is_active !== undefined) {
whereClauses.push('is_active = ?');
whereParams.push(filters.is_active);
}
if (filters.search) {
whereClauses.push('(username LIKE ? OR email LIKE ? OR employee_id LIKE ?)');
const searchTerm = `%${filters.search}%`;
whereParams.push(searchTerm, searchTerm, searchTerm);
}
if (whereClauses.length > 0) {
const whereClause = ' WHERE ' + whereClauses.join(' AND ');
query += whereClause;
countQuery += whereClause;
}
query += ' ORDER BY created_at DESC LIMIT ? OFFSET ?';
try {
// 使用 query 而非 execute因為 LIMIT 和 OFFSET 參數需要數字類型
const [rows] = await pool.query(query, [...whereParams, parseInt(limit), parseInt(offset)]);
const [countResult] = await pool.query(countQuery, whereParams);
return {
data: rows,
pagination: {
page,
limit,
total: countResult[0].total,
totalPages: Math.ceil(countResult[0].total / limit)
}
};
} catch (error) {
throw new Error(`Error getting users: ${error.message}`);
}
}
/**
* 刪除使用者(軟刪除)
*/
static async delete(id) {
try {
await pool.execute(
'UPDATE 5Why_users SET is_active = 0 WHERE id = ?',
[id]
);
return true;
} catch (error) {
throw new Error(`Error deleting user: ${error.message}`);
}
}
/**
* 硬刪除使用者(謹慎使用)
*/
static async hardDelete(id) {
try {
await pool.execute('DELETE FROM 5Why_users WHERE id = ?', [id]);
return true;
} catch (error) {
throw new Error(`Error hard deleting user: ${error.message}`);
}
}
/**
* 取得使用者統計
*/
static async getStats(userId) {
try {
const [rows] = await pool.execute(
'SELECT * FROM 5Why_user_analysis_stats WHERE user_id = ?',
[userId]
);
return rows[0] || null;
} catch (error) {
throw new Error(`Error getting user stats: ${error.message}`);
}
}
}
export default User;