Assist_Design/PLESK_DEPLOYMENT.md
T. Narantuya a95ec60859 Refactor address management and update related services for improved clarity and functionality
- Updated address retrieval in user service to replace billing info with a dedicated address method.
- Adjusted API endpoints to use `PATCH /api/me/address` for address updates instead of billing updates.
- Enhanced documentation to reflect changes in address management processes and API usage.
- Removed deprecated types and services related to billing address handling, streamlining the codebase.
2025-09-17 18:43:43 +09:00

27 KiB

📦 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
  • 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):

# 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:

# 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
  • /apiportal-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:

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):

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)

{
  "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/domain build",
    "dev": "./scripts/dev/manage.sh apps",
    "dev:all": "pnpm --parallel --filter @customer-portal/domain --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/domain 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)

packages:
  - "apps/*" # Frontend and Backend applications
  - "packages/*" # Shared libraries and utilities

Package Dependency Graph

@customer-portal/domain (packages/shared)
├── Built first (no dependencies)
└── Exports: types, utils, constants, schemas

@customer-portal/portal (apps/portal)
├── Depends on: @customer-portal/domain
└── Builds: Next.js standalone application

@customer-portal/bff (apps/bff)
├── Depends on: @customer-portal/domain
├── Generates: Prisma client
└── Builds: NestJS application

🎯 Frontend Application (apps/portal)

Complete Package Configuration

{
  "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/domain": "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)

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)

{
  "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

{
  "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/domain": "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)

{
  "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)

{
  "extends": "./tsconfig.json",
  "exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
}

Jest Testing Configuration

{
  "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

{
  "name": "@customer-portal/domain",
  "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)

{
  "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

// 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

# 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/domain (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.