feat(smartsheet): cycle 7 — workbook hub
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).
This commit is contained in:
@@ -22,6 +22,7 @@ import {
|
||||
regenerateAdminPin,
|
||||
type AdminSettings,
|
||||
} from "@/actions/water-log/settings";
|
||||
import SmartsheetHub from "@/components/admin/water-log/SmartsheetHub";
|
||||
import SmartsheetIntegrationCard from "@/components/admin/water-log/SmartsheetIntegrationCard";
|
||||
import TimeTrackingSmartsheetCard from "@/components/admin/time-tracking/TimeTrackingSmartsheetCard";
|
||||
|
||||
@@ -476,8 +477,10 @@ export default function WaterLogSettingsPage() {
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{/* Smartsheet Integration — independent of the Admin Portal toggle above.
|
||||
Self-contained card with its own save / test / activity surface. */}
|
||||
{/* Smartsheet Workbook Hub (Cycle 7). One token, many sheets.
|
||||
The hub card owns the brand-level workbook connection (token +
|
||||
test + enable); the per-feature cards below pick which sheet
|
||||
inside the workbook to write to. */}
|
||||
<div className="mt-12 border-t border-[#d4d9d3] pt-10">
|
||||
<p className="text-[10px] font-mono uppercase tracking-[0.3em] text-[#8a6b3b]">
|
||||
§ 05 — Integrations
|
||||
@@ -489,39 +492,31 @@ export default function WaterLogSettingsPage() {
|
||||
Smartsheet
|
||||
</h2>
|
||||
<p className="mt-2 text-sm text-[#5a5d5a]">
|
||||
Push every new water log entry to a Smartsheet sheet. The
|
||||
brand admin (you) controls the connection, frequency, and
|
||||
column mapping. The token is encrypted at rest.
|
||||
Connect one Smartsheet workbook and pick which sheet each
|
||||
feature writes to. The token is encrypted at rest; per-feature
|
||||
cards below manage only their sheet mapping.
|
||||
</p>
|
||||
<div className="mt-6">
|
||||
<SmartsheetIntegrationCard brandId={TUXEDO_BRAND_ID} />
|
||||
<SmartsheetHub brandId={TUXEDO_BRAND_ID} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Time Tracking → Smartsheet (Cycle 5). Independent of the
|
||||
water-log smartsheet card above; uses its own sheet ID,
|
||||
token, column mapping, and sync queue. Sibling surface in
|
||||
this same hub view. */}
|
||||
<div className="mt-12 border-t border-[#d4d9d3] pt-10">
|
||||
<p className="text-[10px] font-mono uppercase tracking-[0.3em] text-[#8a6b3b]">
|
||||
§ 06 — Time Tracking Integrations
|
||||
</p>
|
||||
<h2
|
||||
className="mt-2 text-2xl font-medium text-[#1a4d2e]"
|
||||
style={{ fontFamily: "var(--font-display, Georgia, serif)" }}
|
||||
>
|
||||
Smartsheet (Time Tracking)
|
||||
</h2>
|
||||
<p className="mt-2 text-sm text-[#5a5d5a]">
|
||||
Push every closed clock-out from the time tracking system to a
|
||||
configured Smartsheet sheet. Same encryption + retry policy as the
|
||||
water-log smartsheet integration. Config-deferred — wire it up
|
||||
whenever the brand is ready with a sheet ID + API token.
|
||||
</p>
|
||||
<div className="mt-6">
|
||||
<TimeTrackingSmartsheetCard brandId={TUXEDO_BRAND_ID} />
|
||||
</div>
|
||||
{/* Water Log sheet mapping — uses the workbook token from the
|
||||
hub card above. Configures which sheet inside the workbook
|
||||
receives water-log entries. */}
|
||||
<div className="mt-8">
|
||||
<SmartsheetIntegrationCard brandId={TUXEDO_BRAND_ID} />
|
||||
</div>
|
||||
|
||||
{/* Time Tracking sheet mapping — also uses the workbook token.
|
||||
Configures which sheet inside the workbook receives
|
||||
clock-out rows. */}
|
||||
<div className="mt-8">
|
||||
<TimeTrackingSmartsheetCard brandId={TUXEDO_BRAND_ID} />
|
||||
</div>
|
||||
|
||||
{/* § 06 removed in Cycle 7 — Time Tracking sheet mapping is now
|
||||
a sibling card under § 05 above (uses the workbook token). */}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user