Repository navigation
Migrate to Next.js + TypeScript with Prisma on Neon Postgres, migration rules and tests - #34
Merged
Merged
Conversation
Removes the Express server, Sequelize models and static HTML/JS pages and sets up Next.js (App Router), TypeScript, ESLint, legacy *.html redirects and security headers.
- Models ported from Sequelize with snake_case tables, timestamptz, explicit FKs - Explicit join tables, soft-deleted vehicles, revoked_tokens and job_locks tables - CHECK constraint on feedback rating (added by hand to the init migration) - prisma.config.ts: pooled DATABASE_URL at runtime, DIRECT_URL for the CLI - Idempotent dev seed
route() wrapper (rate limits, auth, params, error mapping) -> feature services -> Prisma; pure domain logic for ETA/geo/periods; integrations for Firebase, Google Maps, Africa's Talking and SMTP; ETA cycle with a Postgres job lease, runnable from Vercel Cron, an in-process scheduler or a standalone worker. Fixes from the old server: typed JWTs (2FA temp token was accepted as an access token), working refresh rotation (isBlocklisted was never imported), blocks/password resets take effect immediately, Google sign-in redirect read the wrong part of the URL.
Same markup and styles as the old pages (CSS scoped per page), a typed API client with automatic token refresh, role guards, and fixes for responses the old pages misread (paginated lists, feedback feed). The vehicle stats date picker now works.
- scripts/check-migrations.mjs: immutability, ordering, naming, destructive-SQL guard - CI: lint/typecheck/test/build + migrations job (drift, apply, verify, integration) - Neon preview branch per PR; production deploys only from main - docs/MIGRATIONS.md, README, .env.example, api-tests.http
cliffkoome
force-pushed
the
feat/nextjs-prisma-neon-migration
branch
from
October 19, 2026 21:13
e7ea8d7 to
e0a67c6
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Migrates MatConnect from Express + Sequelize/MariaDB + static HTML to Next.js 16 (App Router) + TypeScript, Prisma 7 on Neon Postgres, with enforced migration rules, a layered server architecture and a test suite.
What changed
Architecture
src/app/api/**/route.ts— thin HTTP layer; same endpoints as before (api-tests.httpupdated)src/server/http/route()— one wrapper for rate limits, auth/role checks, params and error mapping (HttpError/ Zod / Prisma →{ message, errors? })src/server/modules/*— business logic per feature;src/server/domain/*— pure ETA/geo/date logic;src/server/integrations/*— Firebase, Google Maps, Africa's Talking (REST), SMTPDatabase (Prisma + Neon)
timestamptz, explicit join tables, explicitonDelete, CHECK on feedback ratingDATABASE_URLat runtime (Neon serverless driver), directDIRECT_URLfor migrationsrevoked_tokens(logout/refresh rotation — works on serverless, unlike the old in-memory blocklist) andjob_locks(lease so only one ETA cycle runs at a time)Migration rules — see
docs/MIGRATIONS.mdnpm run db:migrate:check: merged migrations immutable, strict ordering, naming, destructive SQL (DROP COLUMN,SET NOT NULL, renames, …) needs an explicit-- migration-safety: destructive-approved <reason>neon-preview.yml: per-PR Neon branch copied from production with the PR's migrations applieddeploy-migrations.yml: the only path to production (migrate deployon merge tomain, in aproductionenvironment)CODEOWNERSforprisma/ETA background cycle — runs every 30 s in-process under
next start, via/api/cron/eta-cycle(Vercel Pro cron,?runs=2), or asnpm run eta:worker.Frontend — every page ported to React with the original markup/CSS (scoped per page), typed API client with automatic token refresh, old
*.htmlURLs redirect.admin-settings.htmlandstage-details.htmlwere unlinked mock-ups and were dropped.Bugs fixed along the way
typclaim./refresh-tokenalways crashed (isBlocklistedwas never imported).{ items, total }.sameSite=nonerefresh cookie → nowLax, httpOnly, scoped to/api/auth.Testing
npm test: 207 unit + route-handler tests (domain logic, JWTs incl. the 2FA regression, TOTP incl. old speakeasy secrets, guard, services with mocked Prisma, ETA cycle, migration rules)npm run test:integration: 11 tests against real Postgres 17 — constraints, unique-violation mapping, concurrent trip counting, job lease, revocation, timezone handling. These caught a real bug:timestampcolumns + a non-UTC server shifted every write by the server offset, so the schema now usestimestamptzand sessions run in UTC.Before merging / deploying
DATABASE_URLand the direct URL toDIRECT_URL(see.env.example).DATABASE_URL_DIRECT_PRODUCTION,NEON_API_KEY; variableNEON_PROJECT_ID; environmentproduction.JWT_SECRET,JWT_REFRESH_SECRET,APP_URL,CRON_SECRET+ optional integrations. Firebase now usesFIREBASE_CREDENTIALS_BASE64(no JSON file on disk).${APP_URL}/api/auth/google/callback.docs/MIGRATIONS.md. Password hashes and 2FA secrets carry over unchanged.npm run eta:workerfor the 30 s cycle (README has the options).Generated by Claude Code