Assist_Design/PLESK_DEPLOYMENT.md

922 lines
27 KiB
Markdown
Raw Normal View History

# 📦 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.