Skip to content

Promote synvya-staging to synvya (production)#42

Merged
alejandro-runner merged 72 commits intosynvyafrom
synvya-staging
Apr 29, 2026
Merged

Promote synvya-staging to synvya (production)#42
alejandro-runner merged 72 commits intosynvyafrom
synvya-staging

Conversation

@alejandro-runner
Copy link
Copy Markdown
Member

Summary

Promotes ~70 commits of accumulated staging work to production. The synvya branch is currently 1 commit ahead (docs-only: ALB/WAF architecture notes) and 71 commits behind staging.

Major changes included

  • Team invitations by email — new invite flow, preview endpoint, card-style invite emails with kind-0 team profile, accept-invite polish
  • Email UX overhaul — unified verify / reset / claim email layouts, Synvya-branded card templates, kind-0 preload in claim emails, split reset URL handling, route verify-email past DISABLE_WEB_UI
  • Support-admin enhancements — Synvya-themed user detail view, collapse same-label authorizations, Revoke button + Show revoked toggle, dark auth-group background, soft-delete for team authorizations
  • First-team creation — allow users to create their first team (tenant-scoped membership)
  • Branding — Synvya logo, themed icons on verify-email, Synvya-styled register/reset-password pages, full brand migration
  • CORS / cookies — credentialed /api/invitations/preview, logout CORS fix, staging-environment secure_cookies handling, localhost session cookies, account/server origins
  • OperationalDISABLE_WEB_UI for Synvya deployments, DISABLE_EMAILS only when explicitly set, Docker cache pruning before deploys, port mapping updates
  • Roster/preview API — include member email in TeamUser responses, team_key_pubkey and invited_by_email in invitation preview
  • Code quality — cargo fmt across admin/claim-email handlers, clippy type_complexity fix in get_user_teams

Diverging commit on synvya

46b11c4 Document dedicated Synvya ALBs and WAF layout — docs-only, will merge cleanly.

Test plan

  • Verify CI passes on the merge
  • Smoke-test login / register / verify-email on staging environment before promoting
  • Confirm team invite end-to-end (preview, email delivery, accept flow)
  • Confirm support-admin revoke + soft-delete behavior
  • Verify CORS for credentialed requests from production client origins
  • Confirm DISABLE_WEB_UI still hides web UI on production deployment

alejandro-runner and others added 30 commits April 8, 2026 21:32
Allow authenticated users to create their first team
Fix tenant-scoped team membership test setup
After verifying their email, users were landing on the Keycast dashboard
instead of being redirected back to the originating client app (e.g.
account.synvya.com). This adds an optional redirect_uri parameter to
registration that is stored and returned as redirect_to after
verification, leveraging the existing frontend redirect logic.

Closes #12

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Redirect to client app after email verification
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implements email-based team invitations so restaurant admins can invite
team members by email instead of requiring Nostr public keys. Existing
users are added immediately; new users receive an invitation email with
a token-based accept flow.

New endpoints:
- POST /teams/:id/invite (instant add or send invitation)
- GET /teams/:id/invitations (list invitations)
- DELETE /teams/:id/invitations/:id (revoke)
- GET /invitations/preview?token= (public preview)
- POST /invitations/accept (accept with auth)

Also fixes cloudbuild.yaml migration glob to pick up date-based
migration filenames (20260413... pattern).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sync the spec with what was actually shipped: partial unique index
instead of plain UNIQUE constraint, lowercase role default, separate
CORS routers, and 404 (not 410) for expired tokens.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds isSynvyaManaged detection to the register page and a proper success
screen to the reset-password flow, matching the Synvya-branded login style
already used in verify-email and reset-password forms.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a light Synvya theme variant to /support-admin and surfaces per-user
team memberships with their team-owned restaurant keys and NIP-46
authorizations. The theme activates only on auth.synvya.com /
auth.staging.synvya.com via the existing getLoginUrl() switch, so diVine
deployments are unchanged.

