import mysql from 'mysql2/promise'; let pool = null; export function getPool() { if (pool) return pool; pool = mysql.createPool({ host: process.env.MYSQL_HOST || 'localhost', port: Number(process.env.MYSQL_PORT) || 3306, user: process.env.MYSQL_USER || 'root', password: process.env.MYSQL_PASSWORD || '', database: process.env.MYSQL_DATABASE || 'esp32block', waitForConnections: true, connectionLimit: 8, queueLimit: 0, charset: 'utf8mb4', }); return pool; } export async function bootstrap() { const db = getPool(); await db.query(` CREATE TABLE IF NOT EXISTS users ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(64) NOT NULL UNIQUE, password_hash VARCHAR(255) NOT NULL, role ENUM('student','teacher') NOT NULL DEFAULT 'student', created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 `); await db.query(` CREATE TABLE IF NOT EXISTS sessions ( token CHAR(48) PRIMARY KEY, user_id INT NOT NULL, expires_at DATETIME NOT NULL, INDEX idx_sessions_user (user_id), CONSTRAINT fk_sessions_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 `); await db.query(` CREATE TABLE IF NOT EXISTS projects ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, name VARCHAR(128) NOT NULL, state_json LONGTEXT NOT NULL, updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uniq_user_project (user_id, name), INDEX idx_projects_user (user_id), CONSTRAINT fk_projects_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 `); } export async function findUserByUsername(username) { const [rows] = await getPool().query( 'SELECT id, username, password_hash, role FROM users WHERE username = ? LIMIT 1', [username], ); return rows[0] || null; } export async function findUserById(id) { const [rows] = await getPool().query( 'SELECT id, username, role FROM users WHERE id = ? LIMIT 1', [id], ); return rows[0] || null; } export async function createUser({ username, passwordHash, role = 'student' }) { const [result] = await getPool().query( 'INSERT INTO users (username, password_hash, role) VALUES (?, ?, ?)', [username, passwordHash, role], ); return { id: result.insertId, username, role }; } export async function createSession({ token, userId, expiresAt }) { await getPool().query( 'INSERT INTO sessions (token, user_id, expires_at) VALUES (?, ?, ?)', [token, userId, expiresAt], ); } export async function findSession(token) { const [rows] = await getPool().query( `SELECT s.token, s.user_id, s.expires_at, u.username, u.role FROM sessions s JOIN users u ON u.id = s.user_id WHERE s.token = ? AND s.expires_at > NOW() LIMIT 1`, [token], ); return rows[0] || null; } export async function deleteSession(token) { await getPool().query('DELETE FROM sessions WHERE token = ?', [token]); } export async function purgeExpiredSessions() { await getPool().query('DELETE FROM sessions WHERE expires_at <= NOW()'); } export async function listProjectsByUser(userId) { const [rows] = await getPool().query( 'SELECT name, state_json, updated_at FROM projects WHERE user_id = ? ORDER BY name ASC', [userId], ); return rows; } export async function upsertProject({ userId, name, stateJson }) { await getPool().query( `INSERT INTO projects (user_id, name, state_json) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE state_json = VALUES(state_json), updated_at = CURRENT_TIMESTAMP`, [userId, name, stateJson], ); } export async function getProjectByName({ userId, name }) { const [rows] = await getPool().query( 'SELECT name, state_json, updated_at FROM projects WHERE user_id = ? AND name = ? LIMIT 1', [userId, name], ); return rows[0] || null; } export async function deleteProjectByName({ userId, name }) { const [result] = await getPool().query( 'DELETE FROM projects WHERE user_id = ? AND name = ?', [userId, name], ); return result.affectedRows > 0; }