Phase 1: backup-grade hardening (viewer role, audit log, seeding, dry-run import, scheduled backup, ops alerts)#8
Merged
Conversation
New practices landed in a blank dashboard. register now seeds default appointment types, rooms, and a starter services catalog (pure defaults module + seedPractice helper), so a clinic is usable immediately — important for the hosted self-serve onboarding. Seeding is non-fatal (logs, never blocks signup). 3 tests on the default data integrity. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Enables running OpenVPM as a safe parallel backup/secondary: a viewer can see everything in their practice but cannot mutate anything. Enforced globally via a guard in protectedProcedure (blocks type==='mutation' for viewers) so no router needs changing. Adds 'viewer' to the user_role enum, the NextAuth role unions, the sidebar nav roles, and the settings staff role picker (admins can assign it). 3 tRPC-caller tests prove queries pass and mutations get FORBIDDEN. Note: additive enum value — run pnpm db:push on deploy. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The auditLog table existed but was never written. protectedProcedure now records who/what/when/where on each successful mutation (entity + action from the tRPC path, redacted input as changes, entity id, client IP threaded from the request). Fire-and-forget so auditing never blocks or fails a request; secret-ish fields (password, keyHash, token...) are redacted. 8 unit tests on the pure helpers (path parse, redaction, entity-id extraction). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lets a clinic safely validate a migration before it touches data. data.import
{Clients,Patients}Csv now accept dryRun; when set they report what WOULD happen
— total, willInsert, duplicates (by email), and unmatched owners (referential
integrity) — without inserting. Pure planClientImport/planPatientImport with
4 unit tests; parse-level row errors still surface in the report.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Zero-DB guard against the catastrophic multi-tenant bug: asserts every router that touches the database references practiceId (with a small, intentional allowlist). Catches a whole router shipping without a tenant filter. Full row-level isolation (Postgres RLS + live-DB integration test) lands in Phase 4; this guards the query layer now. 30 checks across the routers. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cron reminders and webhook delivery failed silently (console only) — a clinic would never know reminders stopped sending or a webhook went dead. Adds alertOps (posts to OPS_ALERT_WEBHOOK_URL Slack-style if set, always logs, never throws) wired into the reminders cron (job crash + partial failures) and the webhook dispatcher (alerts once per batch on failed deliveries; now also treats non-2xx as failure). 2 tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A daily cron exports each practice's core data (clients, patients, appointments, invoices + items, all practice-scoped) to S3/MinIO as a restorable JSON snapshot, independent of the live DB — so a clinic trusting OpenVPM as a backup has its own recoverable copy. Reusable exportPracticeData helper + pure backupKey (per-practice/date namespacing, 2 tests). Failures alert ops. Wired a 03:00 daily cron in vercel.json. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase 1 of the production plan — backup-grade hardening
Makes OpenVPM trustworthy to run as a parallel backup / secondary in a clinic, and lays groundwork for the hosted edition.
What's in this PR
viewerrole — a global guard inprotectedProcedurelets viewers read everything in their practice but blocks every mutation. Lets a clinic run OpenVPM alongside their primary system as a safe backup.auditLogtable (entity + action from the tRPC path, redacted input, entity id, client IP). Sensitive fields (passwords, tokens, key hashes) are redacted before logging. Fire-and-forget; never blocks a request.registernow seeds default appointment types, rooms, and a starter services catalog so a new practice is usable immediately.data.import{Clients,Patients}CsvacceptdryRun: reports total / will-insert / duplicates / unmatched owners without touching data.practiceIdfilter on all queries), and a test verifies every DB-touching router applies it. Defense-in-depth database-level Row-Level Security is planned as part of the hosted multi-tenant rollout.Verification
pnpm test(177 passing, ~40 new),pnpm type-check,pnpm build(both apps) all green.Deploy notes
pnpm db:push(adds theviewervalue to theuser_roleenum).OPS_ALERT_WEBHOOK_URL. New cron:/api/cron/backup(03:00 daily) — usesCRON_SECRET+ S3 env.Follow-on phases (roadmap)
Internationalization/localization, hosted subscription billing + feature gating, regional + dedicated deployment options, and database-level RLS hardening.
🤖 Generated with Claude Code