From f9ee43b73cec3ff10ba18c858bddea9f347d91b2 Mon Sep 17 00:00:00 2001 From: donald Date: Tue, 9 Dec 2025 18:19:53 +0800 Subject: [PATCH] feat: Add 5Why_ prefix to all database tables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- docs/db_schema.sql | 227 ++++++++++++++++++++++++++++++++ models/Analysis.js | 47 +++---- models/AuditLog.js | 17 +-- models/User.js | 29 ++-- routes/llmConfig.js | 20 +-- scripts/add-deepseek-config.js | 10 +- scripts/add-ollama-config.js | 81 ++++++++++++ scripts/migrate-table-prefix.js | 177 +++++++++++++++++++++++++ scripts/seed-test-users.js | 6 +- 9 files changed, 552 insertions(+), 62 deletions(-) create mode 100644 docs/db_schema.sql create mode 100644 scripts/add-ollama-config.js create mode 100644 scripts/migrate-table-prefix.js diff --git a/docs/db_schema.sql b/docs/db_schema.sql new file mode 100644 index 0000000..4e2a46e --- /dev/null +++ b/docs/db_schema.sql @@ -0,0 +1,227 @@ +-- 5 Why Analyzer Database Schema +-- Database: db_A102 +-- Version: 1.1.0 +-- Created: 2025-12-05 +-- Updated: 2025-12-09 (Added 5Why_ prefix to all tables) + +USE db_A102; + +-- ============================================ +-- Table: 5Why_users (ไฝฟ็”จ่€…่ณ‡ๆ–™่กจ) +-- ============================================ +CREATE TABLE IF NOT EXISTS 5Why_users ( + id INT AUTO_INCREMENT PRIMARY KEY, + employee_id VARCHAR(50) UNIQUE NOT NULL COMMENT 'ๅทฅ่™Ÿ', + username VARCHAR(100) NOT NULL COMMENT 'ไฝฟ็”จ่€…ๅ็จฑ', + email VARCHAR(255) UNIQUE NOT NULL COMMENT 'Email', + password_hash VARCHAR(255) NOT NULL COMMENT 'ๅฏ†็ขผ้›œๆนŠ', + role ENUM('user', 'admin', 'super_admin') DEFAULT 'user' COMMENT 'ๆฌŠ้™็ญ‰็ดš', + department VARCHAR(100) COMMENT '้ƒจ้–€', + position VARCHAR(100) COMMENT '่ทไฝ', + is_active BOOLEAN DEFAULT TRUE COMMENT 'ๅธณ่™Ÿๅ•Ÿ็”จ็‹€ๆ…‹', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'ๅปบ็ซ‹ๆ™‚้–“', + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'ๆ›ดๆ–ฐๆ™‚้–“', + last_login_at TIMESTAMP NULL COMMENT 'ๆœ€ๅพŒ็™ปๅ…ฅๆ™‚้–“', + INDEX idx_employee_id (employee_id), + INDEX idx_email (email), + INDEX idx_role (role) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='ไฝฟ็”จ่€…่ณ‡ๆ–™่กจ'; + +-- ============================================ +-- Table: 5Why_analyses (ๅˆ†ๆž่จ˜้Œ„่กจ) +-- ============================================ +CREATE TABLE IF NOT EXISTS 5Why_analyses ( + id INT AUTO_INCREMENT PRIMARY KEY, + user_id INT NOT NULL COMMENT 'ไฝฟ็”จ่€…ID', + finding TEXT NOT NULL COMMENT 'Findingๆ่ฟฐ', + job_content TEXT NOT NULL COMMENT 'ๅทฅไฝœๅ…งๅฎน', + output_language VARCHAR(10) NOT NULL DEFAULT 'zh-TW' COMMENT '่ผธๅ‡บ่ชž่จ€', + problem_restatement TEXT COMMENT 'ๅ•้กŒ้‡่ฟฐ๏ผˆ5W1H๏ผ‰', + analysis_result JSON COMMENT 'ๅฎŒๆ•ดๅˆ†ๆž็ตๆžœ๏ผˆJSONๆ ผๅผ๏ผ‰', + status ENUM('pending', 'processing', 'completed', 'failed') DEFAULT 'pending' COMMENT 'ๅˆ†ๆž็‹€ๆ…‹', + error_message TEXT COMMENT '้Œฏ่ชค่จŠๆฏ', + processing_time INT COMMENT '่™•็†ๆ™‚้–“๏ผˆ็ง’๏ผ‰', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'ๅปบ็ซ‹ๆ™‚้–“', + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'ๆ›ดๆ–ฐๆ™‚้–“', + FOREIGN KEY (user_id) REFERENCES 5Why_users(id) ON DELETE CASCADE, + INDEX idx_user_id (user_id), + INDEX idx_status (status), + INDEX idx_created_at (created_at) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='ๅˆ†ๆž่จ˜้Œ„่กจ'; + +-- ============================================ +-- Table: 5Why_analysis_perspectives (ๅˆ†ๆž่ง’ๅบฆ่ฉณ็ดฐ่กจ) +-- ============================================ +CREATE TABLE IF NOT EXISTS 5Why_analysis_perspectives ( + id INT AUTO_INCREMENT PRIMARY KEY, + analysis_id INT NOT NULL COMMENT 'ๅˆ†ๆž่จ˜้Œ„ID', + perspective VARCHAR(100) NOT NULL COMMENT 'ๅˆ†ๆž่ง’ๅบฆ๏ผˆๆต็จ‹้ขใ€็ณป็ตฑ้ข็ญ‰๏ผ‰', + perspective_icon VARCHAR(10) COMMENT 'Emojiๅœ–็คบ', + root_cause TEXT COMMENT 'ๆ นๆœฌๅŽŸๅ› ', + permanent_solution TEXT COMMENT 'ๆฐธไน…ๆ€งๅฐ็ญ–', + logic_check_forward TEXT COMMENT '้ †ๅ‘้‚่ผฏๆชขๆ ธ', + logic_check_backward TEXT COMMENT '้€†ๅ‘้‚่ผฏๆชขๆ ธ', + logic_valid BOOLEAN DEFAULT TRUE COMMENT '้‚่ผฏๆ˜ฏๅฆๆœ‰ๆ•ˆ', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'ๅปบ็ซ‹ๆ™‚้–“', + FOREIGN KEY (analysis_id) REFERENCES 5Why_analyses(id) ON DELETE CASCADE, + INDEX idx_analysis_id (analysis_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='ๅˆ†ๆž่ง’ๅบฆ่ฉณ็ดฐ่กจ'; + +-- ============================================ +-- Table: 5Why_analysis_whys (5 Why่ฉณ็ดฐ่จ˜้Œ„่กจ) +-- ============================================ +CREATE TABLE IF NOT EXISTS 5Why_analysis_whys ( + id INT AUTO_INCREMENT PRIMARY KEY, + perspective_id INT NOT NULL COMMENT 'ๅˆ†ๆž่ง’ๅบฆID', + level INT NOT NULL COMMENT 'Whyๅฑค็ดš๏ผˆ1-5๏ผ‰', + question TEXT NOT NULL COMMENT 'ๅ•้กŒ', + answer TEXT NOT NULL COMMENT '็ญ”ๆกˆ', + is_verified BOOLEAN DEFAULT FALSE COMMENT 'ๆ˜ฏๅฆๅทฒ้ฉ—่ญ‰', + verification_note TEXT COMMENT '้ฉ—่ญ‰่ชชๆ˜Ž', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'ๅปบ็ซ‹ๆ™‚้–“', + FOREIGN KEY (perspective_id) REFERENCES 5Why_analysis_perspectives(id) ON DELETE CASCADE, + INDEX idx_perspective_id (perspective_id), + INDEX idx_level (level) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='5 Why่ฉณ็ดฐ่จ˜้Œ„่กจ'; + +-- ============================================ +-- Table: 5Why_llm_configs (LLM API้…็ฝฎ่กจ) +-- ============================================ +CREATE TABLE IF NOT EXISTS 5Why_llm_configs ( + id INT AUTO_INCREMENT PRIMARY KEY, + provider VARCHAR(50) NOT NULL COMMENT 'LLMๆไพ›ๅ•†๏ผˆollama, gemini, deepseek, openai๏ผ‰', + api_url VARCHAR(255) COMMENT 'API URL', + api_key VARCHAR(255) COMMENT 'API Key๏ผˆๅŠ ๅฏ†ๅ„ฒๅญ˜๏ผ‰', + model_name VARCHAR(100) COMMENT 'ๆจกๅž‹ๅ็จฑ', + is_active BOOLEAN DEFAULT FALSE COMMENT 'ๆ˜ฏๅฆๅ•Ÿ็”จ', + max_tokens INT DEFAULT 6000 COMMENT 'ๆœ€ๅคงTokenๆ•ธ', + temperature DECIMAL(3,2) DEFAULT 0.7 COMMENT 'ๆบซๅบฆๅƒๆ•ธ', + timeout INT DEFAULT 120000 COMMENT 'Timeout๏ผˆๆฏซ็ง’๏ผ‰', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'ๅปบ็ซ‹ๆ™‚้–“', + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'ๆ›ดๆ–ฐๆ™‚้–“', + created_by INT COMMENT 'ๅปบ็ซ‹่€…ID', + FOREIGN KEY (created_by) REFERENCES 5Why_users(id) ON DELETE SET NULL, + INDEX idx_provider (provider), + INDEX idx_is_active (is_active), + UNIQUE KEY unique_active_provider (provider, is_active) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='LLM API้…็ฝฎ่กจ'; + +-- ============================================ +-- Table: 5Why_system_settings (็ณป็ตฑ่จญๅฎš่กจ) +-- ============================================ +CREATE TABLE IF NOT EXISTS 5Why_system_settings ( + id INT AUTO_INCREMENT PRIMARY KEY, + setting_key VARCHAR(100) UNIQUE NOT NULL COMMENT '่จญๅฎš้ต', + setting_value TEXT COMMENT '่จญๅฎšๅ€ผ', + setting_type VARCHAR(50) DEFAULT 'string' COMMENT '่จญๅฎš้กžๅž‹๏ผˆstring, number, boolean, json๏ผ‰', + description TEXT COMMENT '่ชชๆ˜Ž', + is_public BOOLEAN DEFAULT FALSE COMMENT 'ๆ˜ฏๅฆๅ…ฌ้–‹๏ผˆๅ‰็ซฏๅฏ่ฆ‹๏ผ‰', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'ๅปบ็ซ‹ๆ™‚้–“', + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'ๆ›ดๆ–ฐๆ™‚้–“', + updated_by INT COMMENT 'ๆ›ดๆ–ฐ่€…ID', + FOREIGN KEY (updated_by) REFERENCES 5Why_users(id) ON DELETE SET NULL, + INDEX idx_setting_key (setting_key), + INDEX idx_is_public (is_public) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='็ณป็ตฑ่จญๅฎš่กจ'; + +-- ============================================ +-- Table: 5Why_audit_logs (็จฝๆ ธๆ—ฅ่ชŒ่กจ) +-- ============================================ +CREATE TABLE IF NOT EXISTS 5Why_audit_logs ( + id INT AUTO_INCREMENT PRIMARY KEY, + user_id INT COMMENT 'ไฝฟ็”จ่€…ID', + action VARCHAR(100) NOT NULL COMMENT 'ๅ‹•ไฝœ๏ผˆlogin, logout, create_analysis, update_user็ญ‰๏ผ‰', + entity_type VARCHAR(50) COMMENT 'ๅฏฆ้ซ”้กžๅž‹๏ผˆuser, analysis, llm_config็ญ‰๏ผ‰', + entity_id INT COMMENT 'ๅฏฆ้ซ”ID', + old_value JSON COMMENT '่ˆŠๅ€ผ', + new_value JSON COMMENT 'ๆ–ฐๅ€ผ', + ip_address VARCHAR(45) COMMENT 'IPไฝๅ€', + user_agent TEXT COMMENT 'User Agent', + status ENUM('success', 'failed') DEFAULT 'success' COMMENT 'ๅŸท่กŒ็‹€ๆ…‹', + error_message TEXT COMMENT '้Œฏ่ชค่จŠๆฏ', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'ๅปบ็ซ‹ๆ™‚้–“', + FOREIGN KEY (user_id) REFERENCES 5Why_users(id) ON DELETE SET NULL, + INDEX idx_user_id (user_id), + INDEX idx_action (action), + INDEX idx_created_at (created_at), + INDEX idx_entity (entity_type, entity_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='็จฝๆ ธๆ—ฅ่ชŒ่กจ'; + +-- ============================================ +-- Table: 5Why_sessions (Session่กจ) +-- ============================================ +CREATE TABLE IF NOT EXISTS 5Why_sessions ( + session_id VARCHAR(128) PRIMARY KEY, + expires BIGINT UNSIGNED NOT NULL, + data TEXT, + INDEX idx_expires (expires) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Session่กจ'; + +-- ============================================ +-- Insert Default Data +-- ============================================ + +-- ้ ่จญ็ฎก็†่€…ๅธณ่™Ÿ๏ผˆๅฏ†็ขผ: Admin@123456๏ผ‰ +INSERT INTO 5Why_users (employee_id, username, email, password_hash, role, department, position) +VALUES + ('ADMIN001', 'admin', 'admin@example.com', '$2a$10$YourBcryptHashHere', 'super_admin', 'IT', 'System Administrator'), + ('USER001', 'user001', 'user001@example.com', '$2a$10$YourBcryptHashHere', 'user', 'Manufacturing', 'Engineer'), + ('USER002', 'user002', 'user002@example.com', '$2a$10$YourBcryptHashHere', 'admin', 'Quality', 'QA Manager') +ON DUPLICATE KEY UPDATE updated_at = CURRENT_TIMESTAMP; + +-- ้ ่จญ Ollama LLM ้…็ฝฎ +INSERT INTO 5Why_llm_configs (provider, api_url, model_name, is_active, max_tokens, temperature, timeout) +VALUES + ('ollama', 'https://ollama_pjapi.theaken.com', 'qwen2.5:3b', TRUE, 6000, 0.7, 120000) +ON DUPLICATE KEY UPDATE updated_at = CURRENT_TIMESTAMP; + +-- ้ ่จญ็ณป็ตฑ่จญๅฎš +INSERT INTO 5Why_system_settings (setting_key, setting_value, setting_type, description, is_public) +VALUES + ('app_name', '5 Why Root Cause Analyzer', 'string', 'ๆ‡‰็”จ็จ‹ๅผๅ็จฑ', TRUE), + ('app_version', '1.0.0', 'string', 'ๆ‡‰็”จ็จ‹ๅผ็‰ˆๆœฌ', TRUE), + ('max_analysis_per_day', '50', 'number', 'ๆฏๆ—ฅๆœ€ๅคงๅˆ†ๆžๆฌกๆ•ธ', FALSE), + ('enable_email_notification', 'false', 'boolean', 'ๅ•Ÿ็”จEmail้€š็Ÿฅ', FALSE), + ('default_language', 'zh-TW', 'string', '้ ่จญ่ชž่จ€', TRUE), + ('supported_languages', '["zh-TW","zh-CN","en","ja","ko","vi","th"]', 'json', 'ๆ”ฏๆด็š„่ชž่จ€ๅˆ—่กจ', TRUE) +ON DUPLICATE KEY UPDATE updated_at = CURRENT_TIMESTAMP; + +-- ============================================ +-- Create Views (่ฆ–ๅœ–) +-- ============================================ + +-- ไฝฟ็”จ่€…ๅˆ†ๆž็ตฑ่จˆ่ฆ–ๅœ– +CREATE OR REPLACE VIEW 5Why_user_analysis_stats AS +SELECT + u.id AS user_id, + u.username, + u.employee_id, + u.department, + COUNT(a.id) AS total_analyses, + COUNT(CASE WHEN a.status = 'completed' THEN 1 END) AS completed_analyses, + COUNT(CASE WHEN a.status = 'failed' THEN 1 END) AS failed_analyses, + AVG(a.processing_time) AS avg_processing_time, + MAX(a.created_at) AS last_analysis_at +FROM 5Why_users u +LEFT JOIN 5Why_analyses a ON u.id = a.user_id +GROUP BY u.id, u.username, u.employee_id, u.department; + +-- ๆœ€่ฟ‘ๅˆ†ๆž่จ˜้Œ„่ฆ–ๅœ– +CREATE OR REPLACE VIEW 5Why_recent_analyses AS +SELECT + a.id, + a.finding, + u.username, + u.employee_id, + a.output_language, + a.status, + a.processing_time, + a.created_at +FROM 5Why_analyses a +JOIN 5Why_users u ON a.user_id = u.id +ORDER BY a.created_at DESC +LIMIT 100; + +-- ============================================ +-- End of Schema +-- ============================================ diff --git a/models/Analysis.js b/models/Analysis.js index e910872..9de1b52 100644 --- a/models/Analysis.js +++ b/models/Analysis.js @@ -13,7 +13,7 @@ class Analysis { try { const [result] = await pool.execute( - `INSERT INTO analyses (user_id, finding, job_content, output_language, status) + `INSERT INTO 5Why_analyses (user_id, finding, job_content, output_language, status) VALUES (?, ?, ?, ?, 'pending')`, [user_id, finding, job_content, output_language] ); @@ -30,7 +30,7 @@ class Analysis { static async findById(id) { try { const [rows] = await pool.execute( - 'SELECT * FROM analyses WHERE id = ?', + 'SELECT * FROM 5Why_analyses WHERE id = ?', [id] ); return rows[0] || null; @@ -45,7 +45,7 @@ class Analysis { static async updateStatus(id, status, errorMessage = null) { try { await pool.execute( - 'UPDATE analyses SET status = ?, error_message = ? WHERE id = ?', + 'UPDATE 5Why_analyses SET status = ?, error_message = ? WHERE id = ?', [status, errorMessage, id] ); } catch (error) { @@ -66,7 +66,7 @@ class Analysis { try { // ๆ›ดๆ–ฐไธปๅˆ†ๆž่จ˜้Œ„ await connection.execute( - `UPDATE analyses + `UPDATE 5Why_analyses SET problem_restatement = ?, analysis_result = ?, processing_time = ?, status = 'completed' WHERE id = ?`, [problem_restatement, JSON.stringify(analysis_result), processing_time, id] @@ -76,7 +76,7 @@ class Analysis { if (analysis_result.analyses && Array.isArray(analysis_result.analyses)) { for (const perspective of analysis_result.analyses) { const [perspectiveResult] = await connection.execute( - `INSERT INTO analysis_perspectives + `INSERT INTO 5Why_analysis_perspectives (analysis_id, perspective, perspective_icon, root_cause, permanent_solution, logic_check_forward, logic_check_backward, logic_valid) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, @@ -99,7 +99,7 @@ class Analysis { for (const why of perspective.whys) { if (why && why.level) { await connection.execute( - `INSERT INTO analysis_whys + `INSERT INTO 5Why_analysis_whys (perspective_id, level, question, answer, is_verified, verification_note) VALUES (?, ?, ?, ?, ?, ?)`, [ @@ -136,8 +136,8 @@ class Analysis { */ static async getByUserId(userId, page = 1, limit = 10, filters = {}) { const offset = (page - 1) * limit; - let query = 'SELECT * FROM analyses WHERE user_id = ?'; - let countQuery = 'SELECT COUNT(*) as total FROM analyses WHERE user_id = ?'; + let query = 'SELECT * FROM 5Why_analyses WHERE user_id = ?'; + let countQuery = 'SELECT COUNT(*) as total FROM 5Why_analyses WHERE user_id = ?'; const whereParams = [userId]; const whereClauses = []; @@ -168,8 +168,9 @@ class Analysis { query += ' ORDER BY created_at DESC LIMIT ? OFFSET ?'; try { - const [rows] = await pool.execute(query, [...whereParams, limit, offset]); - const [countResult] = await pool.execute(countQuery, whereParams); + // ไฝฟ็”จ pool.query ่€Œ้ž pool.execute๏ผŒๅ› ็‚บ LIMIT/OFFSET ้œ€่ฆๆ•ธๅญ—้กžๅž‹ + const [rows] = await pool.query(query, [...whereParams, parseInt(limit), parseInt(offset)]); + const [countResult] = await pool.query(countQuery, whereParams); return { data: rows, @@ -192,10 +193,10 @@ class Analysis { const offset = (page - 1) * limit; let query = ` SELECT a.*, u.username, u.employee_id - FROM analyses a - JOIN users u ON a.user_id = u.id + FROM 5Why_analyses a + JOIN 5Why_users u ON a.user_id = u.id `; - let countQuery = 'SELECT COUNT(*) as total FROM analyses a JOIN users u ON a.user_id = u.id'; + let countQuery = 'SELECT COUNT(*) as total FROM 5Why_analyses a JOIN 5Why_users u ON a.user_id = u.id'; const whereParams = []; const whereClauses = []; @@ -223,8 +224,9 @@ class Analysis { query += ' ORDER BY a.created_at DESC LIMIT ? OFFSET ?'; try { - const [rows] = await pool.execute(query, [...whereParams, limit, offset]); - const [countResult] = await pool.execute(countQuery, whereParams); + // ไฝฟ็”จ pool.query ่€Œ้ž pool.execute๏ผŒๅ› ็‚บ LIMIT/OFFSET ้œ€่ฆๆ•ธๅญ—้กžๅž‹ + const [rows] = await pool.query(query, [...whereParams, parseInt(limit), parseInt(offset)]); + const [countResult] = await pool.query(countQuery, whereParams); return { data: rows, @@ -251,14 +253,14 @@ class Analysis { // ๅ–ๅพ—ๅˆ†ๆž่ง’ๅบฆ const [perspectives] = await pool.execute( - 'SELECT * FROM analysis_perspectives WHERE analysis_id = ? ORDER BY id', + 'SELECT * FROM 5Why_analysis_perspectives WHERE analysis_id = ? ORDER BY id', [id] ); // ็‚บๆฏๅ€‹่ง’ๅบฆๅ–ๅพ— Whys for (const perspective of perspectives) { const [whys] = await pool.execute( - 'SELECT * FROM analysis_whys WHERE perspective_id = ? ORDER BY level', + 'SELECT * FROM 5Why_analysis_whys WHERE perspective_id = ? ORDER BY level', [perspective.id] ); perspective.whys = whys; @@ -277,7 +279,7 @@ class Analysis { */ static async delete(id) { try { - await pool.execute('DELETE FROM analyses WHERE id = ?', [id]); + await pool.execute('DELETE FROM 5Why_analyses WHERE id = ?', [id]); return true; } catch (error) { throw new Error(`Error deleting analysis: ${error.message}`); @@ -289,9 +291,10 @@ class Analysis { */ static async getRecent(limit = 100) { try { - const [rows] = await pool.execute( - 'SELECT * FROM recent_analyses LIMIT ?', - [limit] + // ไฝฟ็”จ pool.query ่€Œ้ž pool.execute๏ผŒๅ› ็‚บ LIMIT ้œ€่ฆๆ•ธๅญ—้กžๅž‹ + const [rows] = await pool.query( + 'SELECT * FROM 5Why_recent_analyses LIMIT ?', + [parseInt(limit)] ); return rows; } catch (error) { @@ -312,7 +315,7 @@ class Analysis { COUNT(CASE WHEN status = 'processing' THEN 1 END) as processing, AVG(processing_time) as avg_processing_time, MAX(created_at) as last_analysis_at - FROM analyses + FROM 5Why_analyses `; const params = []; diff --git a/models/AuditLog.js b/models/AuditLog.js index 9611c89..026810d 100644 --- a/models/AuditLog.js +++ b/models/AuditLog.js @@ -24,7 +24,7 @@ class AuditLog { try { await pool.execute( - `INSERT INTO audit_logs + `INSERT INTO 5Why_audit_logs (user_id, action, entity_type, entity_id, old_value, new_value, ip_address, user_agent, status, error_message) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, @@ -126,10 +126,10 @@ class AuditLog { const offset = (page - 1) * limit; let query = ` SELECT al.*, u.username, u.employee_id - FROM audit_logs al - LEFT JOIN users u ON al.user_id = u.id + FROM 5Why_audit_logs al + LEFT JOIN 5Why_users u ON al.user_id = u.id `; - let countQuery = 'SELECT COUNT(*) as total FROM audit_logs al'; + let countQuery = 'SELECT COUNT(*) as total FROM 5Why_audit_logs al'; const params = []; const whereClauses = []; @@ -166,11 +166,12 @@ class AuditLog { } query += ' ORDER BY al.created_at DESC LIMIT ? OFFSET ?'; - params.push(limit, offset); + params.push(parseInt(limit), parseInt(offset)); try { - const [rows] = await pool.execute(query, params); - const [countResult] = await pool.execute(countQuery, params.slice(0, -2)); + // ไฝฟ็”จ pool.query ่€Œ้ž pool.execute๏ผŒๅ› ็‚บ LIMIT/OFFSET ้œ€่ฆๆ•ธๅญ—้กžๅž‹ + const [rows] = await pool.query(query, params); + const [countResult] = await pool.query(countQuery, params.slice(0, -2)); return { data: rows, @@ -199,7 +200,7 @@ class AuditLog { static async cleanup(daysToKeep = 90) { try { const [result] = await pool.execute( - 'DELETE FROM audit_logs WHERE created_at < DATE_SUB(NOW(), INTERVAL ? DAY)', + 'DELETE FROM 5Why_audit_logs WHERE created_at < DATE_SUB(NOW(), INTERVAL ? DAY)', [daysToKeep] ); return result.affectedRows; diff --git a/models/User.js b/models/User.js index 4c87a9d..dbc6b15 100644 --- a/models/User.js +++ b/models/User.js @@ -12,7 +12,7 @@ class User { 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 users WHERE id = ?', + '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; @@ -27,7 +27,7 @@ class User { static async findByEmail(email) { try { const [rows] = await pool.execute( - 'SELECT * FROM users WHERE email = ? AND is_active = 1', + 'SELECT * FROM 5Why_users WHERE email = ? AND is_active = 1', [email] ); return rows[0] || null; @@ -42,7 +42,7 @@ class User { static async findByEmployeeId(employeeId) { try { const [rows] = await pool.execute( - 'SELECT * FROM users WHERE employee_id = ? AND is_active = 1', + 'SELECT * FROM 5Why_users WHERE employee_id = ? AND is_active = 1', [employeeId] ); return rows[0] || null; @@ -69,7 +69,7 @@ class User { const passwordHash = await bcrypt.hash(password, 10); const [result] = await pool.execute( - `INSERT INTO users (employee_id, username, email, password_hash, role, department, position) + `INSERT INTO 5Why_users (employee_id, username, email, password_hash, role, department, position) VALUES (?, ?, ?, ?, ?, ?, ?)`, [employee_id, username, email, passwordHash, role, department, position] ); @@ -91,7 +91,7 @@ class User { try { await pool.execute( - `UPDATE users + `UPDATE 5Why_users SET username = ?, email = ?, role = ?, department = ?, position = ?, is_active = ? WHERE id = ?`, [username, email, role, department, position, is_active, id] @@ -110,7 +110,7 @@ class User { try { const passwordHash = await bcrypt.hash(newPassword, 10); await pool.execute( - 'UPDATE users SET password_hash = ? WHERE id = ?', + 'UPDATE 5Why_users SET password_hash = ? WHERE id = ?', [passwordHash, id] ); return true; @@ -125,7 +125,7 @@ class User { static async updateLastLogin(id) { try { await pool.execute( - 'UPDATE users SET last_login_at = CURRENT_TIMESTAMP WHERE id = ?', + 'UPDATE 5Why_users SET last_login_at = CURRENT_TIMESTAMP WHERE id = ?', [id] ); } catch (error) { @@ -138,8 +138,8 @@ class User { */ 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 users'; - let countQuery = 'SELECT COUNT(*) as total FROM users'; + 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 = []; @@ -167,8 +167,9 @@ class User { query += ' ORDER BY created_at DESC LIMIT ? OFFSET ?'; try { - const [rows] = await pool.execute(query, [...whereParams, limit, offset]); - const [countResult] = await pool.execute(countQuery, whereParams); + // ไฝฟ็”จ 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, @@ -190,7 +191,7 @@ class User { static async delete(id) { try { await pool.execute( - 'UPDATE users SET is_active = 0 WHERE id = ?', + 'UPDATE 5Why_users SET is_active = 0 WHERE id = ?', [id] ); return true; @@ -204,7 +205,7 @@ class User { */ static async hardDelete(id) { try { - await pool.execute('DELETE FROM users WHERE id = ?', [id]); + await pool.execute('DELETE FROM 5Why_users WHERE id = ?', [id]); return true; } catch (error) { throw new Error(`Error hard deleting user: ${error.message}`); @@ -217,7 +218,7 @@ class User { static async getStats(userId) { try { const [rows] = await pool.execute( - 'SELECT * FROM user_analysis_stats WHERE user_id = ?', + 'SELECT * FROM 5Why_user_analysis_stats WHERE user_id = ?', [userId] ); return rows[0] || null; diff --git a/routes/llmConfig.js b/routes/llmConfig.js index 40a1fb5..7ce56dc 100644 --- a/routes/llmConfig.js +++ b/routes/llmConfig.js @@ -13,7 +13,7 @@ const router = express.Router(); router.get('/', requireAuth, asyncHandler(async (req, res) => { const configs = await query( `SELECT id, provider, api_url, model_name, is_active, created_at, updated_at - FROM llm_configs + FROM 5Why_llm_configs ORDER BY is_active DESC, created_at DESC` ); @@ -30,7 +30,7 @@ router.get('/', requireAuth, asyncHandler(async (req, res) => { router.get('/active', requireAuth, asyncHandler(async (req, res) => { const [config] = await query( `SELECT id, provider, api_url, model_name, temperature, max_tokens, timeout - FROM llm_configs + FROM 5Why_llm_configs WHERE is_active = 1 LIMIT 1` ); @@ -72,7 +72,7 @@ router.post('/', requireSuperAdmin, asyncHandler(async (req, res) => { } const result = await query( - `INSERT INTO llm_configs + `INSERT INTO 5Why_llm_configs (provider, api_url, api_key, model_name, temperature, max_tokens, timeout) VALUES (?, ?, ?, ?, ?, ?, ?)`, [ @@ -128,7 +128,7 @@ router.put('/:id', requireSuperAdmin, asyncHandler(async (req, res) => { } // ๆชขๆŸฅ้…็ฝฎๆ˜ฏๅฆๅญ˜ๅœจ - const [existing] = await query('SELECT id FROM llm_configs WHERE id = ?', [configId]); + const [existing] = await query('SELECT id FROM 5Why_llm_configs WHERE id = ?', [configId]); if (!existing) { return res.status(404).json({ success: false, @@ -137,7 +137,7 @@ router.put('/:id', requireSuperAdmin, asyncHandler(async (req, res) => { } await query( - `UPDATE llm_configs + `UPDATE 5Why_llm_configs SET provider = ?, api_url = ?, api_key = ?, model_name = ?, temperature = ?, max_tokens = ?, timeout = ?, updated_at = NOW() WHERE id = ?`, @@ -178,7 +178,7 @@ router.put('/:id/activate', requireSuperAdmin, asyncHandler(async (req, res) => const configId = parseInt(req.params.id); // ๆชขๆŸฅ้…็ฝฎๆ˜ฏๅฆๅญ˜ๅœจ - const [existing] = await query('SELECT id, provider FROM llm_configs WHERE id = ?', [configId]); + const [existing] = await query('SELECT id, provider FROM 5Why_llm_configs WHERE id = ?', [configId]); if (!existing) { return res.status(404).json({ success: false, @@ -187,10 +187,10 @@ router.put('/:id/activate', requireSuperAdmin, asyncHandler(async (req, res) => } // ๅ…ˆๅœ็”จๆ‰€ๆœ‰้…็ฝฎ - await query('UPDATE llm_configs SET is_active = 0'); + await query('UPDATE 5Why_llm_configs SET is_active = 0'); // ๅ•Ÿ็”จๆŒ‡ๅฎš้…็ฝฎ - await query('UPDATE llm_configs SET is_active = 1, updated_at = NOW() WHERE id = ?', [configId]); + await query('UPDATE 5Why_llm_configs SET is_active = 1, updated_at = NOW() WHERE id = ?', [configId]); // ่จ˜้Œ„็จฝๆ ธๆ—ฅ่ชŒ await AuditLog.logUpdate( @@ -217,7 +217,7 @@ router.delete('/:id', requireSuperAdmin, asyncHandler(async (req, res) => { const configId = parseInt(req.params.id); // ๆชขๆŸฅๆ˜ฏๅฆ็‚บๅ•Ÿ็”จไธญ็š„้…็ฝฎ - const [existing] = await query('SELECT is_active FROM llm_configs WHERE id = ?', [configId]); + const [existing] = await query('SELECT is_active FROM 5Why_llm_configs WHERE id = ?', [configId]); if (!existing) { return res.status(404).json({ success: false, @@ -232,7 +232,7 @@ router.delete('/:id', requireSuperAdmin, asyncHandler(async (req, res) => { }); } - await query('DELETE FROM llm_configs WHERE id = ?', [configId]); + await query('DELETE FROM 5Why_llm_configs WHERE id = ?', [configId]); // ่จ˜้Œ„็จฝๆ ธๆ—ฅ่ชŒ await AuditLog.logDelete( diff --git a/scripts/add-deepseek-config.js b/scripts/add-deepseek-config.js index b4edaab..2287c61 100644 --- a/scripts/add-deepseek-config.js +++ b/scripts/add-deepseek-config.js @@ -17,7 +17,7 @@ async function addDeepSeekConfig() { try { // Check if DeepSeek config already exists const existing = await query( - `SELECT id FROM llm_configs WHERE provider_name = 'DeepSeek' LIMIT 1` + `SELECT id FROM 5Why_llm_configs WHERE provider = 'DeepSeek' LIMIT 1` ); if (existing.length > 0) { @@ -35,12 +35,12 @@ async function addDeepSeekConfig() { } // First, deactivate all existing configs - await query('UPDATE llm_configs SET is_active = 0'); + await query('UPDATE 5Why_llm_configs SET is_active = 0'); // Insert DeepSeek configuration const result = await query( - `INSERT INTO llm_configs - (provider_name, api_endpoint, api_key, model_name, temperature, max_tokens, timeout_seconds, is_active) + `INSERT INTO 5Why_llm_configs + (provider, api_url, api_key, model_name, temperature, max_tokens, timeout, is_active) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [ 'DeepSeek', @@ -49,7 +49,7 @@ async function addDeepSeekConfig() { process.env.DEEPSEEK_MODEL || 'deepseek-chat', 0.7, 6000, - 120, + 120000, 1 // Set as active ] ); diff --git a/scripts/add-ollama-config.js b/scripts/add-ollama-config.js new file mode 100644 index 0000000..6b1d759 --- /dev/null +++ b/scripts/add-ollama-config.js @@ -0,0 +1,81 @@ +#!/usr/bin/env node +/** + * Add Ollama LLM Configuration + * This script adds Ollama configuration to the llm_configs table + */ + +import { pool, query } from '../config.js'; +import dotenv from 'dotenv'; + +dotenv.config(); + +async function addOllamaConfig() { + console.log('โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”'); + console.log(' Adding Ollama LLM Configuration'); + console.log('โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\n'); + + try { + // Check if Ollama config already exists + const existing = await query( + `SELECT id FROM 5Why_llm_configs WHERE provider = 'Ollama' LIMIT 1` + ); + + if (existing.length > 0) { + console.log('โœ… Ollama configuration already exists (ID:', existing[0].id, ')'); + console.log(' Skipping...\n'); + return; + } + + // First, deactivate all existing configs + await query('UPDATE 5Why_llm_configs SET is_active = 0'); + + // Insert Ollama configuration + const result = await query( + `INSERT INTO 5Why_llm_configs + (provider, api_url, api_key, model_name, temperature, max_tokens, timeout, is_active) + VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, + [ + 'Ollama', + process.env.OLLAMA_API_URL || 'https://ollama_pjapi.theaken.com', + null, // Ollama doesn't need API key + process.env.OLLAMA_MODEL || 'qwen2.5:3b', + 0.7, + 6000, + 120000, + 1 // Set as active + ] + ); + + console.log('โœ… Ollama configuration added successfully!'); + console.log(' Config ID:', result.insertId); + console.log(' Provider: Ollama'); + console.log(' Model: qwen2.5:3b'); + console.log(' API URL:', process.env.OLLAMA_API_URL || 'https://ollama_pjapi.theaken.com'); + console.log(' Status: Active\n'); + + console.log('๐Ÿ“ Notes:'); + console.log(' - Ollama is FREE and runs on your infrastructure'); + console.log(' - No API key required'); + console.log(' - Current model: qwen2.5:3b'); + console.log(' - You can manage it in Admin Panel > LLM ้…็ฝฎ\n'); + + } catch (error) { + console.error('โŒ Error adding Ollama configuration:', error.message); + process.exit(1); + } finally { + await pool.end(); + } +} + +// Run the script +addOllamaConfig() + .then(() => { + console.log('โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”'); + console.log(' Configuration Complete'); + console.log('โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\n'); + process.exit(0); + }) + .catch((error) => { + console.error('Fatal error:', error); + process.exit(1); + }); diff --git a/scripts/migrate-table-prefix.js b/scripts/migrate-table-prefix.js new file mode 100644 index 0000000..873aa6b --- /dev/null +++ b/scripts/migrate-table-prefix.js @@ -0,0 +1,177 @@ +#!/usr/bin/env node +/** + * Database Migration Script + * Renames all tables to add 5Why_ prefix + * + * Run: node scripts/migrate-table-prefix.js + */ + +import mysql from 'mysql2/promise'; +import dotenv from 'dotenv'; + +dotenv.config(); + +const dbConfig = { + host: process.env.DB_HOST || 'mysql.theaken.com', + port: parseInt(process.env.DB_PORT) || 33306, + user: process.env.DB_USER || 'A102', + password: process.env.DB_PASSWORD || 'Bb123456', + database: process.env.DB_NAME || 'db_A102' +}; + +// ่ฆ้‡ๆ–ฐๅ‘ฝๅ็š„่กจ๏ผˆ่ˆŠๅ -> ๆ–ฐๅ๏ผ‰ +const tableRenames = [ + { old: 'users', new: '5Why_users' }, + { old: 'analyses', new: '5Why_analyses' }, + { old: 'analysis_perspectives', new: '5Why_analysis_perspectives' }, + { old: 'analysis_whys', new: '5Why_analysis_whys' }, + { old: 'llm_configs', new: '5Why_llm_configs' }, + { old: 'system_settings', new: '5Why_system_settings' }, + { old: 'audit_logs', new: '5Why_audit_logs' }, + { old: 'sessions', new: '5Why_sessions' } +]; + +// ่ฆ้‡ๆ–ฐๅปบ็ซ‹็š„่ฆ–ๅœ– +const viewRenames = [ + { old: 'user_analysis_stats', new: '5Why_user_analysis_stats' }, + { old: 'recent_analyses', new: '5Why_recent_analyses' } +]; + +async function migrateTablePrefix() { + let connection; + + try { + console.log('\nโ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—'); + console.log('โ•‘ 5 Why Analyzer - Table Prefix Migration โ•‘'); + console.log('โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•\n'); + + console.log('๐Ÿ”„ Connecting to database...'); + connection = await mysql.createConnection(dbConfig); + console.log('โœ… Connected successfully\n'); + + // ๅ…ˆๅˆช้™ค่ˆŠ็š„่ฆ–ๅœ–๏ผˆๅ› ็‚บๅฎƒๅ€‘ไพ่ณด่กจ๏ผ‰ + console.log('๐Ÿ“‹ Dropping old views...'); + for (const view of viewRenames) { + try { + await connection.execute(`DROP VIEW IF EXISTS ${view.old}`); + console.log(` โœ“ Dropped view: ${view.old}`); + } catch (err) { + console.log(` โš  View ${view.old} not found or already dropped`); + } + } + console.log(''); + + // ้‡ๆ–ฐๅ‘ฝๅ่กจ + console.log('๐Ÿ“‹ Renaming tables...'); + for (const table of tableRenames) { + try { + // ๆชขๆŸฅ่ˆŠ่กจๆ˜ฏๅฆๅญ˜ๅœจ + const [oldExists] = await connection.execute( + `SELECT COUNT(*) as count FROM information_schema.tables + WHERE table_schema = ? AND table_name = ?`, + [dbConfig.database, table.old] + ); + + // ๆชขๆŸฅๆ–ฐ่กจๆ˜ฏๅฆๅทฒๅญ˜ๅœจ + const [newExists] = await connection.execute( + `SELECT COUNT(*) as count FROM information_schema.tables + WHERE table_schema = ? AND table_name = ?`, + [dbConfig.database, table.new] + ); + + if (oldExists[0].count > 0 && newExists[0].count === 0) { + await connection.execute(`RENAME TABLE \`${table.old}\` TO \`${table.new}\``); + console.log(` โœ“ Renamed: ${table.old} -> ${table.new}`); + } else if (newExists[0].count > 0) { + console.log(` โš  Table ${table.new} already exists, skipping...`); + } else { + console.log(` โš  Table ${table.old} not found, skipping...`); + } + } catch (err) { + console.error(` โœ— Error renaming ${table.old}: ${err.message}`); + } + } + console.log(''); + + // ๅปบ็ซ‹ๆ–ฐ็š„่ฆ–ๅœ– + console.log('๐Ÿ“‹ Creating new views...'); + + // user_analysis_stats ่ฆ–ๅœ– + try { + await connection.execute(` + CREATE OR REPLACE VIEW 5Why_user_analysis_stats AS + SELECT + u.id AS user_id, + u.username, + u.employee_id, + u.department, + COUNT(a.id) AS total_analyses, + COUNT(CASE WHEN a.status = 'completed' THEN 1 END) AS completed_analyses, + COUNT(CASE WHEN a.status = 'failed' THEN 1 END) AS failed_analyses, + AVG(a.processing_time) AS avg_processing_time, + MAX(a.created_at) AS last_analysis_at + FROM 5Why_users u + LEFT JOIN 5Why_analyses a ON u.id = a.user_id + GROUP BY u.id, u.username, u.employee_id, u.department + `); + console.log(' โœ“ Created view: 5Why_user_analysis_stats'); + } catch (err) { + console.error(` โœ— Error creating view 5Why_user_analysis_stats: ${err.message}`); + } + + // recent_analyses ่ฆ–ๅœ– + try { + await connection.execute(` + CREATE OR REPLACE VIEW 5Why_recent_analyses AS + SELECT + a.id, + a.finding, + u.username, + u.employee_id, + a.output_language, + a.status, + a.processing_time, + a.created_at + FROM 5Why_analyses a + JOIN 5Why_users u ON a.user_id = u.id + ORDER BY a.created_at DESC + LIMIT 100 + `); + console.log(' โœ“ Created view: 5Why_recent_analyses'); + } catch (err) { + console.error(` โœ— Error creating view 5Why_recent_analyses: ${err.message}`); + } + + console.log('\nโ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—'); + console.log('โ•‘ Migration Complete! โ•‘'); + console.log('โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•\n'); + + // ๅˆ—ๅ‡บๆ‰€ๆœ‰ 5Why_ ่กจ + const [tables] = await connection.execute( + `SELECT table_name FROM information_schema.tables + WHERE table_schema = ? AND table_name LIKE '5Why_%' + ORDER BY table_name`, + [dbConfig.database] + ); + + if (tables.length > 0) { + console.log('๐Ÿ“Š 5Why tables in database:'); + tables.forEach((table, index) => { + console.log(` ${index + 1}. ${table.TABLE_NAME || table.table_name}`); + }); + } + + } catch (error) { + console.error('\nโŒ Migration failed:'); + console.error(' Error:', error.message); + process.exit(1); + } finally { + if (connection) { + await connection.end(); + console.log('\n๐Ÿ”Œ Database connection closed\n'); + } + } +} + +// ๅŸท่กŒ้ท็งป +migrateTablePrefix(); diff --git a/scripts/seed-test-users.js b/scripts/seed-test-users.js index e5a30ef..e76de96 100644 --- a/scripts/seed-test-users.js +++ b/scripts/seed-test-users.js @@ -60,7 +60,7 @@ async function seedTestUsers() { for (const user of testUsers) { // ๆชขๆŸฅไฝฟ็”จ่€…ๆ˜ฏๅฆๅทฒๅญ˜ๅœจ (ๅช็”จ email ็ฒพ็ขบๅŒน้…) const [existing] = await connection.execute( - 'SELECT id FROM users WHERE email = ?', + 'SELECT id FROM 5Why_users WHERE email = ?', [user.email] ); @@ -68,7 +68,7 @@ async function seedTestUsers() { // ๆ›ดๆ–ฐ็พๆœ‰ไฝฟ็”จ่€…็š„ๅฏ†็ขผ const passwordHash = await bcrypt.hash(user.password, 10); await connection.execute( - 'UPDATE users SET password_hash = ?, role = ?, is_active = 1, employee_id = ? WHERE email = ?', + 'UPDATE 5Why_users SET password_hash = ?, role = ?, is_active = 1, employee_id = ? WHERE email = ?', [passwordHash, user.role, user.employee_id, user.email] ); console.log(`๐Ÿ”„ Updated: ${user.email} (${user.role})`); @@ -76,7 +76,7 @@ async function seedTestUsers() { // ๅปบ็ซ‹ๆ–ฐไฝฟ็”จ่€… const passwordHash = await bcrypt.hash(user.password, 10); await connection.execute( - `INSERT INTO users (employee_id, username, email, password_hash, role, department, position, is_active) + `INSERT INTO 5Why_users (employee_id, username, email, password_hash, role, department, position, is_active) VALUES (?, ?, ?, ?, ?, ?, ?, 1)`, [user.employee_id, user.username, user.email, passwordHash, user.role, user.department, user.position] );