尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證正在載入完整 README。 以下為已儲存的摘錄。
Clinic console + public booking/queue site, replacing the notebook and the
index-card cabinet with a paperless MVP. Next.js 16 (App Router) + TypeScript,
Prisma/PostgreSQL, Auth.js, Tailwind + shadcn/ui. See SPEC.md for the full
product spec and DECISIONS.md for build-order progress and the assumptions
made along the way.
This repo previously held a different product (Stretch Lab PH); that work is
preserved in git history but is unrelated to what's here now.檢視完整 README ↗Clinic console + public booking/queue site, replacing the notebook and the
index-card cabinet with a paperless MVP. Next.js 16 (App Router) + TypeScript,
Prisma/PostgreSQL, Auth.js, Tailwind + shadcn/ui. See SPEC.md for the full
product spec and DECISIONS.md for build-order progress and the assumptions
made along the way.
This repo previously held a different product (Stretch Lab PH); that work is preserved in git history but is unrelated to what's here now.
npm install
npx prisma migrate dev
APP_DB_PASSWORD='<match APP_DATABASE_URL below>' psql "$DATABASE_URL" -f prisma/grant-app-role.sql
npm run db:seed
npm run dev
Open http://localhost:3000. Seeded logins (one
holding admin, plus a branch admin/2 front desk/3 doctors per clinic) are
printed by npm run db:seed, all sharing one dev password.
Copy .env.example to .env (Prisma reads DATABASE_URL from here) and
.env.local (everything else) before running migrations. APP_DATABASE_URL
names a non-superuser Postgres role (webinar_app) that doesn't exist on a
fresh Postgres install — the prisma/grant-app-role.sql step above creates
it (or updates its password if it already exists) and grants it the table
access the app needs; RLS depends on connecting as this role rather than a
superuser (see "Authentication & authorization" below), so skipping this
step doesn't break the app, it just silently stops enforcing clinic
scoping at the database layer. Re-run it any time after
prisma migrate reset, which recreates the schema from scratch and drops
these grants with it.
Production runs on Google Cloud Run (Singapore) against Supabase. Deploys
happen from .github/workflows/deploy.yml after CI passes on master; the
one-time setup, verification and cutover steps are in
docs/deploy-cloud-run.md.
In production (Supabase) the two URLs point at two different
things on purpose: DATABASE_URL at the direct or session-mode connection,
because Prisma Migrate needs one, and APP_DATABASE_URL at the pooler in
transaction mode — port 6543, with ?connection_limit=5 (since Prisma 7
the app talks to Postgres through node-postgres — lib/db/client-factory.ts
turns connection_limit into the pool size, and the old pgbouncer=true
flag no longer does anything).
Session mode (port 5432) caps clients at the pooler's pool_size, and a
few warm serverless instances exhaust that; the app then fails mid-request
with EMAXCONNSESSION. lib/db/prisma.ts logs an error at startup if it
sees the wrong shape, and explains why transaction mode is safe for the RLS
scoping here.
Auth.js (Credentials provider) with JWT sessions — no separate session
store. Config is split across two files: proxy.ts deliberately runs only
Prisma-free logic, so its route gating stays a cheap check against
already-encoded JWT claims rather than a DB read on every navigation:
auth.config.ts — Prisma-free base config (session strategy, login page,
the session callback that copies role/clinic fields from the token onto
session.user). No providers, no Prisma. Shared by both files below.auth.ts — the full Node.js-runtime config: the Credentials provider
(bcrypt password check) and a jwt callback that re-checks isActive on
every request, so deactivating a user takes effect on their next
navigation rather than only at their next login.proxy.ts — route gating, built on auth.config.ts only. Redirects
signed-out requests to /login, and redirects a signed-in user's role to
its home page if it doesn't match the section (/staff, /doctor,
/console) it's not allowed into — the one exception being
/staff/inventory, the medicine pages, which a doctor may also enter.
Public prefixes (/book/, /q/,
/display/, /login, /api/auth) skip this entirely — patients never
authenticate (§4). Named proxy.ts per the Next.js 16 convention (the
older middleware.ts name still works but is deprecated); unlike the old
middleware convention, proxy.ts runs on the Node.js runtime rather than
Edge, though this codebase doesn't rely on that for anything yet.Proxy role checks are a coarse first gate, not the authorization
boundary itself: every query-layer function additionally takes an
AbilitySubject (lib/permissions/ability.ts) and is scoped to that user's
clinic, backstopped by Postgres RLS (lib/db/rls.ts) so a bug in the app
layer can't leak another clinic's rows. A forbidden read throws
ForbiddenError (lib/permissions/errors.ts) rather than silently
returning an empty list.
.github/workflows/ci.yml runs on every push/PR to master: spins up a
disposable Postgres 17 service container, applies migrations, runs
prisma/grant-app-role.sql against it (a fixed, clearly-fake password —
the container and its data don't outlive the job), then tsc --noEmit,
eslint ., and vitest run. The test suite talks to that real database
rather than a mock — RLS and the superuser/webinar_app connection split
it depends on can't be verified against anything less — but every test
creates and tears down its own fixtures, so it never needs npm run db:seed first.
npm run dev — start the dev servernpm run build / npm run start — production build/servenpm run lint — ESLintnpm run test — Vitest — lib/queries/__tests__/patients.test.ts proves
the clinic-scoping bar from §12/M1: a cross-clinic read 403s and is
audit-logged, both at the app layer and independently via the Postgres RLS
backstopnpm run db:seed — reseed the holding company, clinics, staff/doctor
accounts, the medicine catalog, and a couple of demo patients per clinic.
Destructive: it deletes every patient, payment, consultation, user and
branch first and rebuilds them, so it is for a development database only —
never point it at a clinic that is in use.npm run db:seed-medicines — adds the starter medicine catalog
(prisma/medicine-catalog.ts, ~38 items) to any branch missing it. The
safe counterpart to the above: it only ever inserts, never deletes and
never edits an existing medicine, so it can be run against a live
deployment. Dry run by default, like db:retention:
npm run db:seed-medicines — report what would be addednpm run db:seed-medicines -- --execute — add the catalog at zero stock... -- --execute --with-opening-stock — also book opening quantities
(demo/staging only; a real clinic receives its own stock)... -- --execute --branch=cebu-city — one branchnpm run db:retention — reports what's past its retention window
(lib/retention/policy.ts) without deleting anything; add -- --execute
to actually purge. Connects via DATABASE_URL (the migration/superuser
role) since this needs DELETE, which the app's runtime
APP_DATABASE_URL role deliberately doesn't have. Not yet wired to run
on a schedule — see SECURITY.md.