mirror of
https://github.com/screentinker/screentinker.git
synced 2026-05-15 07:32:23 -06:00
Phase 2: schedules accept playlist_id, scheduler overrides device playlist
Schedule CRUD now includes playlist_id field. List queries join playlist name. Scheduler tracks active overrides per device and reverts to original playlist/layout when no schedule is active. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
f35894d17a
commit
19a08ef5bc
|
|
@ -6,7 +6,7 @@ const { db } = require('../db/database');
|
||||||
// List schedules (filterable)
|
// List schedules (filterable)
|
||||||
router.get('/', (req, res) => {
|
router.get('/', (req, res) => {
|
||||||
const { device_id, start, end } = req.query;
|
const { device_id, start, end } = req.query;
|
||||||
let sql = 'SELECT s.*, c.filename as content_name, w.name as widget_name FROM schedules s LEFT JOIN content c ON s.content_id = c.id LEFT JOIN widgets w ON s.widget_id = w.id WHERE s.user_id = ?';
|
let sql = 'SELECT s.*, c.filename as content_name, w.name as widget_name, p.name as playlist_name FROM schedules s LEFT JOIN content c ON s.content_id = c.id LEFT JOIN widgets w ON s.widget_id = w.id LEFT JOIN playlists p ON s.playlist_id = p.id WHERE s.user_id = ?';
|
||||||
const params = [req.user.id];
|
const params = [req.user.id];
|
||||||
|
|
||||||
if (device_id) { sql += ' AND s.device_id = ?'; params.push(device_id); }
|
if (device_id) { sql += ' AND s.device_id = ?'; params.push(device_id); }
|
||||||
|
|
@ -20,10 +20,11 @@ router.get('/', (req, res) => {
|
||||||
// Get schedules for a device
|
// Get schedules for a device
|
||||||
router.get('/device/:deviceId', (req, res) => {
|
router.get('/device/:deviceId', (req, res) => {
|
||||||
const schedules = db.prepare(`
|
const schedules = db.prepare(`
|
||||||
SELECT s.*, c.filename as content_name, w.name as widget_name
|
SELECT s.*, c.filename as content_name, w.name as widget_name, p.name as playlist_name
|
||||||
FROM schedules s
|
FROM schedules s
|
||||||
LEFT JOIN content c ON s.content_id = c.id
|
LEFT JOIN content c ON s.content_id = c.id
|
||||||
LEFT JOIN widgets w ON s.widget_id = w.id
|
LEFT JOIN widgets w ON s.widget_id = w.id
|
||||||
|
LEFT JOIN playlists p ON s.playlist_id = p.id
|
||||||
WHERE s.device_id = ? AND s.enabled = 1
|
WHERE s.device_id = ? AND s.enabled = 1
|
||||||
ORDER BY s.priority DESC, s.start_time ASC
|
ORDER BY s.priority DESC, s.start_time ASC
|
||||||
`).all(req.params.deviceId);
|
`).all(req.params.deviceId);
|
||||||
|
|
@ -42,10 +43,11 @@ router.get('/week', (req, res) => {
|
||||||
weekEnd.setDate(weekEnd.getDate() + 7);
|
weekEnd.setDate(weekEnd.getDate() + 7);
|
||||||
|
|
||||||
const schedules = db.prepare(`
|
const schedules = db.prepare(`
|
||||||
SELECT s.*, c.filename as content_name, w.name as widget_name
|
SELECT s.*, c.filename as content_name, w.name as widget_name, p.name as playlist_name
|
||||||
FROM schedules s
|
FROM schedules s
|
||||||
LEFT JOIN content c ON s.content_id = c.id
|
LEFT JOIN content c ON s.content_id = c.id
|
||||||
LEFT JOIN widgets w ON s.widget_id = w.id
|
LEFT JOIN widgets w ON s.widget_id = w.id
|
||||||
|
LEFT JOIN playlists p ON s.playlist_id = p.id
|
||||||
WHERE s.device_id = ? AND s.enabled = 1
|
WHERE s.device_id = ? AND s.enabled = 1
|
||||||
ORDER BY s.priority DESC, s.start_time ASC
|
ORDER BY s.priority DESC, s.start_time ASC
|
||||||
`).all(device_id);
|
`).all(device_id);
|
||||||
|
|
@ -61,7 +63,7 @@ router.get('/week', (req, res) => {
|
||||||
|
|
||||||
// Create schedule
|
// Create schedule
|
||||||
router.post('/', (req, res) => {
|
router.post('/', (req, res) => {
|
||||||
const { device_id, zone_id, content_id, widget_id, layout_id, title, start_time, end_time,
|
const { device_id, zone_id, content_id, widget_id, layout_id, playlist_id, title, start_time, end_time,
|
||||||
timezone, recurrence, recurrence_end, priority, color } = req.body;
|
timezone, recurrence, recurrence_end, priority, color } = req.body;
|
||||||
|
|
||||||
if (!device_id || !start_time || !end_time) {
|
if (!device_id || !start_time || !end_time) {
|
||||||
|
|
@ -70,11 +72,11 @@ router.post('/', (req, res) => {
|
||||||
|
|
||||||
const id = uuidv4();
|
const id = uuidv4();
|
||||||
db.prepare(`
|
db.prepare(`
|
||||||
INSERT INTO schedules (id, user_id, device_id, zone_id, content_id, widget_id, layout_id, title,
|
INSERT INTO schedules (id, user_id, device_id, zone_id, content_id, widget_id, layout_id, playlist_id, title,
|
||||||
start_time, end_time, timezone, recurrence, recurrence_end, priority, color)
|
start_time, end_time, timezone, recurrence, recurrence_end, priority, color)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
`).run(id, req.user.id, device_id, zone_id || null, content_id || null, widget_id || null,
|
`).run(id, req.user.id, device_id, zone_id || null, content_id || null, widget_id || null,
|
||||||
layout_id || null, title || '', start_time, end_time, timezone || 'UTC',
|
layout_id || null, playlist_id || null, title || '', start_time, end_time, timezone || 'UTC',
|
||||||
recurrence || null, recurrence_end || null, priority || 0, color || '#3B82F6');
|
recurrence || null, recurrence_end || null, priority || 0, color || '#3B82F6');
|
||||||
|
|
||||||
const schedule = db.prepare('SELECT * FROM schedules WHERE id = ?').get(id);
|
const schedule = db.prepare('SELECT * FROM schedules WHERE id = ?').get(id);
|
||||||
|
|
@ -87,7 +89,7 @@ router.put('/:id', (req, res) => {
|
||||||
if (!schedule) return res.status(404).json({ error: 'Schedule not found' });
|
if (!schedule) return res.status(404).json({ error: 'Schedule not found' });
|
||||||
if (!['admin','superadmin'].includes(req.user.role) && schedule.user_id !== req.user.id) return res.status(403).json({ error: 'Access denied' });
|
if (!['admin','superadmin'].includes(req.user.role) && schedule.user_id !== req.user.id) return res.status(403).json({ error: 'Access denied' });
|
||||||
|
|
||||||
const fields = ['device_id', 'zone_id', 'content_id', 'widget_id', 'layout_id', 'title',
|
const fields = ['device_id', 'zone_id', 'content_id', 'widget_id', 'layout_id', 'playlist_id', 'title',
|
||||||
'start_time', 'end_time', 'timezone', 'recurrence', 'recurrence_end', 'priority', 'enabled', 'color'];
|
'start_time', 'end_time', 'timezone', 'recurrence', 'recurrence_end', 'priority', 'enabled', 'color'];
|
||||||
const updates = [];
|
const updates = [];
|
||||||
const values = [];
|
const values = [];
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,9 @@ function startScheduler(socketIo) {
|
||||||
console.log('Scheduler service started');
|
console.log('Scheduler service started');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Track which devices have a schedule override active so we can revert
|
||||||
|
const activeOverrides = new Map(); // deviceId -> { playlist_id, layout_id }
|
||||||
|
|
||||||
function evaluateSchedules() {
|
function evaluateSchedules() {
|
||||||
const deviceNs = io?.of('/device');
|
const deviceNs = io?.of('/device');
|
||||||
if (!deviceNs) return;
|
if (!deviceNs) return;
|
||||||
|
|
@ -18,27 +21,38 @@ function evaluateSchedules() {
|
||||||
|
|
||||||
for (const device of onlineDevices) {
|
for (const device of onlineDevices) {
|
||||||
const schedules = db.prepare(`
|
const schedules = db.prepare(`
|
||||||
SELECT s.*, c.filename, c.mime_type, c.filepath, c.file_size, c.remote_url,
|
SELECT s.*
|
||||||
c.duration_sec as content_duration
|
|
||||||
FROM schedules s
|
FROM schedules s
|
||||||
LEFT JOIN content c ON s.content_id = c.id
|
|
||||||
WHERE s.device_id = ? AND s.enabled = 1
|
WHERE s.device_id = ? AND s.enabled = 1
|
||||||
ORDER BY s.priority DESC
|
ORDER BY s.priority DESC
|
||||||
`).all(device.id);
|
`).all(device.id);
|
||||||
|
|
||||||
// Find currently active schedule
|
|
||||||
const active = schedules.find(s => isScheduleActiveNow(s, now));
|
const active = schedules.find(s => isScheduleActiveNow(s, now));
|
||||||
|
const override = activeOverrides.get(device.id);
|
||||||
|
let changed = false;
|
||||||
|
|
||||||
if (active && active.content_id) {
|
if (active) {
|
||||||
// Check if this is different from current playback
|
// Apply layout override if schedule has one
|
||||||
const currentLayout = device.layout_id;
|
if (active.layout_id && active.layout_id !== device.layout_id) {
|
||||||
if (active.layout_id && active.layout_id !== currentLayout) {
|
if (!override) activeOverrides.set(device.id, { layout_id: device.layout_id, playlist_id: device.playlist_id });
|
||||||
// Switch layout
|
|
||||||
db.prepare("UPDATE devices SET layout_id = ? WHERE id = ?").run(active.layout_id, device.id);
|
db.prepare("UPDATE devices SET layout_id = ? WHERE id = ?").run(active.layout_id, device.id);
|
||||||
// Push updated playlist
|
changed = true;
|
||||||
pushPlaylistToDevice(device.id, deviceNs);
|
|
||||||
}
|
}
|
||||||
|
// Apply playlist override if schedule has one
|
||||||
|
if (active.playlist_id && active.playlist_id !== device.playlist_id) {
|
||||||
|
if (!override) activeOverrides.set(device.id, { layout_id: device.layout_id, playlist_id: device.playlist_id });
|
||||||
|
db.prepare("UPDATE devices SET playlist_id = ? WHERE id = ?").run(active.playlist_id, device.id);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
} else if (override) {
|
||||||
|
// No active schedule — revert to original playlist/layout
|
||||||
|
db.prepare("UPDATE devices SET playlist_id = ?, layout_id = ? WHERE id = ?")
|
||||||
|
.run(override.playlist_id, override.layout_id, device.id);
|
||||||
|
activeOverrides.delete(device.id);
|
||||||
|
changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (changed) pushPlaylistToDevice(device.id, deviceNs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue