mirror of
https://github.com/screentinker/screentinker.git
synced 2026-05-15 07:32:23 -06:00
Unify publish behavior: all edits go to draft, require explicit publish
Remove autoPublish from assignments.js and device-groups.js. All item mutations (add, update, delete, reorder, copy) now call markDraft regardless of which UI the edit comes from. Users must explicitly click Publish to push changes to devices. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
436a3be7f6
commit
f30d8b82cd
|
|
@ -3,37 +3,9 @@ const router = express.Router();
|
||||||
const { v4: uuidv4 } = require('uuid');
|
const { v4: uuidv4 } = require('uuid');
|
||||||
const { db } = require('../db/database');
|
const { db } = require('../db/database');
|
||||||
|
|
||||||
// Auto-publish: snapshot current items and push to devices.
|
// Mark playlist as draft (called after any item mutation)
|
||||||
// Device-detail edits always go live immediately.
|
function markDraft(playlistId) {
|
||||||
// If deviceId is provided, pushes to that device only; otherwise pushes to all devices using this playlist.
|
db.prepare("UPDATE playlists SET status = 'draft', updated_at = strftime('%s','now') WHERE id = ?").run(playlistId);
|
||||||
function autoPublish(playlistId, req, deviceId) {
|
|
||||||
const items = db.prepare(`
|
|
||||||
SELECT pi.content_id, pi.widget_id, pi.sort_order, pi.duration_sec,
|
|
||||||
COALESCE(c.filename, w.name) as filename, c.mime_type, c.filepath, c.file_size,
|
|
||||||
c.duration_sec as content_duration, c.remote_url,
|
|
||||||
w.name as widget_name, w.widget_type, w.config as widget_config
|
|
||||||
FROM playlist_items pi
|
|
||||||
LEFT JOIN content c ON pi.content_id = c.id
|
|
||||||
LEFT JOIN widgets w ON pi.widget_id = w.id
|
|
||||||
WHERE pi.playlist_id = ?
|
|
||||||
ORDER BY pi.sort_order ASC
|
|
||||||
`).all(playlistId);
|
|
||||||
db.prepare("UPDATE playlists SET status = 'published', published_snapshot = ?, updated_at = strftime('%s','now') WHERE id = ?")
|
|
||||||
.run(JSON.stringify(items), playlistId);
|
|
||||||
try {
|
|
||||||
const io = req?.app?.get('io');
|
|
||||||
if (!io) return;
|
|
||||||
const { buildPlaylistPayload } = require('../ws/deviceSocket');
|
|
||||||
if (!buildPlaylistPayload) return;
|
|
||||||
if (deviceId) {
|
|
||||||
io.of('/device').to(deviceId).emit('device:playlist-update', buildPlaylistPayload(deviceId));
|
|
||||||
} else {
|
|
||||||
const devices = db.prepare('SELECT id FROM devices WHERE playlist_id = ?').all(playlistId);
|
|
||||||
for (const d of devices) {
|
|
||||||
io.of('/device').to(d.id).emit('device:playlist-update', buildPlaylistPayload(d.id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) { /* silent */ }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check device ownership for device-scoped routes
|
// Check device ownership for device-scoped routes
|
||||||
|
|
@ -117,7 +89,7 @@ router.post('/device/:deviceId', (req, res) => {
|
||||||
VALUES (?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?)
|
||||||
`).run(playlistId, content_id || null, widget_id || null, order, duration_sec);
|
`).run(playlistId, content_id || null, widget_id || null, order, duration_sec);
|
||||||
|
|
||||||
autoPublish(playlistId, req, req.params.deviceId);
|
markDraft(playlistId);
|
||||||
|
|
||||||
const item = db.prepare(`${ITEM_SELECT} WHERE pi.id = ?`).get(result.lastInsertRowid);
|
const item = db.prepare(`${ITEM_SELECT} WHERE pi.id = ?`).get(result.lastInsertRowid);
|
||||||
res.status(201).json(item);
|
res.status(201).json(item);
|
||||||
|
|
@ -145,7 +117,7 @@ router.put('/:id', (req, res) => {
|
||||||
updates.push("updated_at = strftime('%s','now')");
|
updates.push("updated_at = strftime('%s','now')");
|
||||||
values.push(req.params.id);
|
values.push(req.params.id);
|
||||||
db.prepare(`UPDATE playlist_items SET ${updates.join(', ')} WHERE id = ?`).run(...values);
|
db.prepare(`UPDATE playlist_items SET ${updates.join(', ')} WHERE id = ?`).run(...values);
|
||||||
autoPublish(item.playlist_id, req, null);
|
markDraft(item.playlist_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
const updated = db.prepare(`${ITEM_SELECT} WHERE pi.id = ?`).get(req.params.id);
|
const updated = db.prepare(`${ITEM_SELECT} WHERE pi.id = ?`).get(req.params.id);
|
||||||
|
|
@ -158,7 +130,7 @@ router.delete('/:id', (req, res) => {
|
||||||
if (!item) return res.status(404).json({ error: 'Item not found' });
|
if (!item) return res.status(404).json({ error: 'Item not found' });
|
||||||
|
|
||||||
db.prepare('DELETE FROM playlist_items WHERE id = ?').run(req.params.id);
|
db.prepare('DELETE FROM playlist_items WHERE id = ?').run(req.params.id);
|
||||||
autoPublish(item.playlist_id, req, null);
|
markDraft(item.playlist_id);
|
||||||
|
|
||||||
res.json({ success: true, content_id: item.content_id });
|
res.json({ success: true, content_id: item.content_id });
|
||||||
});
|
});
|
||||||
|
|
@ -180,7 +152,7 @@ router.post('/device/:deviceId/reorder', (req, res) => {
|
||||||
});
|
});
|
||||||
transaction();
|
transaction();
|
||||||
|
|
||||||
autoPublish(device.playlist_id, req, req.params.deviceId);
|
markDraft(device.playlist_id);
|
||||||
|
|
||||||
const items = db.prepare(`${ITEM_SELECT} WHERE pi.playlist_id = ? ORDER BY pi.sort_order ASC`)
|
const items = db.prepare(`${ITEM_SELECT} WHERE pi.playlist_id = ? ORDER BY pi.sort_order ASC`)
|
||||||
.all(device.playlist_id);
|
.all(device.playlist_id);
|
||||||
|
|
@ -216,7 +188,7 @@ router.post('/device/:deviceId/copy-to/:targetDeviceId', (req, res) => {
|
||||||
});
|
});
|
||||||
transaction();
|
transaction();
|
||||||
|
|
||||||
autoPublish(targetPlaylistId, req, req.params.targetDeviceId);
|
markDraft(targetPlaylistId);
|
||||||
res.json({ success: true, copied: sourceItems.length });
|
res.json({ success: true, copied: sourceItems.length });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -93,30 +93,9 @@ function ensureDevicePlaylist(deviceId, userId) {
|
||||||
return playlistId;
|
return playlistId;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-publish: snapshot current items and push to device.
|
// Mark playlist as draft (called after any item mutation)
|
||||||
// Group assign-content is the bulk equivalent of device-detail — always goes live immediately.
|
function markDraft(playlistId) {
|
||||||
function autoPublish(playlistId, req, deviceId) {
|
db.prepare("UPDATE playlists SET status = 'draft', updated_at = strftime('%s','now') WHERE id = ?").run(playlistId);
|
||||||
const items = db.prepare(`
|
|
||||||
SELECT pi.content_id, pi.widget_id, pi.sort_order, pi.duration_sec,
|
|
||||||
COALESCE(c.filename, w.name) as filename, c.mime_type, c.filepath, c.file_size,
|
|
||||||
c.duration_sec as content_duration, c.remote_url,
|
|
||||||
w.name as widget_name, w.widget_type, w.config as widget_config
|
|
||||||
FROM playlist_items pi
|
|
||||||
LEFT JOIN content c ON pi.content_id = c.id
|
|
||||||
LEFT JOIN widgets w ON pi.widget_id = w.id
|
|
||||||
WHERE pi.playlist_id = ?
|
|
||||||
ORDER BY pi.sort_order ASC
|
|
||||||
`).all(playlistId);
|
|
||||||
db.prepare("UPDATE playlists SET status = 'published', published_snapshot = ?, updated_at = strftime('%s','now') WHERE id = ?")
|
|
||||||
.run(JSON.stringify(items), playlistId);
|
|
||||||
try {
|
|
||||||
const io = req?.app?.get('io');
|
|
||||||
if (!io) return;
|
|
||||||
const { buildPlaylistPayload } = require('../ws/deviceSocket');
|
|
||||||
if (buildPlaylistPayload && deviceId) {
|
|
||||||
io.of('/device').to(deviceId).emit('device:playlist-update', buildPlaylistPayload(deviceId));
|
|
||||||
}
|
|
||||||
} catch (e) { /* silent */ }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push playlist update to a device (used by assign-playlist which doesn't modify items)
|
// Push playlist update to a device (used by assign-playlist which doesn't modify items)
|
||||||
|
|
@ -147,7 +126,7 @@ router.post('/:id/assign-content', requireGroupOwnership, (req, res) => {
|
||||||
const max = db.prepare('SELECT COALESCE(MAX(sort_order),0)+1 as next FROM playlist_items WHERE playlist_id = ?').get(playlistId);
|
const max = db.prepare('SELECT COALESCE(MAX(sort_order),0)+1 as next FROM playlist_items WHERE playlist_id = ?').get(playlistId);
|
||||||
db.prepare('INSERT INTO playlist_items (playlist_id, content_id, sort_order, duration_sec) VALUES (?, ?, ?, ?)')
|
db.prepare('INSERT INTO playlist_items (playlist_id, content_id, sort_order, duration_sec) VALUES (?, ?, ?, ?)')
|
||||||
.run(playlistId, content_id, max.next, duration_sec || 10);
|
.run(playlistId, content_id, max.next, duration_sec || 10);
|
||||||
autoPublish(playlistId, req, m.device_id);
|
markDraft(playlistId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
transaction();
|
transaction();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue