Multi-tenant flight logging & aircraft maintenance — a clean foundation to build on.
This is not the full application. It is a deliberately small base with the hard parts (multi-tenancy, auth, validation, a service layer) done correctly, plus aircraft and flight logging implemented end-to-end as the reference pattern to copy when adding features.
- Next.js 14 (App Router) + React 18 + TypeScript
- Postgres via Prisma
- NextAuth (credentials + JWT) for auth
- Zod for input validation
- Tailwind CSS
npm install
# configure environment
cp .env.example .env
# - set DATABASE_URL to your Postgres database
# - set NEXTAUTH_SECRET (openssl rand -base64 32)
# create the database schema
npx prisma migrate dev --name init
# load demo data (one org, one aircraft, one flight)
npm run db:seed
npm run devThen open http://localhost:3000 and sign in with the seeded account:
owner@demo.test / password123
app/
(app)/ authenticated area (shares nav + auth gate)
dashboard/
aircraft/ ← reference feature: list / new / [id]
flights/ ← reference feature: list / new
login/ register/ public auth pages
api/auth/ NextAuth route
components/ shared UI (app-nav)
lib/
auth.ts NextAuth config
auth-guard.ts requireUser / requireRole — use in every protected page & action
prisma.ts Prisma client singleton
validation.ts Zod schemas (the only place input shapes are defined)
services/ data access + business logic, always scoped by orgId
format.ts display helpers
prisma/
schema.prisma multi-tenant data model
seed.ts demo data
When you add a feature, copy how aircraft / flights are built:
- Validation — define the input shape as a Zod schema in
lib/validation.ts. - Service — put data access in
lib/services/<x>-service.ts. Every query is scoped byorgId; that is the tenant boundary. Never query without it. - Server action — in
app/(app)/<x>/actions.ts. CallrequireUser()orrequireRole()first; deriveorgId/userIdfrom the session, never from form input. Validate, call the service, redirect on success. - Pages — server components that call the service; client components only for forms/interactivity.
Concurrency note: flight-service.logFlight locks the aircraft row
(SELECT … FOR UPDATE) before updating airframe hours — follow that approach
for any operation that reads-then-writes shared counters.
OCR meter reading, engine-monitor data, financials, GPS/KML, PDF export, and
the legacy admin/pilot screens are not here. The previous implementation
of all of it is preserved in _legacy/ (gitignored) for reference — copy
pieces back as you rebuild them on this foundation.
See DATABASE_DESIGN.md and REMEDIATION_PLAN.md (repo root) for the schema
rationale and the longer-term roadmap, including the planned Expo mobile app.