mirror of
https://github.com/screentinker/screentinker.git
synced 2026-06-17 03:32:32 -06:00
Agencies can only be designated FULL-SCREEN playlists (no item with zone_id) - a full-screen agency upload can't safely target a zone, so the ambiguous case is excluded rather than solved. Checked at THREE points: - Designation (tokens.js create + PUT /:id/targets) -> 400: reject a zoned target. - Upload (agency.js item-add) -> 409: block if the playlist BECAME zoned after designation. MANDATORY because auto-publish has no draft net - a full-screen playlist designated to an auto-publish token, then zone-assigned, would otherwise auto-publish a full-screen upload into a zoned playlist. The upload check is the only thing that catches it. - Picker (settings.js): zoned playlists greyed/disabled with the reason (GET /playlists now returns a zoned flag); backend reject is the guard if the UI is bypassed. i18n x5. isZonedPlaylist = EXISTS(playlist_items WHERE zone_id IS NOT NULL). Pure restriction - no zone structure, no api_token_target_zones. Bite-test (the exact sequence) GREEN and re-proven to bite: full-screen -> designate to an auto-publish token -> zone-assign the playlist -> agency upload is BLOCKED (409), not auto-published; neutralizing the upload check makes it go red. 149 suite green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
30 lines
1.5 KiB
JavaScript
30 lines
1.5 KiB
JavaScript
'use strict';
|
|
|
|
// #73: the single query behind GET /api/agency/playlists. Returns ONLY this token's
|
|
// designated playlists, in its bound workspace. The WHERE clause IS the confinement and is
|
|
// the thing to bite-test:
|
|
// t.token_id = ? -> this token's targets, never another token's
|
|
// (JOIN api_token_targets) -> only allowlisted playlists, never one outside the allowlist
|
|
// p.workspace_id = ? -> only the bound workspace, never cross-workspace
|
|
// db is passed in (not module-required) so the confinement is unit-testable in isolation.
|
|
function listDesignatedPlaylists(db, tokenId, workspaceId) {
|
|
return db.prepare(`
|
|
SELECT p.id, p.name, p.status
|
|
FROM api_token_targets t
|
|
JOIN playlists p ON p.id = t.playlist_id
|
|
WHERE t.token_id = ? AND p.workspace_id = ?
|
|
ORDER BY p.name
|
|
`).all(tokenId, workspaceId);
|
|
}
|
|
|
|
// #73 full-screen guardrail: a playlist is "zoned" if any item targets a layout zone. Agency
|
|
// uploads are full-screen and can't safely target a zone, so a zoned playlist can't be shared
|
|
// with an agency. Checked at BOTH designation (reject the grant) AND upload (block the add) -
|
|
// the upload check is mandatory because auto-publish has no draft step to catch a playlist
|
|
// that becomes zoned after designation.
|
|
function isZonedPlaylist(db, playlistId) {
|
|
return !!db.prepare('SELECT 1 FROM playlist_items WHERE playlist_id = ? AND zone_id IS NOT NULL LIMIT 1').get(playlistId);
|
|
}
|
|
|
|
module.exports = { listDesignatedPlaylists, isZonedPlaylist };
|