251 lines
12 KiB
JavaScript
251 lines
12 KiB
JavaScript
const mysql = require('mysql2/promise');
|
|
const { hashPassword } = require('./auth');
|
|
|
|
function createPool() {
|
|
return mysql.createPool({
|
|
host: process.env.DB_HOST || '127.0.0.1',
|
|
port: Number(process.env.DB_PORT || 3306),
|
|
user: process.env.DB_USER || 'signage_user',
|
|
password: process.env.DB_PASSWORD || 'signage_password',
|
|
database: process.env.DB_NAME || 'signage',
|
|
waitForConnections: true,
|
|
connectionLimit: 10,
|
|
namedPlaceholders: true
|
|
});
|
|
}
|
|
|
|
async function addColumnIfMissing(pool, tableName, columnName, columnDefinition) {
|
|
const [rows] = await pool.query(
|
|
`SELECT COUNT(*) AS column_count
|
|
FROM information_schema.columns
|
|
WHERE table_schema = DATABASE()
|
|
AND table_name = ?
|
|
AND column_name = ?`,
|
|
[tableName, columnName]
|
|
);
|
|
|
|
if (rows.length && Number(rows[0].column_count) > 0) {
|
|
return;
|
|
}
|
|
|
|
await pool.query(`ALTER TABLE \`${tableName}\` ADD COLUMN \`${columnName}\` ${columnDefinition}`);
|
|
}
|
|
|
|
async function ensureSchema(pool) {
|
|
await pool.query(`
|
|
CREATE TABLE IF NOT EXISTS canvas_sizes (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
name VARCHAR(255) NOT NULL,
|
|
width INT NOT NULL,
|
|
height INT NOT NULL,
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
modified_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
UNIQUE KEY uq_canvas_sizes_dimensions (width, height)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
|
`);
|
|
await addColumnIfMissing(pool, 'canvas_sizes', 'modified_at', 'TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');
|
|
await addColumnIfMissing(pool, 'canvas_sizes', 'created_by', 'INT NULL');
|
|
await addColumnIfMissing(pool, 'canvas_sizes', 'modified_by', 'INT NULL');
|
|
|
|
await pool.query(`
|
|
CREATE TABLE IF NOT EXISTS playlists (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
name VARCHAR(255) NOT NULL,
|
|
fade_between_slides TINYINT(1) NOT NULL DEFAULT 0,
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
modified_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
|
`);
|
|
await addColumnIfMissing(pool, 'playlists', 'fade_between_slides', 'TINYINT(1) NOT NULL DEFAULT 0');
|
|
await addColumnIfMissing(pool, 'playlists', 'modified_at', 'TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');
|
|
await addColumnIfMissing(pool, 'playlists', 'created_by', 'INT NULL');
|
|
await addColumnIfMissing(pool, 'playlists', 'modified_by', 'INT NULL');
|
|
|
|
await pool.query(`
|
|
CREATE TABLE IF NOT EXISTS slide_templates (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
name VARCHAR(255) NOT NULL,
|
|
canvas_size_id INT NULL,
|
|
background_image_path VARCHAR(512) NULL,
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
modified_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
|
`);
|
|
|
|
await addColumnIfMissing(pool, 'slide_templates', 'canvas_size_id', 'INT NULL');
|
|
await addColumnIfMissing(pool, 'slide_templates', 'background_image_path', 'VARCHAR(512) NULL');
|
|
await addColumnIfMissing(pool, 'slide_templates', 'modified_at', 'TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');
|
|
await addColumnIfMissing(pool, 'slide_templates', 'created_by', 'INT NULL');
|
|
await addColumnIfMissing(pool, 'slide_templates', 'modified_by', 'INT NULL');
|
|
|
|
await pool.query(`
|
|
INSERT IGNORE INTO canvas_sizes (name, width, height) VALUES
|
|
('Full HD', 1920, 1080),
|
|
('HD', 1280, 720),
|
|
('4K UHD', 3840, 2160),
|
|
('Portrait Full HD', 1080, 1920),
|
|
('Portrait HD', 720, 1280)
|
|
`);
|
|
|
|
const [legacyTemplateColumns] = await pool.query(`
|
|
SELECT COUNT(*) AS column_count
|
|
FROM information_schema.columns
|
|
WHERE table_schema = DATABASE()
|
|
AND table_name = 'slide_templates'
|
|
AND column_name IN ('canvas_width', 'canvas_height')
|
|
`);
|
|
if (legacyTemplateColumns[0] && Number(legacyTemplateColumns[0].column_count) === 2) {
|
|
await pool.query(`
|
|
UPDATE slide_templates st
|
|
JOIN canvas_sizes cs ON cs.width = st.canvas_width AND cs.height = st.canvas_height
|
|
SET st.canvas_size_id = cs.id
|
|
WHERE st.canvas_size_id IS NULL
|
|
`);
|
|
}
|
|
|
|
await pool.query(`
|
|
CREATE TABLE IF NOT EXISTS slide_template_regions (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
template_id INT NOT NULL,
|
|
region_key VARCHAR(100) NOT NULL,
|
|
region_type VARCHAR(20) NOT NULL,
|
|
label VARCHAR(255) NOT NULL,
|
|
x INT NOT NULL DEFAULT 0,
|
|
y INT NOT NULL DEFAULT 0,
|
|
width INT NOT NULL DEFAULT 100,
|
|
height INT NOT NULL DEFAULT 100,
|
|
z_index INT NOT NULL DEFAULT 0,
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
modified_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
|
`);
|
|
|
|
await addColumnIfMissing(pool, 'slide_template_regions', 'font_family', 'VARCHAR(100) NULL');
|
|
await addColumnIfMissing(pool, 'slide_template_regions', 'modified_at', 'TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');
|
|
await addColumnIfMissing(pool, 'slide_template_regions', 'created_by', 'INT NULL');
|
|
await addColumnIfMissing(pool, 'slide_template_regions', 'modified_by', 'INT NULL');
|
|
|
|
await pool.query(`
|
|
CREATE TABLE IF NOT EXISTS slides (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
title VARCHAR(255) NOT NULL,
|
|
body TEXT NULL,
|
|
template_id INT NULL,
|
|
content_json JSON NULL,
|
|
media_path VARCHAR(512) NULL,
|
|
media_type VARCHAR(100) NULL,
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
modified_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
|
`);
|
|
|
|
await addColumnIfMissing(pool, 'slides', 'body', 'TEXT NULL');
|
|
await addColumnIfMissing(pool, 'slides', 'template_id', 'INT NULL');
|
|
await addColumnIfMissing(pool, 'slides', 'content_json', 'JSON NULL');
|
|
await addColumnIfMissing(pool, 'slides', 'media_path', 'VARCHAR(512) NULL');
|
|
await addColumnIfMissing(pool, 'slides', 'media_type', 'VARCHAR(100) NULL');
|
|
await addColumnIfMissing(pool, 'slides', 'modified_at', 'TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');
|
|
await addColumnIfMissing(pool, 'slides', 'created_by', 'INT NULL');
|
|
await addColumnIfMissing(pool, 'slides', 'modified_by', 'INT NULL');
|
|
|
|
await pool.query(`
|
|
CREATE TABLE IF NOT EXISTS playlist_slides (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
playlist_id INT NOT NULL,
|
|
slide_id INT NOT NULL,
|
|
position INT NOT NULL DEFAULT 0,
|
|
duration_seconds INT NOT NULL DEFAULT 10,
|
|
schedule_mode VARCHAR(20) NOT NULL DEFAULT 'always',
|
|
schedule_start_datetime DATETIME NULL,
|
|
schedule_end_datetime DATETIME NULL,
|
|
schedule_start_time TIME NULL,
|
|
schedule_end_time TIME NULL,
|
|
schedule_days_json JSON NULL,
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
modified_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
CONSTRAINT fk_playlist_slides_playlist FOREIGN KEY (playlist_id) REFERENCES playlists(id) ON DELETE CASCADE,
|
|
CONSTRAINT fk_playlist_slides_slide FOREIGN KEY (slide_id) REFERENCES slides(id) ON DELETE CASCADE
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
|
`);
|
|
|
|
await addColumnIfMissing(pool, 'playlist_slides', 'duration_seconds', 'INT NOT NULL DEFAULT 10');
|
|
await addColumnIfMissing(pool, 'playlist_slides', 'schedule_mode', "VARCHAR(20) NOT NULL DEFAULT 'always'");
|
|
await addColumnIfMissing(pool, 'playlist_slides', 'schedule_start_datetime', 'DATETIME NULL');
|
|
await addColumnIfMissing(pool, 'playlist_slides', 'schedule_end_datetime', 'DATETIME NULL');
|
|
await addColumnIfMissing(pool, 'playlist_slides', 'schedule_start_time', 'TIME NULL');
|
|
await addColumnIfMissing(pool, 'playlist_slides', 'schedule_end_time', 'TIME NULL');
|
|
await addColumnIfMissing(pool, 'playlist_slides', 'schedule_days_json', 'JSON NULL');
|
|
await addColumnIfMissing(pool, 'playlist_slides', 'modified_at', 'TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');
|
|
await addColumnIfMissing(pool, 'playlist_slides', 'created_by', 'INT NULL');
|
|
await addColumnIfMissing(pool, 'playlist_slides', 'modified_by', 'INT NULL');
|
|
|
|
await pool.query(`
|
|
CREATE TABLE IF NOT EXISTS screens (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
name VARCHAR(255) NOT NULL,
|
|
slug VARCHAR(255) NOT NULL UNIQUE,
|
|
playlist_id INT NULL,
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
modified_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
CONSTRAINT fk_screens_playlist FOREIGN KEY (playlist_id) REFERENCES playlists(id) ON DELETE SET NULL
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
|
`);
|
|
|
|
await addColumnIfMissing(pool, 'screens', 'playlist_id', 'INT NULL');
|
|
await addColumnIfMissing(pool, 'screens', 'modified_at', 'TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');
|
|
await addColumnIfMissing(pool, 'screens', 'created_by', 'INT NULL');
|
|
await addColumnIfMissing(pool, 'screens', 'modified_by', 'INT NULL');
|
|
|
|
await pool.query(`
|
|
CREATE TABLE IF NOT EXISTS users (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
name VARCHAR(255) NULL,
|
|
username VARCHAR(255) NOT NULL UNIQUE,
|
|
password_hash CHAR(64) NOT NULL,
|
|
password_salt VARCHAR(64) NOT NULL,
|
|
password_iterations INT NOT NULL,
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
modified_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
|
`);
|
|
await addColumnIfMissing(pool, 'users', 'name', 'VARCHAR(255) NULL');
|
|
await addColumnIfMissing(pool, 'users', 'password_hash', 'CHAR(64) NOT NULL');
|
|
await addColumnIfMissing(pool, 'users', 'password_salt', 'VARCHAR(64) NOT NULL');
|
|
await addColumnIfMissing(pool, 'users', 'password_iterations', 'INT NOT NULL');
|
|
await addColumnIfMissing(pool, 'users', 'modified_at', 'TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');
|
|
await addColumnIfMissing(pool, 'users', 'created_by', 'INT NULL');
|
|
await addColumnIfMissing(pool, 'users', 'modified_by', 'INT NULL');
|
|
|
|
await pool.query(`
|
|
CREATE TABLE IF NOT EXISTS auth_sessions (
|
|
session_hash CHAR(64) PRIMARY KEY,
|
|
user_id INT NOT NULL,
|
|
expires_at DATETIME NOT NULL,
|
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
last_used_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
CONSTRAINT fk_auth_sessions_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
|
`);
|
|
await addColumnIfMissing(pool, 'auth_sessions', 'created_by', 'INT NULL');
|
|
await addColumnIfMissing(pool, 'auth_sessions', 'modified_by', 'INT NULL');
|
|
|
|
const [userCountRows] = await pool.query('SELECT COUNT(*) AS user_count FROM users');
|
|
if (!userCountRows.length || Number(userCountRows[0].user_count) === 0) {
|
|
const username = String(process.env.DEFAULT_ADMIN_USERNAME || 'admin').trim() || 'admin';
|
|
const name = String(process.env.DEFAULT_ADMIN_NAME || 'Admin').trim() || 'Admin';
|
|
const password = String(process.env.DEFAULT_ADMIN_PASSWORD || 'admin').trim() || 'admin';
|
|
const passwordRecord = hashPassword(password);
|
|
await pool.query(
|
|
'INSERT INTO users (name, username, password_hash, password_salt, password_iterations, created_by, modified_by) VALUES (?, ?, ?, ?, ?, ?, ?)',
|
|
[name, username, passwordRecord.hash, passwordRecord.salt, passwordRecord.iterations, null, null]
|
|
);
|
|
}
|
|
|
|
await pool.query('UPDATE users SET name = username WHERE name IS NULL OR name = ""');
|
|
}
|
|
|
|
module.exports = {
|
|
createPool,
|
|
ensureSchema
|
|
};
|