Replaces two independent Smartsheet configs (water log + time tracking)
with one brand-level workbook connection that owns the encrypted API
token. Per-feature configs keep their sheet_id + column_mapping +
frequency + sync_enabled.
Schema (migration 0096):
- NEW smartsheet_workspace: one row per brand. Holds encrypted_api_token
+ token_iv + token_auth_tag (AES-256-GCM, same key), default_sheet_id,
connection_enabled (master switch), last_test_*, audit columns. RLS
brand-scoped.
- Backfill: water_smartsheet_config rows copy into workspace first;
time_tracking_smartsheet_config fills in only brands with no workspace
row yet. Idempotent (NOT EXISTS + ON CONFLICT DO NOTHING).
- DROP encrypted_api_token + token_iv + token_auth_tag from both feature
configs (destructive — bytes are copied to workspace first).
- Drop unused bytea customType from both schemas (no longer needed).
Actions:
- NEW src/actions/smartsheet/workspace.ts: getSmartsheetWorkspace,
saveSmartsheetWorkspace, testSmartsheetWorkspaceConnection,
disableSmartsheetWorkspace. Permission: can_manage_settings +
platform_admin.
- NEW src/services/workspace-token.ts (server-only): resolveWorkspaceToken
helper. Lives outside the 'use server' file so the plaintext token
is not exposed as an RPC surface — only callable from server-side
sync engines.
- MODIFIED water-log + time-tracking smartsheet actions: refactored to
read token via resolveWorkspaceToken; save* no longer accepts a
token; test* falls through to the workspace token if no token given.
- MODIFIED both sync services: load token from resolveWorkspaceToken
instead of decrypting the per-feature config. 'connection_disabled'
now skips cleanly (no retry, no failed log row) — pausing the
workspace at the hub level no longer floods Recent Activity with
'disabled' failures.
UI:
- NEW src/components/admin/water-log/SmartsheetHub.tsx: top-level hub
card. Owns the token + Test Connection + connection enabled toggle +
default sheet ID + last-verified badge. Permission gated.
- MODIFIED SmartsheetIntegrationCard.tsx (water): rewritten without
token UI. Reads masked token from workspace. Sheet ID + column
mapping + frequency + enabled + backfill + recent activity.
- MODIFIED TimeTrackingSmartsheetCard.tsx: same treatment.
- MODIFIED /admin/water-log/settings/page: collapses §05 + §06 into a
single Smartsheet section with the hub on top + both feature cards
as siblings underneath.
Tuxedo-only throughout (TUXEDO_BRAND_ID hardcoded in the settings
page; matches existing single-tenant /water convention).
Security fixes from pr-reviewer:
- resolveWorkspaceToken extracted from 'use server' file to a
server-only service module — was reachable as an RPC, would have
returned the plaintext token to any authenticated admin.
- getSmartsheetWorkspace now actually checks the auth result instead
of calling requireManageSettings() and discarding the return value.
Diff: -1742 / +597 lines (net -1100 from the simpler per-feature card).