- Updated PLESK_DEPLOYMENT.md to include new Salesforce credentials and webhook security configurations. - Refactored order fulfillment controller to streamline the process and improve readability. - Introduced EnhancedWebhookSignatureGuard for improved HMAC signature validation and nonce management. - Updated various documentation files to reflect changes in endpoint naming from `/provision` to `/fulfill` for clarity and consistency. - Enhanced Redis integration for nonce storage to prevent replay attacks. - Removed deprecated WebhookSignatureGuard in favor of the new enhanced guard.
880 lines
27 KiB
Markdown
880 lines
27 KiB
Markdown
# 📦 Customer Portal - Package Structure & Architecture
|
|
|
|
This document provides comprehensive information about the Customer Portal codebase structure, package configurations, dependencies, and build architecture.
|
|
|
|
## 🏗️ Monorepo Architecture
|
|
|
|
### Complete Project Structure
|
|
```
|
|
customer-portal/
|
|
├── apps/
|
|
│ ├── portal/ # Next.js 15.5 Frontend Application
|
|
│ │ ├── src/
|
|
│ │ │ ├── app/ # Next.js App Router
|
|
│ │ │ ├── components/ # React components
|
|
│ │ │ ├── lib/ # Utility functions
|
|
│ │ │ └── styles/ # Tailwind CSS styles
|
|
│ │ ├── public/ # Static assets
|
|
│ │ ├── package.json # Frontend dependencies
|
|
│ │ ├── next.config.mjs # Next.js configuration
|
|
│ │ ├── tailwind.config.js # Tailwind CSS config
|
|
│ │ ├── tsconfig.json # TypeScript config
|
|
│ │ └── Dockerfile # Container build file
|
|
│ │
|
|
│ └── bff/ # NestJS Backend for Frontend
|
|
│ ├── src/
|
|
│ │ ├── modules/ # Feature modules
|
|
│ │ ├── common/ # Shared backend utilities
|
|
│ │ ├── config/ # Configuration files
|
|
│ │ └── main.ts # Application entry point
|
|
│ ├── prisma/
|
|
│ │ ├── schema.prisma # Database schema
|
|
│ │ ├── migrations/ # Database migrations
|
|
│ │ └── seed.ts # Database seeding
|
|
│ ├── test/ # Test files
|
|
│ ├── package.json # Backend dependencies
|
|
│ ├── tsconfig.json # TypeScript config
|
|
│ ├── tsconfig.build.json # Build-specific TS config
|
|
│ └── Dockerfile # Container build file
|
|
│
|
|
├── packages/
|
|
│ └── shared/ # Shared Package
|
|
│ ├── src/
|
|
│ │ ├── types/ # TypeScript type definitions
|
|
│ │ ├── utils/ # Common utility functions
|
|
│ │ ├── constants/ # Application constants
|
|
│ │ ├── schemas/ # Validation schemas (Zod)
|
|
│ │ └── index.ts # Package exports
|
|
│ ├── dist/ # Compiled output (generated)
|
|
│ ├── package.json # Shared package config
|
|
│ └── tsconfig.json # TypeScript config
|
|
│
|
|
├── scripts/ # Build and deployment scripts
|
|
├── docs/ # Documentation
|
|
├── secrets/ # Sensitive configuration files
|
|
├── compose-plesk.yaml # Plesk Docker stack
|
|
│
|
|
├── package.json # Root workspace configuration
|
|
├── pnpm-workspace.yaml # pnpm workspace definition
|
|
├── pnpm-lock.yaml # Dependency lock file
|
|
├── tsconfig.json # Root TypeScript config
|
|
├── eslint.config.mjs # ESLint configuration
|
|
├── compose-plesk.yaml # Plesk deployment config
|
|
├── .env # Environment variables
|
|
└── README.md # Project documentation
|
|
```
|
|
|
|
## 🛠️ Plesk Deployment Notes (Recommended)
|
|
|
|
- Proxy rules:
|
|
- `/` → container `portal-frontend` port `3000`
|
|
- `/api` → container `portal-backend` port `4000`
|
|
- Frontend → Backend base URL: set `NEXT_PUBLIC_API_BASE=/api` (same-origin; no CORS needed).
|
|
- Env split (do not keep secrets under `httpdocs`):
|
|
- Frontend env (client-safe): `/var/www/vhosts/asolutions.jp/private/env/portal-frontend.env`
|
|
- Backend env (server-only): `/var/www/vhosts/asolutions.jp/private/env/portal-backend.env`
|
|
- Compose (`compose-plesk.yaml`) highlights:
|
|
- Binds app ports to `127.0.0.1` and keeps DB/Redis internal.
|
|
- Uses `env_file` per service pointing to the private env files above.
|
|
- Backend waits for Postgres then runs migrations.
|
|
- Alpine images: backend installs toolchain for bcrypt/Prisma; frontend includes `libc6-compat`.
|
|
- Health endpoints: Next `/api/health`, Nest `/health`.
|
|
|
|
### Environment Files (Plesk)
|
|
|
|
- Create two env files on the server (under the domain's private dir):
|
|
- Frontend: `/var/www/vhosts/asolutions.jp/private/env/portal-frontend.env` — based on `env/portal-frontend.env.sample`
|
|
- Must include: `NEXT_PUBLIC_API_BASE=/api`
|
|
- Backend: `/var/www/vhosts/asolutions.jp/private/env/portal-backend.env` — based on `env/portal-backend.env.sample`
|
|
- Must include: `TRUST_PROXY=true`
|
|
- `DATABASE_URL` should use `database:5432`
|
|
- `REDIS_URL` should use `cache:6379`
|
|
- Set `JWT_SECRET` to a strong value
|
|
- Salesforce credentials: `SF_LOGIN_URL`, `SF_CLIENT_ID`, `SF_USERNAME`
|
|
- Salesforce private key: set `SF_PRIVATE_KEY_PATH=/app/secrets/sf-private.key` and mount `/app/secrets`
|
|
- Webhook secrets: `SF_WEBHOOK_SECRET` (Salesforce), `WHMCS_WEBHOOK_SECRET` (if using WHMCS webhooks)
|
|
- Webhook tolerances: `WEBHOOK_TIMESTAMP_TOLERANCE=300000` (ms; optional)
|
|
- Optional IP allowlists: `SF_WEBHOOK_IP_ALLOWLIST`, `WHMCS_WEBHOOK_IP_ALLOWLIST` (CSV of IP/CIDR)
|
|
- Pricebook: `PORTAL_PRICEBOOK_ID`
|
|
|
|
### Image Build and Upload
|
|
|
|
Option A — Use the helper script (recommended):
|
|
|
|
```bash
|
|
# Build both images, tag :latest and a date+sha tag, and write tarballs
|
|
scripts/plesk/build-images.sh
|
|
|
|
# Custom tag and output directory
|
|
scripts/plesk/build-images.sh --tag v1.0.0 --output ./dist
|
|
|
|
# Also push to a registry (e.g., GHCR)
|
|
scripts/plesk/build-images.sh --tag v1.0.0 --push ghcr.io/<org>
|
|
```
|
|
|
|
Option B — Manual build commands:
|
|
|
|
```bash
|
|
# Frontend
|
|
docker build -t portal-frontend:latest -f apps/portal/Dockerfile .
|
|
docker save -o portal-frontend.latest.tar portal-frontend:latest
|
|
|
|
# Backend
|
|
docker build -t portal-backend:latest -f apps/bff/Dockerfile .
|
|
docker save -o portal-backend.latest.tar portal-backend:latest
|
|
```
|
|
|
|
In Plesk → Docker → Images, upload both tar files. Then use `compose-plesk.yaml` under Docker → Stacks → Add Stack to deploy the services. Configure Proxy Rules on the domain:
|
|
|
|
- `/` → `portal-frontend` port `3000`
|
|
- `/api` → `portal-backend` port `4000`
|
|
|
|
### Webhook Security (Plesk)
|
|
|
|
- Endpoint for Salesforce Quick Action:
|
|
- `POST /api/orders/{sfOrderId}/fulfill`
|
|
- Required backend env (see above). Ensure the same HMAC secret is configured in Salesforce.
|
|
- The backend guard enforces:
|
|
- HMAC for all webhooks
|
|
- Salesforce: timestamp + nonce with Redis-backed replay protection
|
|
- WHMCS: timestamp/nonce optional (validated if present)
|
|
- Health check `/health` includes `integrations.redis` to verify nonce storage.
|
|
|
|
Alternatively, load via SSH on the Plesk host:
|
|
|
|
```bash
|
|
scp portal-frontend.latest.tar portal-backend.latest.tar user@plesk-host:/tmp/
|
|
ssh user@plesk-host
|
|
sudo docker load -i /tmp/portal-frontend.latest.tar
|
|
sudo docker load -i /tmp/portal-backend.latest.tar
|
|
```
|
|
|
|
Or push to a registry (example: GHCR):
|
|
|
|
```bash
|
|
docker tag portal-frontend:latest ghcr.io/<org>/portal-frontend:latest
|
|
docker tag portal-backend:latest ghcr.io/<org>/portal-backend:latest
|
|
docker push ghcr.io/<org>/portal-frontend:latest
|
|
docker push ghcr.io/<org>/portal-backend:latest
|
|
```
|
|
|
|
Quick checklist:
|
|
- Proxy rules added for `/` and `/api`.
|
|
- `NEXT_PUBLIC_API_BASE=/api` available to the frontend.
|
|
- DB URL uses `database:5432` (compose service name).
|
|
- Secrets are under `/var/www/vhosts/asolutions.jp/private` (not `httpdocs`).
|
|
|
|
### Technology Stack & Versions
|
|
- **Runtime**: Node.js 22+ (LTS)
|
|
- **Package Manager**: pnpm 10.15.0 (workspace support)
|
|
- **Language**: TypeScript 5.9.2 (strict mode)
|
|
- **Frontend Framework**: Next.js 15.5.0 with React 19.1.1
|
|
- **Backend Framework**: NestJS 11.1.6
|
|
- **Database ORM**: Prisma 6.14.0
|
|
- **Styling**: Tailwind CSS 4.1.12
|
|
- **State Management**: Zustand 5.0.8 + TanStack Query 5.85.5
|
|
- **Validation**: Zod 4.0.17
|
|
- **Authentication**: JWT + bcrypt
|
|
- **Database**: PostgreSQL 17
|
|
- **Cache**: Redis 7 with ioredis 5.7.0
|
|
|
|
## 📦 Workspace Configuration
|
|
|
|
### Root Package.json (Workspace Root)
|
|
```json
|
|
{
|
|
"name": "customer-portal",
|
|
"version": "1.0.0",
|
|
"description": "Customer portal with BFF architecture",
|
|
"private": true,
|
|
"packageManager": "pnpm@10.15.0",
|
|
"engines": {
|
|
"node": ">=22.0.0",
|
|
"pnpm": ">=10.0.0"
|
|
},
|
|
"scripts": {
|
|
"predev": "pnpm --filter @customer-portal/shared build",
|
|
"dev": "./scripts/dev/manage.sh apps",
|
|
"dev:all": "pnpm --parallel --filter @customer-portal/shared --filter @customer-portal/portal --filter @customer-portal/bff run dev",
|
|
"build": "pnpm --recursive --reporter=default run build",
|
|
"start": "pnpm --parallel --filter @customer-portal/portal --filter @customer-portal/bff run start",
|
|
"test": "pnpm --recursive run test",
|
|
"lint": "pnpm --recursive run lint",
|
|
"type-check": "pnpm --filter @customer-portal/shared build && pnpm --recursive run type-check",
|
|
"clean": "pnpm --recursive run clean"
|
|
},
|
|
"devDependencies": {
|
|
"@eslint/eslintrc": "^3.3.1",
|
|
"@eslint/js": "^9.34.0",
|
|
"@types/node": "^24.3.0",
|
|
"eslint": "^9.33.0",
|
|
"eslint-config-next": "15.5.0",
|
|
"eslint-plugin-prettier": "^5.5.4",
|
|
"globals": "^16.3.0",
|
|
"husky": "^9.1.7",
|
|
"prettier": "^3.6.2",
|
|
"typescript": "^5.9.2",
|
|
"typescript-eslint": "^8.40.0"
|
|
},
|
|
"dependencies": {
|
|
"@sendgrid/mail": "^8.1.5"
|
|
}
|
|
}
|
|
```
|
|
|
|
### Workspace Definition (pnpm-workspace.yaml)
|
|
```yaml
|
|
packages:
|
|
- 'apps/*' # Frontend and Backend applications
|
|
- 'packages/*' # Shared libraries and utilities
|
|
```
|
|
|
|
### Package Dependency Graph
|
|
```
|
|
@customer-portal/shared (packages/shared)
|
|
├── Built first (no dependencies)
|
|
└── Exports: types, utils, constants, schemas
|
|
|
|
@customer-portal/portal (apps/portal)
|
|
├── Depends on: @customer-portal/shared
|
|
└── Builds: Next.js standalone application
|
|
|
|
@customer-portal/bff (apps/bff)
|
|
├── Depends on: @customer-portal/shared
|
|
├── Generates: Prisma client
|
|
└── Builds: NestJS application
|
|
```
|
|
|
|
## 🎯 Frontend Application (apps/portal)
|
|
|
|
### Complete Package Configuration
|
|
```json
|
|
{
|
|
"name": "@customer-portal/portal",
|
|
"version": "0.1.0",
|
|
"private": true,
|
|
"scripts": {
|
|
"dev": "next dev -p ${NEXT_PORT:-3000}",
|
|
"build": "next build",
|
|
"build:turbo": "next build --turbopack",
|
|
"start": "next start -p ${NEXT_PORT:-3000}",
|
|
"lint": "eslint .",
|
|
"lint:fix": "eslint . --fix",
|
|
"type-check": "tsc --noEmit",
|
|
"test": "echo 'No tests yet'"
|
|
},
|
|
"dependencies": {
|
|
"@customer-portal/shared": "workspace:*",
|
|
"@heroicons/react": "^2.2.0",
|
|
"@hookform/resolvers": "^5.2.1",
|
|
"@tanstack/react-query": "^5.85.5",
|
|
"@tanstack/react-query-devtools": "^5.85.5",
|
|
"class-variance-authority": "^0.7.1",
|
|
"clsx": "^2.1.1",
|
|
"date-fns": "^4.1.0",
|
|
"lucide-react": "^0.540.0",
|
|
"next": "15.5.0",
|
|
"react": "19.1.1",
|
|
"react-dom": "19.1.1",
|
|
"react-hook-form": "^7.62.0",
|
|
"tailwind-merge": "^3.3.1",
|
|
"tw-animate-css": "^1.3.7",
|
|
"world-countries": "^5.1.0",
|
|
"zod": "^4.0.17",
|
|
"zustand": "^5.0.8"
|
|
},
|
|
"devDependencies": {
|
|
"@tailwindcss/postcss": "^4.1.12",
|
|
"@types/node": "^24.3.0",
|
|
"@types/react": "^19.1.10",
|
|
"@types/react-dom": "^19.1.7",
|
|
"tailwindcss": "^4.1.12",
|
|
"typescript": "^5.9.2"
|
|
}
|
|
}
|
|
```
|
|
|
|
### Frontend Architecture & Dependencies
|
|
|
|
#### Core Framework Stack
|
|
- **Next.js 15.5.0**: App Router, Server Components, Streaming
|
|
- **React 19.1.1**: Latest React with concurrent features
|
|
- **TypeScript 5.9.2**: Strict type checking
|
|
|
|
#### UI & Styling
|
|
- **Tailwind CSS 4.1.12**: Utility-first CSS framework
|
|
- **@tailwindcss/postcss**: PostCSS integration
|
|
- **tailwind-merge**: Conditional class merging
|
|
- **class-variance-authority**: Component variant management
|
|
- **clsx**: Conditional className utility
|
|
- **tw-animate-css**: Tailwind animation utilities
|
|
|
|
#### State Management & Data Fetching
|
|
- **Zustand 5.0.8**: Lightweight state management
|
|
- **TanStack Query 5.85.5**: Server state management
|
|
- **@tanstack/react-query-devtools**: Development tools
|
|
|
|
#### Forms & Validation
|
|
- **react-hook-form 7.62.0**: Form state management
|
|
- **@hookform/resolvers 5.2.1**: Validation resolvers
|
|
- **Zod 4.0.17**: Runtime type validation
|
|
|
|
#### Icons & UI Components
|
|
- **@heroicons/react 2.2.0**: SVG icon library
|
|
- **lucide-react 0.540.0**: Additional icon set
|
|
|
|
#### Utilities
|
|
- **date-fns 4.1.0**: Date manipulation library
|
|
- **world-countries 5.1.0**: Country data
|
|
|
|
### Next.js Configuration (next.config.mjs)
|
|
```javascript
|
|
const nextConfig = {
|
|
// Enable standalone output for production deployment
|
|
output: process.env.NODE_ENV === "production" ? "standalone" : undefined,
|
|
|
|
// Exclude server-only packages from client bundle
|
|
serverExternalPackages: [
|
|
"pino", "pino-pretty", "pino-abstract-transport",
|
|
"thread-stream", "sonic-boom"
|
|
],
|
|
|
|
// Turbopack configuration (Next.js 15.5+)
|
|
turbopack: {
|
|
resolveAlias: { "@": "./src" }
|
|
},
|
|
|
|
// Environment variables validation
|
|
env: {
|
|
NEXT_PUBLIC_API_BASE: process.env.NEXT_PUBLIC_API_BASE,
|
|
NEXT_PUBLIC_APP_NAME: process.env.NEXT_PUBLIC_APP_NAME,
|
|
NEXT_PUBLIC_APP_VERSION: process.env.NEXT_PUBLIC_APP_VERSION,
|
|
},
|
|
|
|
// Image optimization
|
|
images: {
|
|
remotePatterns: [{ protocol: "https", hostname: "**" }]
|
|
},
|
|
|
|
// Disable ESLint during builds (handled separately)
|
|
eslint: { ignoreDuringBuilds: true },
|
|
|
|
// Security headers
|
|
async headers() {
|
|
return [{
|
|
source: "/(.*)",
|
|
headers: [
|
|
{ key: "X-Frame-Options", value: "DENY" },
|
|
{ key: "X-Content-Type-Options", value: "nosniff" },
|
|
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" }
|
|
]
|
|
}];
|
|
},
|
|
|
|
// Production optimizations
|
|
compiler: {
|
|
removeConsole: process.env.NODE_ENV === "production"
|
|
}
|
|
};
|
|
```
|
|
|
|
### TypeScript Configuration (tsconfig.json)
|
|
```json
|
|
{
|
|
"extends": "../../tsconfig.json",
|
|
"compilerOptions": {
|
|
"baseUrl": ".",
|
|
"paths": {
|
|
"@/*": ["./src/*"]
|
|
},
|
|
"plugins": [{ "name": "next" }]
|
|
},
|
|
"include": [
|
|
"next-env.d.ts",
|
|
"**/*.ts",
|
|
"**/*.tsx",
|
|
".next/types/**/*.ts"
|
|
],
|
|
"exclude": ["node_modules"]
|
|
}
|
|
```
|
|
|
|
## 🎯 Backend Application (apps/bff)
|
|
|
|
### Complete Package Configuration
|
|
```json
|
|
{
|
|
"name": "@customer-portal/bff",
|
|
"version": "1.0.0",
|
|
"description": "Backend for Frontend API",
|
|
"author": "",
|
|
"private": true,
|
|
"license": "UNLICENSED",
|
|
"scripts": {
|
|
"build": "nest build -c tsconfig.build.json",
|
|
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
|
"start": "nest start",
|
|
"dev": "NODE_OPTIONS=\"--no-deprecation\" nest start --watch --preserveWatchOutput -c tsconfig.build.json",
|
|
"start:debug": "NODE_OPTIONS=\"--no-deprecation\" nest start --debug --watch",
|
|
"start:prod": "node dist/main",
|
|
"lint": "eslint .",
|
|
"lint:fix": "eslint . --fix",
|
|
"test": "jest",
|
|
"test:watch": "jest --watch",
|
|
"test:cov": "jest --coverage",
|
|
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
|
"test:e2e": "jest --config ./test/jest-e2e.json",
|
|
"type-check": "tsc --noEmit",
|
|
"clean": "rm -rf dist",
|
|
"db:migrate": "prisma migrate dev",
|
|
"db:generate": "prisma generate",
|
|
"db:studio": "prisma studio",
|
|
"db:reset": "prisma migrate reset",
|
|
"db:seed": "ts-node prisma/seed.ts"
|
|
},
|
|
"dependencies": {
|
|
"@customer-portal/shared": "workspace:*",
|
|
"@nestjs/bullmq": "^11.0.3",
|
|
"@nestjs/common": "^11.1.6",
|
|
"@nestjs/config": "^4.0.2",
|
|
"@nestjs/core": "^11.1.6",
|
|
"@nestjs/jwt": "^11.0.0",
|
|
"@nestjs/passport": "^11.0.5",
|
|
"@nestjs/platform-express": "^11.1.6",
|
|
"@nestjs/swagger": "^11.2.0",
|
|
"@nestjs/throttler": "^6.4.0",
|
|
"@prisma/client": "^6.14.0",
|
|
"@types/jsonwebtoken": "^9.0.10",
|
|
"bcrypt": "^6.0.0",
|
|
"bullmq": "^5.58.0",
|
|
"class-transformer": "^0.5.1",
|
|
"class-validator": "^0.14.2",
|
|
"cookie-parser": "^1.4.7",
|
|
"helmet": "^8.1.0",
|
|
"ioredis": "^5.7.0",
|
|
"jsforce": "^3.10.4",
|
|
"jsonwebtoken": "^9.0.2",
|
|
"nestjs-pino": "^4.4.0",
|
|
"passport": "^0.7.0",
|
|
"passport-jwt": "^4.0.1",
|
|
"passport-local": "^1.0.0",
|
|
"pino": "^9.9.0",
|
|
"pino-http": "^10.5.0",
|
|
"pino-pretty": "^13.1.1",
|
|
"prisma": "^6.14.0",
|
|
"reflect-metadata": "^0.2.2",
|
|
"rxjs": "^7.8.2",
|
|
"@sendgrid/mail": "^8.1.3",
|
|
"speakeasy": "^2.0.0",
|
|
"uuid": "^11.1.0",
|
|
"zod": "^4.0.17"
|
|
},
|
|
"devDependencies": {
|
|
"@nestjs/cli": "^11.0.10",
|
|
"@nestjs/schematics": "^11.0.7",
|
|
"@nestjs/testing": "^11.1.6",
|
|
"@types/bcrypt": "^6.0.0",
|
|
"@types/cookie-parser": "^1.4.9",
|
|
"@types/express": "^5.0.3",
|
|
"@types/jest": "^30.0.0",
|
|
"@types/node": "^24.3.0",
|
|
"@types/passport-jwt": "^4.0.1",
|
|
"@types/passport-local": "^1.0.38",
|
|
"@types/speakeasy": "^2.0.10",
|
|
"@types/supertest": "^6.0.3",
|
|
"@types/uuid": "^10.0.0",
|
|
"jest": "^30.0.5",
|
|
"source-map-support": "^0.5.21",
|
|
"supertest": "^7.1.4",
|
|
"ts-jest": "^29.4.1",
|
|
"ts-node": "^10.9.2",
|
|
"tsconfig-paths": "^4.2.0",
|
|
"typescript": "^5.9.2"
|
|
}
|
|
}
|
|
```
|
|
|
|
### Backend Architecture & Dependencies
|
|
|
|
#### Core NestJS Framework
|
|
- **@nestjs/core 11.1.6**: Core framework
|
|
- **@nestjs/common 11.1.6**: Common decorators and utilities
|
|
- **@nestjs/platform-express 11.1.6**: Express platform adapter
|
|
- **@nestjs/config 4.0.2**: Configuration management
|
|
- **reflect-metadata 0.2.2**: Metadata reflection API
|
|
|
|
#### Authentication & Security
|
|
- **@nestjs/passport 11.0.5**: Passport integration
|
|
- **@nestjs/jwt 11.0.0**: JWT token handling
|
|
- **passport 0.7.0**: Authentication middleware
|
|
- **passport-jwt 4.0.1**: JWT strategy
|
|
- **passport-local 1.0.0**: Local authentication strategy
|
|
- **jsonwebtoken 9.0.2**: JWT implementation
|
|
- **bcrypt 6.0.0**: Password hashing
|
|
- **helmet 8.1.0**: Security headers
|
|
- **@nestjs/throttler 6.4.0**: Rate limiting
|
|
- **speakeasy 2.0.0**: Two-factor authentication
|
|
|
|
#### Database & ORM
|
|
- **@prisma/client 6.14.0**: Prisma ORM client
|
|
- **prisma 6.14.0**: Prisma CLI and schema management
|
|
|
|
#### Caching & Queues
|
|
- **ioredis 5.7.0**: Redis client
|
|
- **@nestjs/bullmq 11.0.3**: Queue management
|
|
- **bullmq 5.58.0**: Redis-based queue system
|
|
|
|
#### Validation & Transformation
|
|
- **class-validator 0.14.2**: Decorator-based validation
|
|
- **class-transformer 0.5.1**: Object transformation
|
|
- **zod 4.0.17**: Runtime type validation
|
|
|
|
#### External Integrations
|
|
- **jsforce 3.10.4**: Salesforce API client
|
|
- **@sendgrid/mail 8.1.3**: SendGrid email service
|
|
|
|
#### Logging & Monitoring
|
|
- **nestjs-pino 4.4.0**: NestJS Pino integration
|
|
- **pino 9.9.0**: High-performance logging
|
|
- **pino-http 10.5.0**: HTTP request logging
|
|
- **pino-pretty 13.1.1**: Pretty-printed logs for development
|
|
|
|
#### API Documentation
|
|
- **@nestjs/swagger 11.2.0**: OpenAPI/Swagger documentation
|
|
|
|
#### Utilities
|
|
- **rxjs 7.8.2**: Reactive programming
|
|
- **uuid 11.1.0**: UUID generation
|
|
- **cookie-parser 1.4.7**: Cookie parsing middleware
|
|
|
|
### TypeScript Configurations
|
|
|
|
#### Main TypeScript Config (tsconfig.json)
|
|
```json
|
|
{
|
|
"extends": "../../tsconfig.json",
|
|
"compilerOptions": {
|
|
"baseUrl": "./",
|
|
"paths": {
|
|
"@/*": ["src/*"]
|
|
},
|
|
"experimentalDecorators": true,
|
|
"emitDecoratorMetadata": true,
|
|
"allowSyntheticDefaultImports": true,
|
|
"target": "ES2021",
|
|
"lib": ["ES2021"],
|
|
"module": "commonjs",
|
|
"declaration": true,
|
|
"removeComments": true,
|
|
"incremental": true,
|
|
"skipLibCheck": true,
|
|
"strictNullChecks": false,
|
|
"noImplicitAny": false,
|
|
"strictBindCallApply": false,
|
|
"forceConsistentCasingInFileNames": false,
|
|
"noFallthroughCasesInSwitch": false
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Build TypeScript Config (tsconfig.build.json)
|
|
```json
|
|
{
|
|
"extends": "./tsconfig.json",
|
|
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
|
|
}
|
|
```
|
|
|
|
### Jest Testing Configuration
|
|
```json
|
|
{
|
|
"moduleFileExtensions": ["js", "json", "ts"],
|
|
"rootDir": "src",
|
|
"testRegex": ".*\\.spec\\.ts$",
|
|
"transform": {
|
|
"^.+\\.(t|j)s$": "ts-jest"
|
|
},
|
|
"collectCoverageFrom": [
|
|
"**/*.(t|j)s",
|
|
"!**/*.spec.ts",
|
|
"!**/node_modules/**"
|
|
],
|
|
"coverageDirectory": "../coverage",
|
|
"testEnvironment": "node",
|
|
"moduleNameMapping": {
|
|
"^@/(.*)$": "<rootDir>/$1"
|
|
},
|
|
"passWithNoTests": true
|
|
}
|
|
```
|
|
|
|
## 📦 Shared Package (packages/shared)
|
|
|
|
### Complete Package Configuration
|
|
```json
|
|
{
|
|
"name": "@customer-portal/shared",
|
|
"version": "1.0.0",
|
|
"description": "Shared utilities and types for Customer Portal",
|
|
"main": "dist/index.js",
|
|
"types": "dist/index.d.ts",
|
|
"private": true,
|
|
"scripts": {
|
|
"build": "tsc",
|
|
"dev": "tsc --watch",
|
|
"clean": "rm -rf dist",
|
|
"type-check": "tsc --noEmit"
|
|
},
|
|
"dependencies": {
|
|
"zod": "^4.0.17"
|
|
},
|
|
"devDependencies": {
|
|
"typescript": "^5.9.2",
|
|
"@types/node": "^24.3.0"
|
|
},
|
|
"exports": {
|
|
".": {
|
|
"types": "./dist/index.d.ts",
|
|
"default": "./dist/index.js"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### TypeScript Configuration (tsconfig.json)
|
|
```json
|
|
{
|
|
"extends": "../../tsconfig.json",
|
|
"compilerOptions": {
|
|
"outDir": "./dist",
|
|
"rootDir": "./src",
|
|
"declaration": true,
|
|
"declarationMap": true,
|
|
"sourceMap": true,
|
|
"composite": true,
|
|
"incremental": true
|
|
},
|
|
"include": ["src/**/*"],
|
|
"exclude": ["node_modules", "dist"]
|
|
}
|
|
```
|
|
|
|
### Package Structure & Exports
|
|
```typescript
|
|
// src/index.ts - Main export file
|
|
export * from './types';
|
|
export * from './utils';
|
|
export * from './constants';
|
|
export * from './schemas';
|
|
|
|
// src/types/index.ts - Type definitions
|
|
export interface User {
|
|
id: string;
|
|
email: string;
|
|
name: string;
|
|
role: UserRole;
|
|
}
|
|
|
|
export type UserRole = 'admin' | 'customer' | 'support';
|
|
|
|
export interface ApiResponse<T = any> {
|
|
success: boolean;
|
|
data?: T;
|
|
error?: string;
|
|
message?: string;
|
|
}
|
|
|
|
// src/utils/index.ts - Utility functions
|
|
export const formatCurrency = (amount: number, currency = 'USD') => {
|
|
return new Intl.NumberFormat('en-US', {
|
|
style: 'currency',
|
|
currency
|
|
}).format(amount);
|
|
};
|
|
|
|
export const slugify = (text: string): string => {
|
|
return text
|
|
.toLowerCase()
|
|
.replace(/[^\w\s-]/g, '')
|
|
.replace(/[\s_-]+/g, '-')
|
|
.replace(/^-+|-+$/g, '');
|
|
};
|
|
|
|
// src/constants/index.ts - Application constants
|
|
export const API_ENDPOINTS = {
|
|
AUTH: '/auth',
|
|
USERS: '/users',
|
|
PRODUCTS: '/products',
|
|
ORDERS: '/orders'
|
|
} as const;
|
|
|
|
export const USER_ROLES = {
|
|
ADMIN: 'admin',
|
|
CUSTOMER: 'customer',
|
|
SUPPORT: 'support'
|
|
} as const;
|
|
|
|
// src/schemas/index.ts - Zod validation schemas
|
|
import { z } from 'zod';
|
|
|
|
export const userSchema = z.object({
|
|
id: z.string().uuid(),
|
|
email: z.string().email(),
|
|
name: z.string().min(1),
|
|
role: z.enum(['admin', 'customer', 'support'])
|
|
});
|
|
|
|
export const loginSchema = z.object({
|
|
email: z.string().email(),
|
|
password: z.string().min(8)
|
|
});
|
|
```
|
|
|
|
### Build Output Structure
|
|
```
|
|
packages/shared/dist/
|
|
├── index.js # Main compiled entry point
|
|
├── index.d.ts # Type definitions
|
|
├── index.js.map # Source map
|
|
├── types/
|
|
│ ├── index.js
|
|
│ └── index.d.ts
|
|
├── utils/
|
|
│ ├── index.js
|
|
│ └── index.d.ts
|
|
├── constants/
|
|
│ ├── index.js
|
|
│ └── index.d.ts
|
|
└── schemas/
|
|
├── index.js
|
|
└── index.d.ts
|
|
```
|
|
|
|
## 🔨 Build Process & Dependencies
|
|
|
|
### Complete Build Order
|
|
```bash
|
|
# 1. Install all workspace dependencies
|
|
pnpm install --frozen-lockfile
|
|
|
|
# 2. Build shared package (required by other packages)
|
|
cd packages/shared
|
|
pnpm build
|
|
|
|
# 3. Generate Prisma client (backend requirement)
|
|
cd ../../apps/bff
|
|
pnpm prisma generate
|
|
|
|
# 4. Build backend application
|
|
pnpm build
|
|
|
|
# 5. Build frontend application
|
|
cd ../portal
|
|
pnpm build
|
|
```
|
|
|
|
### Production Build Requirements
|
|
|
|
#### Shared Package Build
|
|
- **Input**: TypeScript source files in `src/`
|
|
- **Output**: Compiled JavaScript + type definitions in `dist/`
|
|
- **Dependencies**: Only Zod for runtime validation
|
|
- **Build time**: ~5-10 seconds
|
|
|
|
#### Frontend Build
|
|
- **Input**: Next.js application with React components
|
|
- **Output**: Standalone server bundle + static assets
|
|
- **Dependencies**: Shared package must be built first
|
|
- **Build time**: ~30-60 seconds
|
|
- **Output size**: ~15-25MB (standalone bundle)
|
|
|
|
#### Backend Build
|
|
- **Input**: NestJS application with Prisma schema
|
|
- **Output**: Compiled Node.js application
|
|
- **Dependencies**: Shared package + generated Prisma client
|
|
- **Build time**: ~20-40 seconds
|
|
- **Critical step**: Prisma client generation before TypeScript compilation
|
|
|
|
### Native Module Compilation
|
|
These packages require native compilation and rebuilding in production:
|
|
|
|
#### bcrypt (Password Hashing)
|
|
- **Platform dependent**: Yes (native C++ bindings)
|
|
- **Rebuild required**: Yes, for target architecture
|
|
- **Build command**: `pnpm rebuild bcrypt`
|
|
|
|
#### @prisma/client (Database ORM)
|
|
- **Platform dependent**: Yes (query engine binaries)
|
|
- **Rebuild required**: Yes, includes platform-specific engines
|
|
- **Build command**: `pnpm rebuild @prisma/client @prisma/engines`
|
|
|
|
#### @prisma/engines
|
|
- **Platform dependent**: Yes (database query engines)
|
|
- **Architecture specific**: linux-musl-x64, linux-gnu-x64, etc.
|
|
- **Size**: ~50-100MB of engine binaries
|
|
|
|
### Environment-Specific Configurations
|
|
|
|
#### Development Environment
|
|
- **Hot reload**: Enabled for all packages
|
|
- **Source maps**: Generated for debugging
|
|
- **Type checking**: Strict mode enabled
|
|
- **Logging**: Pretty-printed with colors
|
|
|
|
#### Production Environment
|
|
- **Minification**: Enabled for frontend
|
|
- **Source maps**: Disabled for security
|
|
- **Console removal**: Automatic in frontend builds
|
|
- **Logging**: JSON format for structured logging
|
|
- **Health checks**: Built-in endpoints for monitoring
|
|
|
|
## 📋 Package Summary
|
|
|
|
### Dependency Overview
|
|
```
|
|
Total packages: 3 workspace packages
|
|
├── @customer-portal/shared (packages/shared)
|
|
│ ├── Dependencies: 1 (zod)
|
|
│ ├── DevDependencies: 2 (typescript, @types/node)
|
|
│ └── Build output: ~50KB
|
|
│
|
|
├── @customer-portal/portal (apps/portal)
|
|
│ ├── Dependencies: 16 (Next.js, React, UI libraries)
|
|
│ ├── DevDependencies: 5 (Tailwind, TypeScript types)
|
|
│ └── Build output: ~15-25MB (standalone)
|
|
│
|
|
└── @customer-portal/bff (apps/bff)
|
|
├── Dependencies: 25 (NestJS, Prisma, integrations)
|
|
├── DevDependencies: 13 (testing, build tools)
|
|
└── Build output: ~5-10MB
|
|
```
|
|
|
|
### Critical Build Dependencies
|
|
1. **pnpm 10.15.0**: Workspace package manager
|
|
2. **TypeScript 5.9.2**: Language compiler for all packages
|
|
3. **Node.js 22+**: Runtime environment
|
|
4. **Prisma 6.14.0**: Database client generation
|
|
5. **Next.js 15.5.0**: Frontend framework with standalone output
|
|
|
|
### Package Interdependencies
|
|
```
|
|
Build Order (Critical):
|
|
1. packages/shared → Compiles TypeScript to JavaScript
|
|
2. apps/bff → Generates Prisma client, then compiles NestJS
|
|
3. apps/portal → Compiles Next.js with shared package dependency
|
|
|
|
Runtime Dependencies:
|
|
- Frontend depends on shared package types and utilities
|
|
- Backend depends on shared package + generated Prisma client
|
|
- Both applications require external services (PostgreSQL, Redis)
|
|
```
|
|
|
|
### Key Configuration Files
|
|
- **pnpm-workspace.yaml**: Defines monorepo structure
|
|
- **Root package.json**: Workspace scripts and shared devDependencies
|
|
- **tsconfig.json**: TypeScript configuration inheritance
|
|
- **next.config.mjs**: Next.js production optimizations
|
|
- **prisma/schema.prisma**: Database schema and client generation
|
|
|
|
This architecture provides a scalable, type-safe monorepo with shared utilities, modern frontend framework, robust backend API, and comprehensive external service integrations.
|