screentinker/server
screentinker 2f3dd80881
feat(agency): per-token upload folder — auto-created, subtree-confined (#158) (#171)
Agency-portal uploads previously all landed at the workspace library root, unsorted.
Instead of the issue's whole-workspace folder dropdown (which would leak every folder
name to an external party), bind ONE folder per agency token — admin-controlled and
agency-invisible — and scope the portal picker strictly to that folder's own subtree
(Hybrid-C). Fully backwards-compatible: no bound folder -> root, exactly as before.

Model / multi-workspace: an agency token is bound to ONE workspace at issuance, so the
token key IS that workspace's private link and the bound folder lives in that workspace.
An admin with N workspaces mints one token per workspace (each with its own auto-folder).
No workspace-switcher in the portal — the token is the tenant boundary.

Backend:
- api_tokens.upload_folder_id (additive; ON DELETE SET NULL -> deleting the folder falls
  back to root).
- lib/agency-targets.folderSubtree(): recursive-CTE helper = the SINGLE confinement source
  shared by GET /api/agency/folders AND the POST /api/agency/content target check, so the
  set the agency can SEE and the set it may WRITE to can never drift. Workspace-guarded at
  the anchor row; descendants inherit the workspace (folders.js forbids cross-ws parents).
- routes/agency.js: GET /folders (bound subtree only); POST /content defaults to the bound
  folder and 403s any folder_id outside the subtree.
- routes/tokens.js: create auto-creates "Agency — <name>" (or binds a picked folder,
  validated same-workspace, respecting the 100-folder cap) inside the token tx; new
  PUT /:id/upload-folder to rebind; listing surfaces the bound folder name.
- middleware/apiToken.js + lib/content-ingest.js: upload_folder_id onto req.apiToken; ingest
  writes folder_id.

Frontend:
- Agency portal: folder <select> shown only when a real subfolder choice exists (identifies
  the "Main folder" root client-side without learning the token's folder id).
- Settings: folder pick at token creation, bound-folder display, rebind modal.
- i18n: 7 new apitoken.* keys across all 5 locales.

Tests (429/429):
- test/agency-folder.test.js: 5 folderSubtree confinement bites (subtree in, siblings out,
  workspace guard, null -> root).
- test/agency.test.js (+1 e2e): auto-create, default-to-bound, in-subtree pick lands there,
  sibling -> 403, admin-pick, unknown-pick -> 400, rebind-to-root.

Closes #158.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 21:23:25 -05:00
..
config PiP overlay MVP: push image/web overlays to a device or group (#109) (#127) 2026-06-18 14:54:44 -05:00
db feat(agency): per-token upload folder — auto-created, subtree-confined (#158) (#171) 2026-07-12 21:23:25 -05:00
lib feat(agency): per-token upload folder — auto-created, subtree-confined (#158) (#171) 2026-07-12 21:23:25 -05:00
middleware feat(agency): per-token upload folder — auto-created, subtree-confined (#158) (#171) 2026-07-12 21:23:25 -05:00
player feat(device-owner): tier foundation + QR provisioning + content-expiry & device enhancements (#168) 2026-07-12 19:41:07 -05:00
routes feat(agency): per-token upload folder — auto-created, subtree-confined (#158) (#171) 2026-07-12 21:23:25 -05:00
scripts feat(scheduling): per-item schedule blocks (#74 dayparting, #75 auto-expire) 2026-06-11 15:46:41 -05:00
services feat(device-owner): tier foundation + QR provisioning + content-expiry & device enhancements (#168) 2026-07-12 19:41:07 -05:00
test feat(agency): per-token upload folder — auto-created, subtree-confined (#158) (#171) 2026-07-12 21:23:25 -05:00
ws feat(system-control): Tier 0/1 controls with no device-owner dependency (#160) (#169) 2026-07-12 20:48:47 -05:00
.gitignore feat(email): Microsoft Graph send + alert spam protection + preferences UI 2026-05-12 18:16:40 -05:00
config.js feat(dashboard): version indicator + GHCR update check (#165) 2026-07-10 22:40:06 -05:00
package-lock.json feat(device-owner): tier foundation + QR provisioning + content-expiry & device enhancements (#168) 2026-07-12 19:41:07 -05:00
package.json feat(device-owner): tier foundation + QR provisioning + content-expiry & device enhancements (#168) 2026-07-12 19:41:07 -05:00
server.js feat(device-owner): tier foundation + QR provisioning + content-expiry & device enhancements (#168) 2026-07-12 19:41:07 -05:00
version.js chore(version): single-source VERSION, env-configurable data paths, bump tooling 2026-06-10 12:56:03 -05:00