Backend:
- New GET /api/admin/user-teams?pubkey=<hex> (support-admin only,
  tenant-scoped) returning teams → restaurant_keys → authorizations with
  label, bunker pubkey, relays, connected/expires/created timestamps.

Frontend:
- Support-admin page dual-modes via isSynvyaManaged; Synvya variant uses
  a white admin surface with Synvya logo header.
- New "Teams & Restaurants" section (Synvya-only) lazy-loads user-teams
  on expand and groups authorizations under their restaurant key, with
  visual accents for Synvya Server (24/7) vs Synvya Client (interactive)
  labels and a "shared across the team" hint reflecting the Keycast
  boundary: authorizations are team-scoped, not user-scoped.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add Synvya-themed support-admin user detail view
Replace inline 9-tuple sqlx::query_as with a dedicated FromRow struct
(AuthorizationRow) to satisfy clippy::type_complexity under -D warnings.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix clippy type_complexity in get_user_teams
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Apply cargo fmt to admin get_user_teams handler
Adds `revoked_at` + `revoked_reason` columns to the `authorizations`
table, mirrors the existing `oauth_authorizations` pattern. The signer
daemon now skips revoked rows on lazy load, the repository filters them
by default, and a new support-admin endpoint marks rows inactive without
hard-deleting and losing forensic history.

- Migration adds columns + partial index on active rows.
- AuthorizationRepository: filter revoked in find_by_stored_key/all_ids;
  add find_by_stored_key_with_revoked and revoke(tenant_id, id, reason).
- Signer daemon: load_single_authorization skips revoked team rows
  (matches OAuth path).
- New endpoint: POST /api/admin/authorizations/:id/revoke (support admin,
  tenant-scoped). Notifies signer via existing AuthorizationCommand::Remove
  channel so the in-memory handler is tombstoned immediately.

