148 lines
4.4 KiB
JavaScript
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;
|
|
}
|