esp32blockly/server/db.js

148 lines
4.4 KiB
JavaScript

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 listStudents() {
const [rows] = await getPool().query(
`SELECT id, username, role, created_at
FROM users
WHERE role = 'student'
ORDER BY username ASC`,
);
return rows;
}
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;
}