3 Architecture
Hardik edited this page 2026-06-19 14:06:07 +05:30

Architecture

Pelagia Portal is a full-stack Next.js 15 (App Router) application with Prisma + PostgreSQL, NextAuth v5 auth, and Tailwind CSS v4. It is an internal line-of-business app; the stack optimises for developer velocity, end-to-end type safety, and operational simplicity (minimal infrastructure).

Technology stack

Layer Choice Why
Framework Next.js 15 (App Router) Full-stack React; Server Components cut client JS; built-in API routes
Language TypeScript 5 (strict) Shared types front-to-back; contract mismatches caught at compile time
UI React 19 Concurrent rendering, Server Components
Components shadcn/ui + Radix primitives Accessible, unstyled, copy-owned source (no black-box upgrades)
Styling Tailwind CSS v4 Utility-first, consistent design tokens
ORM Prisma 5 Type-safe client; schema-first migrations; Studio for data inspection
Database PostgreSQL 16 ACID; JSON columns; mature tooling
Auth NextAuth.js v5 Microsoft Entra SSO and credentials provider
File storage Cloudflare R2 (prod) / local FS (dev) S3-compatible, presigned uploads off the app server; dev avoids paid services
Email Resend + React Email (prod) / console (dev) Transactional email, React-rendered templates; dev needs no API key
Charts Recharts Lightweight, composable, works in RSC
Validation Zod Shared between server actions and client forms
Testing Vitest + Testing Library + Playwright Fast unit/integration; E2E for critical paths
Export exceljs / xlsx XLSX export & Excel PO import parsing
CI/CD Forgejo + Forgejo Actions (self-hosted) Issue→fix→PR pipeline; tag-triggered deploy
Hosting Self-hosted on pms1 (Ubuntu), pm2 + native PostgreSQL Single-VM, full data control

High-level system

┌─────────────────────────────────────────────┐
│ Browser — React 19 + shadcn/ui + Tailwind    │
│ Server Components (read) + Client (forms)    │
└───────────────────┬──────────────────────────┘
                    │ HTTPS
┌───────────────────▼──────────────────────────┐
│ Next.js 15 App Server                         │
│  App Router pages (RSC)  │  Server Actions /  │
│                          │  Route Handlers    │
│  ┌──────────────────────────────────────────┐│
│  │ Business logic: PO state machine,         ││
│  │ permission checks, notifier               ││
│  └──────────────────────────────────────────┘│
└───────┬───────────────┬───────────────┬───────┘
        │               │               │
┌───────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐
│ PostgreSQL   │ │ Cloudflare  │ │   Resend    │
│ (Prisma)     │ │ R2 (docs)   │ │  (email)    │
└──────────────┘ └─────────────┘ └─────────────┘
        │
┌───────▼──────────────────────────────────────┐
│ GstService (Express + Playwright, :3003)      │
│ GST portal CAPTCHA / taxpayer lookup proxy    │
└───────────────────────────────────────────────┘

Key design decisions

  • Server Components for all data-fetching pages; Client Components only where interactivity is needed.
  • Server Actions for all mutations (create PO, approve, pay, etc.). There are no REST endpoints for mutations — route handlers exist only for auth, file signing/serving, GST proxying, notifications, search, and exports.
  • Prisma Decimal cannot cross into Client Components — convert with Number() in the Server Component before passing as a prop (see po-detail.tsxlineItemsForEditor).
  • Storage and email toggle automatically on NODE_ENV (R2/Resend in prod, local disk/console in dev).
  • One source of truth for state — every PO status change goes through lib/po-state-machine.ts and is recorded as a POAction (audit trail).

Application structure

app/
├── (auth)/login/
├── (portal)/                       # authenticated shell
│   ├── layout.tsx                  # sidebar + header (role-aware)
│   ├── dashboard/
│   ├── my-orders/
│   ├── po/{new,import,[id],[id]/edit,[id]/receipt}/
│   ├── approvals/{,[id]}/
│   ├── payments/{,history}/
│   ├── history/
│   ├── inventory/{items,vendors,cart}/
│   └── admin/{users,companies,accounts,products,sites,vessels,vendors,superuser-requests}/
└── api/
    ├── auth/[...nextauth]/
    ├── files/{sign, dev/[...key]}/
    ├── gst/{, captcha}/
    ├── notifications/{, read}/
    ├── po/[id]/export/
    ├── po/import/
    ├── products/search/
    └── reports/export/

lib/
├── db.ts                  # Prisma client singleton
├── auth helpers           # (auth.ts at App root; NextAuth v5 config)
├── po-state-machine.ts    # all valid status transitions + required roles
├── permissions.ts         # role → allowed-action map
├── po-number.ts           # structured PO number gen/parse
├── po-import-parser.ts    # Excel PO parsing
├── notifier.ts            # email dispatch (Resend prod / console dev)
├── storage.ts             # file upload/download (R2 prod / local dev)
├── upload-files.ts        # client-side upload helper
├── attachments.ts         # PO document grouping
├── cart.ts                # localStorage cart helpers
├── cost-centre-groups.ts  # vessel grouping for selects
├── geo.ts                 # pincode → lat/long, distance
├── id-generators.ts       # vendor/product code generation
├── feature-flags.ts       # INVENTORY_ENABLED
├── forgejo.ts             # Report-Issue → Forgejo API
├── utils.ts               # formatCurrency, formatDate, status maps
└── validations/{po.ts,user.ts}   # Zod schemas

See the Data Model, PO Lifecycle, and Roles and Permissions for the core domain logic.

API surface

All data mutations are Server Actions co-located with their page (app/(portal)/*/actions.ts). Route handlers are reserved for:

Route Handler Method Purpose
/api/auth/[...nextauth] GET/POST Auth.js session endpoints
/api/files/sign POST Generate R2 presigned upload URL (prod)
/api/files/dev/[...key] GET/PUT Local upload/download (dev only; 404 in prod)
/api/gst POST Proxy GST taxpayer search via GstService
/api/gst/captcha GET Proxy GST portal CAPTCHA image/session
/api/notifications GET Fetch the current user's notifications
/api/notifications/read POST Mark notifications read
/api/po/[id]/export GET Export single PO as PDF/XLSX (gated to MGR_APPROVED+)
/api/po/import POST Parse an uploaded Excel PO (Manager/SuperUser/Admin)
/api/products/search GET Fuzzy product search for the line-item editor
/api/reports/export GET Export PO history as CSV/PDF

Auth & authorisation

  • NextAuth v5 with a Microsoft Entra SSO provider and a credentials provider. Passwords hashed with bcrypt.
  • SSO-only users have no passwordHash (nullable); the profile page lets them optionally set one and is reachable by every role.
  • Authorisation is centralised in lib/permissions.ts (hasPermission / requirePermission). Server Actions call requirePermission() at the top before any DB write; Server Components gate whole page segments. Full matrix on Roles and Permissions.

Development conventions

  • Trunk: master. Work lands via PRs (feat//fix//chore/, or claude/issue-N from the automated pipeline). Production is whatever vX.Y.Z tag is deployed; staging is a deployed instance of latest master, not a branch.
  • Commits: Conventional Commits (feat:, fix:, refactor:).
  • Migrations: never edit schema.prisma without generating and committing a migration; migration files are reviewed in PRs.
  • PR policy: every change goes through a PR; PRs add docs + tests.
  • Secrets: never committed — server ~/pms/App/.env, local .env.local.