Closes #18

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
feat: soft-delete for team authorizations (#18)
Groups authorizations by label in the Synvya support-admin user detail
view so a restaurant key with many rows (e.g. several repeated logins
producing `Synvya Client (staging)` authorizations) renders as one card
per label with an "N older" disclosure instead of a flat scroll.

- Group by label (empty labels bucket as `(no label)`).
- Newest-first inside each group; groups sorted by most-recent row.
- Show newest inline; click "N older" to expand the rest.
- Color accent (green for Synvya Server, blue for Synvya Client) moves
  to the group header.
- Row count badge on the group header.
- Only active in Synvya-gated view (isSynvyaManaged); diVine deployments
  keep the existing flat rendering.

Per #19 out-of-scope: no backend-shape change, no dedupe-on-create.
Revoked-row handling deferred to a follow-up now that #18 has landed.

Closes #19

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
feat(support-admin): collapse same-label authorizations (#19)
Closes the UI gap left by #18: support admins can now revoke an
authorization directly from /support-admin (Synvya variant) via a
per-card Revoke button that prompts for an optional reason and calls
POST /api/admin/authorizations/:id/revoke.

- Backend: expose `revoked_at` and `revoked_reason` on AdminAuthorization
  (and the underlying sqlx FromRow struct + SELECT in get_user_teams).
- Frontend: hide revoked authorizations by default; per-restaurant-key
  "Show revoked (N)" toggle surfaces them with tombstone styling
  (opacity, strikethrough label, revoked pill, reason tooltip).
- Revoke button appears on active authorizations only; during the
  request the button shows "Revoking…" and is disabled. On success we
  reload the user's teams so the UI reflects the new revoked state.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
alejandro-runner and others added 29 commits April 17, 2026 15:10
fix(cors): allow credentialed requests on /api/invitations/preview
Lets the invitation landing page render the team's kind-0 profile
(display name + avatar) by pubkey and show the inviter's email instead
of a truncated pubkey. Closes #29.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…mail

feat(invitations): add team_key_pubkey and invited_by_email to preview
Synvya deployments run keycast with DISABLE_WEB_UI=true and redirect
unknown routes to www.synvya.com. That swallowed the /verify-email
SvelteKit route so invited users landed on the marketing site instead
of verifying their email.

Allowlist /verify-email (plus SvelteKit /_app static assets) in the
disabled-UI branch, and add PASSWORD_RESET_BASE_URL so the reset link
can point at account.{staging.,}synvya.com where Synvya hosts its own
reset form that POSTs to /api/auth/reset-password.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fix(email): route /verify-email past DISABLE_WEB_UI; split password reset URL
change diVine to Synvya
svelte tooling introduced formatting changes to .svelte pages
keycast/ is running locally now.

────────────
     Summary [  25.980s] 368 tests run: 368 passed, 11 skipped
Rework the team-invitation email so it mirrors the accept-invite page:
centred Synvya wordmark, "Team Invitation" header, white card with the
team's avatar and display name, inviter + recipient + expiry, and a
prominent Accept Invitation button.

- New api/src/nostr_profile.rs fetches kind-0 metadata (display_name,
  picture) from BUNKER_RELAYS with a 3s timeout; returns None on any
  failure so the email still sends with the DB handle as fallback.
- Prefer inviter email over the pubkey-prefix fallback when the admin
  has no display_name/username, so recipients see "staging@…" instead
  of "f7ebfcf1…".
- Team display name + avatar in the email now match what the accept
  page shows, because both derive from the same kind-0 record.
- HTML-escape all interpolated values and restrict avatar URLs to
  http/https.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
feat(invitations): card-style invite email with kind-0 team profile
Fixes formatting in teams.rs, email_service.rs, and nostr_profile.rs
flagged by the `cargo fmt --all -- --check` CI step on synvya-staging.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Align the three remaining transactional emails (verification, password
reset, account claim) with the visual language of the new team-invite
email: centred Synvya wordmark header, centred heading + intro, rounded
pill button, copy-link fallback, muted footer note.

Done via a small shared helper `basic_email_html` that takes heading,
intro, CTA label, CTA URL, and footer note. Keeps the emails minimal —
no card, no stylised subject — since these flows have no entity to
present and overly stylised reset/verify emails can read as phishing.

All interpolated values are HTML-escaped.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Claim emails now render the same card layout as team invites when the
preloaded account has kind-0 metadata: avatar, display name (restaurant
name), and "Claim your login for {email}". Falls back to the plain
single-CTA layout when kind-0 is unavailable.

- Extend send_claim_email signature with account_display_name and
  account_picture Options; threaded through all three EmailSender impls
  and the EmailService facade.
- claim_email_html: branches to card layout when either field is set;
  otherwise delegates to basic_email_html. HTML-escapes interpolated
  values; avatar URL restricted to http/https via safe_http_url.
- admin.rs batch claim handler: best-effort kind-0 fetch from
  BUNKER_RELAYS for each user_pubkey (3s timeout, handled entirely in
  fetch_profile_metadata) before sending. Silent fallback on failure so
  batch throughput degrades gracefully.

Password-reset and verification emails intentionally left on the plain
layout — no entity to present, and over-stylised reset mail reads as
phishing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
feat(email): unify verify / reset / claim email layouts
Collapse a two-line statement onto one line to satisfy rustfmt's width
heuristics — the preceding line ran one char longer than cargo fmt's
breakpoint, leaving the two lines inconsistent.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
chore(fmt): apply cargo fmt to admin.rs claim-email call site
Murali dev diVine-> synvya; enable local host build and test
bring in latest feature changes from staging
Tests pass
  Summary [  26.036s] 373 tests run: 373 passed, 11 skipped
Test: api/tests/common/mod.rs#L46
called `Iterator::last` on a `DoubleEndedIterator`; this will needlessly iterate the entire iterator
@alejandro-runner alejandro-runner merged commit a1069cf into synvya Apr 29, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants