Skip to content

feat: admin dashboard — search, analytics, users, templates#6

Merged
mqmalagris merged 5 commits into
mainfrom
dev
May 1, 2026
Merged

feat: admin dashboard — search, analytics, users, templates#6
mqmalagris merged 5 commits into
mainfrom
dev

Conversation

@mqmalagris
Copy link
Copy Markdown
Owner

@mqmalagris mqmalagris commented May 1, 2026

Summary

Phase 4 (Growth & Admin) work, all behind the existing admin role gate.

  • Inbox message search — client-side filter on the admin inbox view by from, subject, or preview substring.
  • Stats strip — new `GET /admin/stats` returns total users, premium users, active inboxes, and message counts (24h / 7d) via paginated DynamoDB Scan with SelectCount; AdminView shows a five-tile strip at the top so the operator sees platform health at a glance.
  • User management (read-only) — `GET /admin/users` lists every account (id, email, tier, role, has-webhook, created_at). AdminView grew an INBOXES / USERS tab switcher; the USERS tab renders a sortable table with client-side search. API key and webhook secret are deliberately excluded from the response.
  • Compose email templates — new `template` package stores per-admin presets under PK `template:`; `GET/POST/DELETE /admin/templates` (max 25). The compose form now has an "Insert template" dropdown that fills subject + body, plus a "Save template" input that captures the current draft. Saved templates list inline with delete buttons.

Mutations on user records (promote/demote, manual tier change) are intentionally absent for v1 — they need confirmation UX and audit logging before shipping.

Scan-based aggregation is fine at current volumes; if the table grows we will move to per-counter sentinel items or CloudWatch metrics.

Test plan

  • `pnpm build` and `go test ./...` pass
  • `terraform apply` in dev creates the new admin routes
  • As admin, visit `/admin` → stats strip populates with non-zero numbers
  • Search box on the inbox view filters messages live
  • Switch to USERS tab → table loads, search filters by id/email/tier/role
  • Compose: type a draft → "SAVE TEMPLATE" with a name → reload → "Insert template" dropdown shows it → selecting fills subject + body
  • Delete template → row disappears
  • Hit POST /admin/templates as a non-admin user → 401

- Search box on the admin inbox view filters the message list client-side
  by from/subject/preview substrings.
- New stats package exposes user, premium, active inbox, and message
  (24h/7d) counts via DynamoDB scans with paginated SelectCount.
- GET /admin/stats returns the snapshot; AdminView renders a five-tile
  strip at the top so the operator sees platform health at a glance.

Scan-based aggregation is fine at current volumes; if the table grows we
will move to per-counter sentinel items or CloudWatch metrics.
Adds GET /admin/users returning every user with id, email, tier, role,
webhook flag, and created_at. AdminView grew an INBOXES / USERS tab
switcher under the stats strip; the USERS tab shows a sortable table
with client-side search by id/email/tier/role.

Sensitive fields (api key, webhook secret) are deliberately omitted
from the admin view. Mutations (promote/demote, manual tier change)
left out for v1 — they need confirmation UX and audit logging before
shipping.
New template package persists per-admin compose presets in DynamoDB
under PK __template__:<userID>. Admin endpoints GET/POST/DELETE
/admin/templates list, create (max 25 per user), and remove templates.

Compose form gets a 'Insert template' dropdown that fills subject and
body, and a 'Save template' input next to SEND that captures the
current draft under a name. Saved templates are listed below the form
with inline delete buttons.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 1, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
ephemask-web Ready Ready Preview, Comment May 1, 2026 2:37pm

Stamp read_at on the first GetMessage call via a conditional UpdateItem
(skips write if already set). MessageSummary now carries read_at so the
list renders unread rows in bold with a brighter dot and a subtle
border highlight; opening a message optimistically flips the row in
the client before the next poll lands.
Owners (premium) can grant another account read access to one of their
inboxes by entering the recipient's account email. The recipient
authenticates with their own api key when fetching the inbox; backend
authenticateInbox now allows owner OR shared_with match.

New endpoints (premium-gated for add):
  GET    /inbox/{addr}/shares
  POST   /inbox/{addr}/shares          { email }
  DELETE /inbox/{addr}/shares/{userId}

Shares are persisted as a string list on the inbox sentinel item; the
recipient surfaces them via direct URL for now (a "Shared with you"
listing for the recipient is left for v2 — no reverse index yet).

Account page grew an inline Share section per active inbox with email
input, error states for unknown email or self-share, and an inline
list with per-row remove.
@mqmalagris mqmalagris merged commit ae81078 into main May 1, 2026
8 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.

1 participant