Refactor web server and release workflow

This commit is contained in:
2026-07-14 18:35:24 +01:00
parent 9070ded66d
commit 4e0e87c86c
76 changed files with 616 additions and 84 deletions
+48 -1
View File
@@ -6,6 +6,7 @@ const path = require('path');
const { WebSocketServer, WebSocket } = require('ws');
const common = require('./common');
// Playlist assembly and revision helpers.
async function buildScreenPlaylist(pool, slug) {
const [screenRows] = await pool.query('SELECT id, name, slug, playlist_id, created_at, modified_at, created_by, modified_by FROM screens WHERE slug = ?', [slug]);
if (!screenRows.length) {
@@ -147,12 +148,13 @@ function getPlaylistRevision(screen, playlist, slideRows, templateRows, regionRo
}
// Player runtime, upload API, and websocket wiring.
async function start() {
const app = express();
const pool = common.createPool();
const PORT = Number(process.env.PLAYER_PORT || 3001);
const ASSET_DIR = path.join(__dirname, 'player', 'public');
const UPLOAD_DIR = process.env.UPLOAD_DIR || path.join(__dirname, '..', 'uploads');
const UPLOAD_DIR = path.join(__dirname, '..', 'uploads');
const connectionsBySlug = new Map();
const dashboardListenersBySlug = new Map();
const server = http.createServer(app);
@@ -160,9 +162,52 @@ async function start() {
app.use(express.json());
// Static assets and mirrored uploads are served from the player container.
app.use('/assets', express.static(ASSET_DIR));
app.use('/uploads', express.static(UPLOAD_DIR));
app.get('/api/uploads/config', function (_req, res) {
res.json({
uploadDir: UPLOAD_DIR
});
});
app.put('/api/uploads/:filename', express.raw({ type: '*/*', limit: '100mb' }), async function (req, res, next) {
try {
const filename = path.basename(String(req.params.filename || '').trim());
if (!filename) {
return res.status(400).json({ error: 'Filename is required' });
}
const filePath = path.join(UPLOAD_DIR, filename);
const body = Buffer.isBuffer(req.body) ? req.body : Buffer.from(req.body || '');
await fs.promises.mkdir(UPLOAD_DIR, { recursive: true });
await fs.promises.writeFile(filePath, body);
res.json({ ok: true, filename: filename });
} catch (error) {
next(error);
}
});
app.delete('/api/uploads/:filename', async function (req, res, next) {
try {
const filename = path.basename(String(req.params.filename || '').trim());
if (!filename) {
return res.status(400).json({ error: 'Filename is required' });
}
const filePath = path.join(UPLOAD_DIR, filename);
try {
await fs.promises.unlink(filePath);
} catch (error) {
if (!error || error.code !== 'ENOENT') {
throw error;
}
}
res.json({ ok: true, filename: filename });
} catch (error) {
next(error);
}
});
function getConnectionBucket(slug) {
const key = String(slug || '').trim();
if (!key) {
@@ -337,6 +382,7 @@ async function start() {
res.send('Pulse Signage player service');
});
// Screen playback endpoints render the active playlist for a slug.
app.get('/screen/:slug', function (req, res) {
res.set('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
res.set('Pragma', 'no-cache');
@@ -439,6 +485,7 @@ async function start() {
await common.ensureSchema(pool);
fs.mkdirSync(UPLOAD_DIR, { recursive: true });
// Websocket upgrades split dashboard snapshots from player client sessions.
server.on('upgrade', function (request, socket, head) {
let pathname = '';
try {