Reconstruct the full app from init-source overlays (base + fix-1..6 + update-1..3, last-wins) at the repo root, complete the missing pieces so it builds and runs, and stage the Unraid deployment. App completion: - types/index.ts: former Prisma enums as string-literal unions + AppUser - pages/_app.tsx + styles/globals.css (mount AppProvider/ToastProvider) - API routes: auth/login, auth/me, users, submissions (+REVIEW_READY notify), forms (list/create), notifications - scripts/create-admin.js: idempotent first-admin bootstrap - 14 unbuilt nav targets stubbed via ComingSoon placeholder SQLite refactor (single-container, no external DB): - schema provider -> sqlite; enums -> String; Json -> String; FormField.options String[] -> JSON-encoded String - lib/forms.ts (de)serialises options at the DB boundary - drop mode:"insensitive" (unsupported on SQLite) - enum imports repointed from @prisma/client to @/types Deploy: - multi-stage Dockerfile (next build -> prod runner), docker-entrypoint.sh (prisma db push -> create-admin -> next start), .dockerignore - docker-compose.yml: br0 10.2.0.x, /mnt/user/appdata/qms -> /data volume - README rewritten for the Unraid/Gitea Actions flow; .env scrubbed of the live Supabase credential; vercel.json removed Verified: next build clean (41 routes); live SQLite round-trip of login/session, form options array, and submission -> REVIEW_READY. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4.6 KiB
V11 Enterprise QMS
Full-stack quality management system — Next.js 15 (pages router) + Prisma + SQLite, packaged as a single Docker container for self-hosting on Unraid. Starts from zero data and learns as your team uses it.
Status — partial build. The implemented modules are: authentication, the first-build Form builder (admin), Fill / Report an issue (production), Nonconformances + Resolutions library (QC), Client release / Shipments, Client issues / Quality escapes, and the living Shipping standard. The remaining nav items (Admin dashboard, Users, Audit trail, Settings, QC dashboard, CAPA, Audits, Documents, Risk, Suppliers, Standards, My submissions, Management dashboard & Reports) are wired into navigation but render a "Coming soon" placeholder — the data model for them already exists in
prisma/schema.prisma.
Roles & access
| Role | Access |
|---|---|
| Admin | Everything: form builder, users, settings, audit trail |
| QC | CAPA, audits, NCRs, resolutions, documents, risk, suppliers, standards, shipping standard |
| Production | Fill first-build forms, report issues |
| Production lead | Production + client release, client issues, shipping standard, NCRs |
| Logistics lead | Client release, client issues, shipping standard, NCRs |
| Management | Read-only dashboards and reports |
Only Admin / Production lead / Logistics lead can email a client release package.
Deploy on Unraid (the intended setup)
CI/CD: push to Gitea → Gitea Actions builds the image → pushes to registry.alwisp.com/jason/qms:latest
→ install/Force-update from the Unraid Docker GUI.
1. Build & push (automatic)
.gitea/workflows/docker-build.yml builds and pushes the image to
registry.alwisp.com/<owner>/<repo>:latest on every push to main. It needs two repo secrets:
REGISTRY_USER and REGISTRY_TOKEN.
2. Run on Unraid
Either docker compose up -d with the bundled docker-compose.yml, or add a
container in the Unraid Docker GUI with the equivalent settings:
| Setting | Value |
|---|---|
| Repository | registry.alwisp.com/jason/qms:latest |
| Network | Custom: br0 — assign a free IP in 10.2.0.0/24 |
| Volume | /mnt/user/appdata/qms → /data |
DATABASE_URL |
file:/data/qms.db |
NEXT_PUBLIC_APP_URL |
http://<the-br0-ip>:3000 |
ADMIN_EMAIL / ADMIN_PASSWORD / ADMIN_NAME |
first-run admin (created only if no admin exists) |
SMTP_* (optional) |
enables outbound email; omit to keep notifications in-app only |
On first start the container runs prisma db push (creates /data/qms.db), then create-admin
(creates the admin from ADMIN_* only if none exists), then serves on port 3000. The SQLite file
lives entirely in the mapped volume, so the container is disposable — update by pulling a new image.
The app is reached at http://<the-br0-ip>:3000. Sign in with the admin credentials and change the
password.
Local development
npm install
cp .env.example .env # DATABASE_URL defaults to file:./dev.db
npm run db:push # create the SQLite schema
npm run create-admin # seed the first admin from ADMIN_* in .env
npm run dev # http://localhost:3000
Useful scripts: npm run build, npm run start, npm run db:studio.
Why SQLite (not Postgres)
This deploy targets a single self-contained container, so the schema was converted from PostgreSQL to
SQLite. SQLite (via Prisma) supports neither native enums nor Json/scalar-list columns, so:
- former enums are stored as
Stringand validated in app code (union types intypes/index.ts); - former
Jsoncolumns (AuditLog.before/after,FormSubmission.data,QualityStandard.specs) and theFormField.optionslist are stored as JSON-encoded strings, (de)serialised at the DB boundary (seelib/forms.ts).
Project structure
/pages
/api ← backend API routes (auth, forms, submissions, ncrs, escapes, shipments, …)
/admin /qc /fill /management ← role-scoped pages
/components ← layout shell, shared UI kit, form field renderer
/lib ← prisma client, auth/session, email, form (de)serialisation helpers
/prisma ← schema.prisma (SQLite)
/scripts ← create-admin.js (first-run bootstrap)
Dockerfile · docker-compose.yml · docker-entrypoint.sh · .gitea/workflows/build.yml
init-source/holds the original delivery (base snapshot + fix/update overlays) for provenance; it is excluded from the build and the image.