mirror of
https://github.com/temetro/temetro.git
synced 2026-08-21 15:36:47 +00:00
backend: Patient Portal over the relay + wallet linking
- services/portal.ts: clinic-scoped portal actions (clinic info, doctors, availability, linkWallet, conflict-checked booking, results, downloadable lab files) plus handlePortalRequest to dispatch a relayed portal:request. - relay-client.ts: handle portal:request on the hub and ack the result back down the relay (device path identifies the patient by verified wallet number). - patients: new nullable wallet_number column (+ migration); linking stores it, and walletNumberForPatient now resolves via it so pushes work after a portal link, not only after a permanent share. - routes/portal.ts: GET /:clinic/link returns the relay-based pairing descriptor (clinic signing key + relay URL) for the QR — no more localhost-baked API URL. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,7 @@ import { z } from "zod";
|
||||
import { db } from "../db/index.js";
|
||||
import { member, organization, user } from "../db/schema/auth.js";
|
||||
import { staffProfile } from "../db/schema/staff-profile.js";
|
||||
import { env } from "../env.js";
|
||||
import { appointmentInputSchema } from "../lib/appointment-validation.js";
|
||||
import { HttpError } from "../lib/http-error.js";
|
||||
import { initialsFromName } from "../lib/initials.js";
|
||||
@@ -12,6 +13,7 @@ import { patientInputSchema } from "../lib/patient-validation.js";
|
||||
import { recordActivity } from "../services/activity.js";
|
||||
import { createAppointment, listAppointments } from "../services/appointments.js";
|
||||
import { createPatient, getPatient } from "../services/patients.js";
|
||||
import { getOrCreateKey } from "../services/signing.js";
|
||||
|
||||
// Clinical-capable roles that can be a patient's provider (mirrors
|
||||
// staff.ts PROVIDER_ROLES). Department roles (reception, pharmacy, lab) excluded.
|
||||
@@ -50,6 +52,27 @@ portalRouter.get("/:clinic", async (req, res, next) => {
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/portal/:clinic/link — the relay-based pairing descriptor the wallet
|
||||
// app scans to talk to this clinic over the Temetro Network: the clinic's
|
||||
// signing public key (the relay's routing id) + the relay URL. Both values are
|
||||
// non-secret (the signing key is the clinic's public identity). This is what
|
||||
// makes the Patient Portal QR reachable from a real phone — it no longer bakes
|
||||
// in a localhost API URL.
|
||||
portalRouter.get("/:clinic/link", async (req, res, next) => {
|
||||
try {
|
||||
const clinic = await resolveClinic(req);
|
||||
const key = await getOrCreateKey(clinic.id);
|
||||
res.json({
|
||||
clinicId: key.publicKey,
|
||||
relay: env.RELAY_URL,
|
||||
slug: String(req.params.clinic ?? "").trim(),
|
||||
name: clinic.name,
|
||||
});
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
|
||||
// GET /api/portal/:clinic/doctors — public list of the clinic's providers so a
|
||||
// patient can pick who to see. Returns only display-safe fields (name +
|
||||
// specialty); no ids, emails, or usernames leave this unauthenticated surface.
|
||||
|
||||
Reference in New Issue
Block a user