The default http://localhost:8080 silently failed for clinics that joined
the network without setting RELAY_URL — inside Docker localhost is the
container itself, so the hub connection never reached the relay (endless
"relay unreachable" retries) and pairing QRs encoded an unreachable
localhost. Default to https://network.temetro.com so "Join Temetro Network"
works out of the box; self-hosters running their own relay still override
it. Also (re)ensure the hub is connected before pre-registering a pairing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The relay is now multi-clinic. Each clinic authenticates to the /hub
namespace by signing a challenge with its own Ed25519 clinic signing key
(a per-clinic identity, not a shared RELAY_TOKEN), and the relay routes
every device response back to only the clinic that originated the request
(keyed by requestId) — so clinics never see each other's traffic.
Backend:
- clinic_signing_keys.network_enabled + GET/PUT /api/signing/network
(owner/admin) to join/leave the network.
- relay-client keeps one authenticated hub connection per network-enabled
org (connectOrg/disconnectOrg, hubs map keyed by orgId); emitToWallet/
sendToWallet take orgId; offline flush is org-scoped.
- Wallet import/push return 409 until a clinic joins.
- RELAY_TOKEN is now optional/legacy (open relay needs no shared secret).
Frontend:
- "Join Temetro Network" toggle in Settings → Signing, localized in all
five languages (en, fr, de, so, ar).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Devices no longer connect to the backend directly. The /wallet Socket.io
namespace is removed from realtime.ts; a new services/relay-client.ts connects
to the standalone Temetro Network relay's /hub namespace (RELAY_TOKEN-auth),
emitToWallet delegates to its sendToWallet, and device responses + wallet:online
replay are handled there via the same wallet-share/wallet-updates services.
- Add RELAY_URL + RELAY_TOKEN env (env.ts, .env.example, docker-compose.yml);
the wallet-import QR (resolveRelayUrl) now points at RELAY_URL.
- Add socket.io-client dependency.
- Document the Temetro Network folder/service in root + backend CLAUDE.md.
- Bump root/backend/frontend to 0.7.0; CHANGELOG entry.
The relay service itself lives in github.com/temetro/temetro-network.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mirror the existing POSTGRES_PORT override for the backend, frontend and adminer
host ports (BACKEND_PORT / FRONTEND_PORT / ADMINER_PORT) so a port clash on
`docker compose up -d` can be fixed via .env without editing the compose file.
Documented in .env.example and the README.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The "Import from a patient app → QR" code baked the web app's own
API_BASE_URL (typically http://localhost:4000) into the pairing URI, so a
real phone could never reach the relay and "Scan to connect" silently
stalled. Use the backend's server-resolved, device-reachable relay URL
instead (PUBLIC_RELAY_URL, else the request host), surface it on the
WalletPairing type, and document PUBLIC_RELAY_URL in .env.example.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a real file-storage layer to the backend and wire upload UI into the
frontend.
backend:
- new `attachments` table (org-scoped, links to a patient file number and
optionally a lab result) + Drizzle migration
- `/api/attachments` route: upload (multer → disk under UPLOAD_DIR),
list, stream/download, delete; gated by patient:write OR lab:write via
a new requireAnyPermission helper so lab staff can attach analyses
- UPLOAD_DIR env (default ./uploads) + a persistent docker volume
frontend:
- lib/attachments.ts client (multipart upload, list, delete, preview URL)
- staged file picker in the patient Add/Edit dialog (uploaded after save)
and the lab Add-result dialog (linked to the result)
- a Files section in the patient sheet that lists attachments and opens
them in a preview dialog (images inline, others via download)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Real LLM chat replacing the mock, backend-centric per the plan:
- Multi-provider API-key mode (OpenAI / Anthropic / Gemini via the AI SDK) plus
local Ollama (OpenAI-compatible endpoint). Provider is derived from the
picked model id; the matching stored key is used. New user_ai_settings table
holds per-user config with provider API keys encrypted at rest (AES-256-GCM,
src/lib/crypto.ts, keyed by AI_CREDENTIALS_KEY).
- POST /api/ai/config (get/put, secrets never returned), POST /api/ai/test
(Ollama ping / key presence), POST /api/ai/import (approved migration commit,
re-validated server-side, reuses the audited patient service).
- POST /api/chat: streamText agent with tools (getPatient, getPatientLabs,
searchPatients, previewImport). Real record data streams to the clinician as
custom data parts (cards) while the model sees only Veil-redacted results.
- Veil (src/services/ai/veil.ts): de-identifies patient identifiers to tokens
before external calls, resolves tokens on tool args, and rehydrates the final
answer. Bypassed for local Ollama. External mode runs non-streamed so the
rehydrated text is correct. Every call is audited (provider + Veil level).
- Shared role-scoping helpers extracted to src/lib/role-scope.ts (reused by the
patient routes and chat tools so visibility rules match).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Implements the first temetro backend: an Express 5 API on Postgres via
Drizzle ORM, with authentication and multi-tenant clinics powered by
Better Auth.
- Auth: email/password with required email verification, password reset,
rate limiting, CSRF/trusted-origins, secure cookies, session audit hook.
- Organizations (clinics) with RBAC (owner/admin/member/viewer) and an
extended `patient` permission set; member invitations by email.
- Org-scoped patient records mirroring the frontend Patient shape, with
CRUD endpoints gated by permission (read/write/delete).
- Email helper logs links to the console when SMTP is unset (zero-setup
local dev); Dockerfile + docker-compose (db + backend + frontend) with
migrations applied on startup and a configurable Postgres host port.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>