mirror of
https://github.com/screentinker/screentinker.git
synced 2026-06-17 03:32:32 -06:00
Issuance (on the proven seam): - tokens.js create + PUT /:id/targets accept per-playlist zone grants (target_zones), inserted into api_token_target_zones inside the same transaction as the playlist grants (FK requires the parent, so order matters and is correct). - Issuance validation (the mirror of runtime confinement): grantableZoneIds() - can grant ONLY a zone the playlist's layout actually feeds; can't grant one it doesn't have or one from another playlist's layout. Bite-tested. PUT re-designate stays atomic: delete parent rows -> zone grants cascade out (no manual child delete). - settings.js: checking a designated playlist reveals its grantable zones (GET /api/playlists/:id/zones, JWT); leave unchecked = whole-playlist. i18n across all 5 locales. Card: - GET /api/agency/playlists/:playlistId/layout (rides router.param - confined; a non- designated playlist -> 403, asserted). "Your zone" = the GRANTED zones. Retired the token-wide /layouts (the per-playlist card replaces the disconnected lump). - Portal card reacts to the playlist selector: pick a playlist -> its layout renders, the granted zone highlighted with px size, siblings as context. Full suite + agency bite-suite green (154). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
58 lines
2.9 KiB
JavaScript
58 lines
2.9 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: resolve which zone an agency item-add lands in, enforcing the zone grants. The grant
|
|
// is the boundary; a body-supplied zone can pick WITHIN it but never escape it.
|
|
// - No zone grants for (token, playlist) -> whole-playlist/full-screen (zone_id NULL); a
|
|
// body zone_id is ignored (placement isn't agency-driven when nothing's granted).
|
|
// - Zone grants exist -> the item MUST land in a GRANTED zone:
|
|
// requested zone that IS granted -> use it (agency picks among its grants);
|
|
// requested zone NOT granted -> { ok:false, reason:'forbidden' } (403);
|
|
// no request, exactly one grant -> auto-place into it;
|
|
// no request, multiple grants -> { ok:false, reason:'ambiguous' } (must pick).
|
|
function resolveGrantedZone(db, tokenId, playlistId, requestedZoneId) {
|
|
const grants = db.prepare('SELECT zone_id FROM api_token_target_zones WHERE token_id = ? AND playlist_id = ?')
|
|
.all(tokenId, playlistId).map(r => r.zone_id);
|
|
if (!grants.length) return { ok: true, zoneId: null };
|
|
if (requestedZoneId) {
|
|
return grants.includes(requestedZoneId)
|
|
? { ok: true, zoneId: requestedZoneId }
|
|
: { ok: false, reason: 'forbidden' };
|
|
}
|
|
if (grants.length === 1) return { ok: true, zoneId: grants[0] };
|
|
return { ok: false, reason: 'ambiguous' };
|
|
}
|
|
|
|
// #73 issuance-side mirror of the runtime confinement: the set of zone_ids an admin may
|
|
// grant for a playlist = all zones of the layout(s) that playlist feeds (its items'
|
|
// zones -> their layouts -> all zones of those layouts). Token-less (used at create time,
|
|
// before the token exists). A zone the playlist's layout doesn't have -> not in this set ->
|
|
// rejected at issuance, the same boundary resolveGrantedZone enforces at runtime.
|
|
function grantableZoneIds(db, playlistId) {
|
|
return new Set(db.prepare(`
|
|
SELECT DISTINCT lz_all.id
|
|
FROM playlist_items pi
|
|
JOIN layout_zones lz ON lz.id = pi.zone_id
|
|
JOIN layout_zones lz_all ON lz_all.layout_id = lz.layout_id
|
|
WHERE pi.playlist_id = ?
|
|
`).all(playlistId).map(r => r.id));
|
|
}
|
|
|
|
module.exports = { listDesignatedPlaylists, resolveGrantedZone, grantableZoneIds };
|