Update pnpm-lock.yaml, Dockerfile, and environment configurations

- Added nestjs-pino dependency to pnpm-lock.yaml for improved logging capabilities.
- Updated Dockerfile to include a custom entrypoint script for better container management.
- Modified health controllers to include @Public() decorator for public access.
- Cleaned up environment variable samples for clarity and added new variables for Freebit integration.
- Adjusted Content Security Policy in next.config.mjs to allow inline scripts/styles for Next.js compatibility.
- Refactored ReissueSimModal to specify simType during eSIM reissue requests.
This commit is contained in:
barsa 2025-12-01 15:30:04 +09:00
parent 1dafa7334a
commit f4d4cb0ab0
16 changed files with 377 additions and 124 deletions

View File

@ -111,6 +111,10 @@ RUN pnpm dlx prisma@6.14.0 generate
# Strip build toolchain to shrink image
RUN apk del --no-cache python3 make g++ pkgconfig && rm -rf /root/.cache /var/cache/apk/*
# Copy entrypoint script
COPY apps/bff/scripts/docker-entrypoint.sh /app/docker-entrypoint.sh
RUN chmod +x /app/docker-entrypoint.sh
# Create non-root user for security [[memory:6689308]]
RUN addgroup --system --gid 1001 nodejs && \
adduser --system --uid 1001 nestjs
@ -122,7 +126,7 @@ RUN mkdir -p /app/secrets /app/logs && \
# Switch to non-root user
USER nestjs
# Expose port (required for Plesk port mapping)
# Expose port
EXPOSE 4000
# Environment variables
@ -136,6 +140,6 @@ WORKDIR /app/apps/bff
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:4000/health || exit 1
# Use dumb-init for proper signal handling in containers
ENTRYPOINT ["dumb-init", "--"]
# Use dumb-init for proper signal handling, then entrypoint script
ENTRYPOINT ["dumb-init", "--", "/app/docker-entrypoint.sh"]
CMD ["node", "dist/main.js"]

View File

@ -0,0 +1,35 @@
#!/bin/sh
set -e
# =============================================================================
# Docker Entrypoint Script
# =============================================================================
# Handles runtime setup before starting the application:
# - Decodes SF_PRIVATE_KEY_BASE64 to file if provided
# - Runs Prisma migrations if DATABASE_URL is set
# =============================================================================
echo "🚀 Starting Customer Portal Backend..."
# Handle Salesforce private key from base64 environment variable
if [ -n "$SF_PRIVATE_KEY_BASE64" ]; then
echo "📝 Decoding Salesforce private key..."
mkdir -p /app/secrets
echo "$SF_PRIVATE_KEY_BASE64" | base64 -d > /app/secrets/sf-private.key
chmod 600 /app/secrets/sf-private.key
export SF_PRIVATE_KEY_PATH=/app/secrets/sf-private.key
echo "✅ Salesforce private key configured"
fi
# Run database migrations if enabled
if [ "$RUN_MIGRATIONS" = "true" ] && [ -n "$DATABASE_URL" ]; then
echo "🗄️ Running database migrations..."
npx prisma migrate deploy --schema=/app/apps/bff/prisma/schema.prisma
echo "✅ Migrations complete"
fi
echo "🌐 Starting server on port ${PORT:-4000}..."
# Execute the main command (node dist/main.js)
exec "$@"

View File

@ -53,17 +53,14 @@ export const envSchema = z.object({
WHMCS_BASE_URL: z.string().url().optional(),
WHMCS_API_IDENTIFIER: z.string().optional(),
WHMCS_API_SECRET: z.string().optional(),
WHMCS_WEBHOOK_SECRET: z.string().optional(),
WHMCS_DEV_BASE_URL: z.string().url().optional(),
WHMCS_DEV_API_IDENTIFIER: z.string().optional(),
WHMCS_DEV_API_SECRET: z.string().optional(),
WHMCS_DEV_WEBHOOK_SECRET: z.string().optional(),
SF_LOGIN_URL: z.string().url().optional(),
SF_USERNAME: z.string().optional(),
SF_CLIENT_ID: z.string().optional(),
SF_PRIVATE_KEY_PATH: z.string().optional(),
SF_WEBHOOK_SECRET: z.string().optional(),
SF_AUTH_TIMEOUT_MS: z.coerce.number().int().positive().default(30000),
SF_TOKEN_TTL_MS: z.coerce.number().int().positive().default(720000),
SF_TOKEN_REFRESH_BUFFER_MS: z.coerce.number().int().positive().default(60000),

View File

@ -1,8 +1,10 @@
import { Controller, Get } from "@nestjs/common";
import { WhmcsRequestQueueService } from "@bff/core/queue/services/whmcs-request-queue.service";
import { SalesforceRequestQueueService } from "@bff/core/queue/services/salesforce-request-queue.service";
import { Public } from "@bff/modules/auth/decorators/public.decorator";
@Controller("health/queues")
@Public()
export class QueueHealthController {
constructor(
private readonly whmcsQueue: WhmcsRequestQueueService,

View File

@ -1,5 +1,6 @@
import { Controller, Get } from "@nestjs/common";
import { CatalogCacheService, CatalogCacheSnapshot } from "./services/catalog-cache.service";
import { Public } from "@bff/modules/auth/decorators/public.decorator";
interface CatalogCacheHealthResponse {
timestamp: string;
@ -13,6 +14,7 @@ interface CatalogCacheHealthResponse {
}
@Controller("health/catalog")
@Public()
export class CatalogHealthController {
constructor(private readonly catalogCache: CatalogCacheService) {}

View File

@ -2,8 +2,10 @@ import { Controller, Get, Inject } from "@nestjs/common";
import { Logger } from "nestjs-pino";
import { PrismaService } from "@bff/infra/database/prisma.service";
import { CacheService } from "@bff/infra/cache/cache.service";
import { Public } from "@bff/modules/auth/decorators/public.decorator";
@Controller("health")
@Public()
export class HealthController {
constructor(
private readonly prisma: PrismaService,

View File

@ -77,21 +77,21 @@ const nextConfig = {
key: "X-XSS-Protection",
value: "1; mode=block",
},
// Content Security Policy - development-friendly
// Content Security Policy - allows Next.js inline scripts/styles
{
key: "Content-Security-Policy",
value:
process.env.NODE_ENV === "development"
? "default-src 'self'; script-src 'self' 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https: http://localhost:* ws://localhost:*; frame-ancestors 'none';"
: [
"default-src 'self'",
"script-src 'self'",
"style-src 'self'",
"img-src 'self' data: https:",
"font-src 'self' data:",
"connect-src 'self'",
"frame-ancestors 'none'",
].join("; "),
value: [
"default-src 'self'",
// Next.js requires unsafe-inline for hydration scripts
"script-src 'self' 'unsafe-inline' 'unsafe-eval'",
// Next.js/Tailwind requires unsafe-inline for styles
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: https:",
"font-src 'self' data:",
// Allow API connections
"connect-src 'self' https:",
"frame-ancestors 'none'",
].join("; "),
},
],
},

View File

@ -68,7 +68,8 @@ export function ReissueSimModal({
setValidationError(null);
setSubmitting(true);
try {
await simActionsService.reissueEsim(String(subscriptionId), {
await simActionsService.reissueSim(String(subscriptionId), {
simType: "esim",
newEid: newEid.trim() || undefined,
});
onSuccess();

138
env/dev.env.sample vendored Normal file
View File

@ -0,0 +1,138 @@
# =============================================================================
# Customer Portal - Development Environment
# =============================================================================
# Copy to .env in project root for local development
# This file configures both frontend and backend for dev mode
# =============================================================================
# -----------------------------------------------------------------------------
# Core
# -----------------------------------------------------------------------------
NODE_ENV=development
APP_NAME=customer-portal-bff
APP_BASE_URL=http://localhost:3000
# Ports
BFF_PORT=4000
NEXT_PORT=3000
# -----------------------------------------------------------------------------
# Frontend (Next.js) - Browser-exposed variables
# -----------------------------------------------------------------------------
NEXT_PUBLIC_APP_NAME="Customer Portal (Dev)"
NEXT_PUBLIC_APP_VERSION=1.0.0-dev
NEXT_PUBLIC_API_BASE=http://localhost:4000
NEXT_PUBLIC_ENABLE_DEVTOOLS=true
# -----------------------------------------------------------------------------
# Database & Cache
# -----------------------------------------------------------------------------
DATABASE_URL=postgresql://dev:dev@localhost:5432/portal_dev?schema=public
REDIS_URL=redis://localhost:6379
# -----------------------------------------------------------------------------
# Security
# -----------------------------------------------------------------------------
# Generate with: openssl rand -base64 32
JWT_SECRET=HjHsUyTE3WhPn5N07iSvurdV4hk2VEkIuN+lIflHhVQ=
JWT_EXPIRES_IN=7d
BCRYPT_ROUNDS=12
CORS_ORIGIN=http://localhost:3000
TRUST_PROXY=false
# Redis token handling (relaxed for dev)
AUTH_ALLOW_REDIS_TOKEN_FAILOPEN=false
AUTH_REQUIRE_REDIS_FOR_TOKENS=false
AUTH_MAINTENANCE_MODE=false
# -----------------------------------------------------------------------------
# Rate Limiting (relaxed for dev)
# -----------------------------------------------------------------------------
RATE_LIMIT_TTL=60
RATE_LIMIT_LIMIT=1000
AUTH_RATE_LIMIT_TTL=900
AUTH_RATE_LIMIT_LIMIT=10
# Show detailed validation errors in dev
EXPOSE_VALIDATION_ERRORS=true
# -----------------------------------------------------------------------------
# WHMCS Integration (dev credentials)
# -----------------------------------------------------------------------------
WHMCS_DEV_BASE_URL=
WHMCS_DEV_API_IDENTIFIER=
WHMCS_DEV_API_SECRET=
WHMCS_QUEUE_CONCURRENCY=15
WHMCS_QUEUE_TIMEOUT_MS=30000
# -----------------------------------------------------------------------------
# Salesforce Integration
# -----------------------------------------------------------------------------
SF_LOGIN_URL=
SF_CLIENT_ID=
SF_PRIVATE_KEY_PATH=./secrets/sf-private.key
SF_USERNAME=
# Timeouts
SF_AUTH_TIMEOUT_MS=30000
SF_TOKEN_TTL_MS=720000
SF_TOKEN_REFRESH_BUFFER_MS=60000
# Queue throttling
SF_QUEUE_CONCURRENCY=15
SF_QUEUE_TIMEOUT_MS=30000
SF_QUEUE_LONG_RUNNING_TIMEOUT_MS=600000
# Platform Events
SF_EVENTS_ENABLED=true
SF_EVENTS_REPLAY=LATEST
SF_PUBSUB_ENDPOINT=api.pubsub.salesforce.com:7443
# -----------------------------------------------------------------------------
# Freebit SIM Management
# -----------------------------------------------------------------------------
FREEBIT_BASE_URL=
FREEBIT_OEM_ID=
FREEBIT_OEM_KEY=
FREEBIT_TIMEOUT=30000
# -----------------------------------------------------------------------------
# Email
# -----------------------------------------------------------------------------
EMAIL_ENABLED=true
EMAIL_USE_QUEUE=true
EMAIL_FROM=no-reply@asolutions.co.jp
EMAIL_FROM_NAME=Assist Solutions
# -----------------------------------------------------------------------------
# Portal Configuration
# -----------------------------------------------------------------------------
PORTAL_PRICEBOOK_ID=01sTL000008eLVlYAM
PORTAL_PRICEBOOK_NAME=Portal
# -----------------------------------------------------------------------------
# Logging
# -----------------------------------------------------------------------------
LOG_LEVEL=debug
DISABLE_HTTP_LOGGING=false
# -----------------------------------------------------------------------------
# Local Dev Bypasses (NEVER enable in production!)
# -----------------------------------------------------------------------------
DISABLE_CSRF=false
DISABLE_RATE_LIMIT=false
DISABLE_ACCOUNT_LOCKING=false

View File

@ -1,144 +1,190 @@
# =============================================================================
# Customer Portal Backend (BFF) - Production Environment
# =============================================================================
# Copy to portal-backend.env and configure values marked with CHANGE_ME
# Variables with defaults can be omitted unless you need to override them
# =============================================================================
# -----------------------------------------------------------------------------
# REQUIRED - Must be configured
# -----------------------------------------------------------------------------
NODE_ENV=production
# App
APP_BASE_URL=https://asolutions.jp
BFF_PORT=4000
APP_NAME=customer-portal-bff
# Database (PostgreSQL)
POSTGRES_DB=portal_prod
POSTGRES_USER=portal
POSTGRES_PASSWORD=CHANGE_ME
DATABASE_URL=postgresql://portal:CHANGE_ME@database:5432/portal_prod?schema=public
JWT_SECRET=CHANGE_ME_GENERATE_WITH_openssl_rand_base64_32
# Cache (Redis)
REDIS_URL=redis://cache:6379/0
AUTH_ALLOW_REDIS_TOKEN_FAILOPEN=false
# Redis-required token flow (when enabled, tokens require Redis to be available)
AUTH_REQUIRE_REDIS_FOR_TOKENS=false
# Maintenance mode for authentication service
AUTH_MAINTENANCE_MODE=false
AUTH_MAINTENANCE_MESSAGE=Authentication service is temporarily unavailable for maintenance. Please try again later.
# -----------------------------------------------------------------------------
# Core Application
# -----------------------------------------------------------------------------
APP_NAME=customer-portal-bff
APP_BASE_URL=https://your-domain.com
BFF_PORT=4000
# -----------------------------------------------------------------------------
# Security
JWT_SECRET=CHANGE_ME
# -----------------------------------------------------------------------------
# Redis cache (required for production token management)
REDIS_URL=redis://cache:6379/0
# JWT configuration
JWT_EXPIRES_IN=7d
BCRYPT_ROUNDS=12
# CSRF Protection
CSRF_TOKEN_EXPIRY=3600000
CSRF_SECRET_KEY=CHANGE_ME_AT_LEAST_32_CHARACTERS_LONG
CSRF_COOKIE_NAME=csrf-secret
CSRF_HEADER_NAME=X-CSRF-Token
# CORS / Proxy
CORS_ORIGIN=https://asolutions.jp
# CORS - set to your frontend domain
CORS_ORIGIN=https://your-domain.com
TRUST_PROXY=true
# Rate Limiting (optional; defaults shown - ttl values in seconds)
# CSRF Protection (generate secret: openssl rand -base64 32)
CSRF_SECRET_KEY=CHANGE_ME_GENERATE_WITH_openssl_rand_base64_32
# Redis token handling
AUTH_ALLOW_REDIS_TOKEN_FAILOPEN=false
AUTH_REQUIRE_REDIS_FOR_TOKENS=false
# Maintenance mode (enable during deployments)
AUTH_MAINTENANCE_MODE=false
# -----------------------------------------------------------------------------
# Rate Limiting
# -----------------------------------------------------------------------------
RATE_LIMIT_TTL=60
RATE_LIMIT_LIMIT=100
AUTH_RATE_LIMIT_TTL=900
AUTH_RATE_LIMIT_LIMIT=3
AUTH_REFRESH_RATE_LIMIT_TTL=300
AUTH_REFRESH_RATE_LIMIT_LIMIT=10
LOGIN_RATE_LIMIT_TTL=900
LOGIN_RATE_LIMIT_LIMIT=5
LOGIN_CAPTCHA_AFTER_ATTEMPTS=3
SIGNUP_RATE_LIMIT_TTL=900
SIGNUP_RATE_LIMIT_LIMIT=5
PASSWORD_RESET_RATE_LIMIT_TTL=900
PASSWORD_RESET_RATE_LIMIT_LIMIT=5
# CAPTCHA Configuration
# CAPTCHA (optional - set provider to 'turnstile' or 'hcaptcha' to enable)
AUTH_CAPTCHA_PROVIDER=none
AUTH_CAPTCHA_SECRET=
AUTH_CAPTCHA_THRESHOLD=0
AUTH_CAPTCHA_ALWAYS_ON=false
# Validation error visibility (set true to show field-level errors to clients)
# Hide validation errors from clients in production
EXPOSE_VALIDATION_ERRORS=false
# WHMCS Credentials
# -----------------------------------------------------------------------------
# WHMCS Integration
# -----------------------------------------------------------------------------
WHMCS_BASE_URL=https://accounts.asolutions.co.jp
WHMCS_API_IDENTIFIER=
WHMCS_API_SECRET=
# Optional webhook security for WHMCS webhooks
WHMCS_WEBHOOK_SECRET=
# Salesforce Credentials
# Queue throttling
WHMCS_QUEUE_CONCURRENCY=15
WHMCS_QUEUE_TIMEOUT_MS=30000
# -----------------------------------------------------------------------------
# Salesforce Integration
# -----------------------------------------------------------------------------
SF_LOGIN_URL=https://asolutions.my.salesforce.com
SF_CLIENT_ID=
SF_PRIVATE_KEY_PATH=/app/secrets/sf-private.key
SF_USERNAME=
SF_PRIVATE_KEY_PATH=/app/secrets/sf-private.key
SF_WEBHOOK_SECRET=
# Salesforce Authentication Timeouts (in milliseconds)
SF_AUTH_TIMEOUT_MS=30000
SF_TOKEN_TTL_MS=720000
SF_TOKEN_REFRESH_BUFFER_MS=60000
# Queue Throttling Configuration
WHMCS_QUEUE_CONCURRENCY=15
WHMCS_QUEUE_INTERVAL_CAP=300
WHMCS_QUEUE_TIMEOUT_MS=30000
# Queue throttling
SF_QUEUE_CONCURRENCY=15
SF_QUEUE_LONG_RUNNING_CONCURRENCY=22
SF_QUEUE_INTERVAL_CAP=600
SF_QUEUE_TIMEOUT_MS=30000
SF_QUEUE_LONG_RUNNING_TIMEOUT_MS=600000
# Salesforce Platform Events (Provisioning)
# Platform Events
SF_EVENTS_ENABLED=true
SF_CATALOG_EVENT_CHANNEL=/event/Product_and_Pricebook_Change__e
SF_ACCOUNT_EVENT_CHANNEL=/event/Account_Internet_Eligibility_Update__e
SF_EVENTS_REPLAY=LATEST
SF_PUBSUB_NUM_REQUESTED=25
SF_PUBSUB_QUEUE_MAX=100
SF_PUBSUB_ENDPOINT=api.pubsub.salesforce.com:7443
# Salesforce Change Data Capture (CDC) for Catalog Cache Invalidation
# These use /data/ prefix for built-in CDC events (no setup needed in Salesforce)
SF_CATALOG_PRODUCT_CDC_CHANNEL=/data/Product2ChangeEvent
SF_CATALOG_PRICEBOOKENTRY_CDC_CHANNEL=/data/PricebookEntryChangeEvent
# Optional: Platform Event for account eligibility updates (requires Salesforce setup)
SF_ACCOUNT_ELIGIBILITY_CHANNEL=/event/Account_Internet_Eligibility_Update__e
# -----------------------------------------------------------------------------
# Freebit SIM Management
# -----------------------------------------------------------------------------
# Salesforce Change Data Capture (CDC) for Order Cache Invalidation
# These use /data/ prefix for built-in CDC events (no setup needed in Salesforce)
# Smart filtering: Only invalidates cache for customer-facing field changes, NOT internal fulfillment fields
SF_ORDER_CDC_CHANNEL=/data/OrderChangeEvent
SF_ORDER_ITEM_CDC_CHANNEL=/data/OrderItemChangeEvent
# Salesforce Pricing
PORTAL_PRICEBOOK_ID=
# Logging
LOG_LEVEL=info
LOG_FORMAT=json
# Email (SendGrid)
SENDGRID_API_KEY=
EMAIL_FROM=no-reply@asolutions.jp
EMAIL_FROM_NAME=Assist Solutions
EMAIL_ENABLED=true
EMAIL_USE_QUEUE=true
SENDGRID_SANDBOX=false
EMAIL_TEMPLATE_RESET=
EMAIL_TEMPLATE_WELCOME=
# Freebit (SIM management; optional)
FREEBIT_BASE_URL=https://i1.mvno.net/emptool/api
FREEBIT_OEM_ID=PASI
FREEBIT_OEM_KEY=
FREEBIT_TIMEOUT=30000
FREEBIT_RETRY_ATTEMPTS=3
FREEBIT_DETAILS_ENDPOINT=/master/getAcnt/
# Node Options
# -----------------------------------------------------------------------------
# Email (SendGrid)
# -----------------------------------------------------------------------------
EMAIL_ENABLED=true
EMAIL_FROM=no-reply@asolutions.jp
EMAIL_FROM_NAME=Assist Solutions
SENDGRID_API_KEY=
SENDGRID_SANDBOX=false
# -----------------------------------------------------------------------------
# Portal Configuration
# -----------------------------------------------------------------------------
PORTAL_PRICEBOOK_ID=
PORTAL_PRICEBOOK_NAME=Portal
# -----------------------------------------------------------------------------
# Logging
# -----------------------------------------------------------------------------
LOG_LEVEL=info
# -----------------------------------------------------------------------------
# Node Runtime
# -----------------------------------------------------------------------------
NODE_OPTIONS=--max-old-space-size=512
# NOTE: Frontend (Next.js) uses a separate env file (portal-frontend.env)
# Do not include NEXT_PUBLIC_* variables here.
# Salesforce Account Portal Flags
ACCOUNT_PORTAL_STATUS_FIELD=Portal_Status__c
ACCOUNT_PORTAL_STATUS_SOURCE_FIELD=Portal_Registration_Source__c
ACCOUNT_PORTAL_LAST_SIGNED_IN_FIELD=Portal_Last_SignIn__c
# =============================================================================
# ADVANCED CONFIGURATION (rarely need to change)
# =============================================================================
# The following variables have sensible defaults and only need to be set
# if your Salesforce org uses non-standard field API names.
# Uncomment and modify only if needed.
# =============================================================================
# --- Salesforce Field Mappings - Account ---
# ACCOUNT_INTERNET_ELIGIBILITY_FIELD=Internet_Eligibility__c
# ACCOUNT_CUSTOMER_NUMBER_FIELD=SF_Account_No__c
# ACCOUNT_PORTAL_STATUS_FIELD=Portal_Status__c
# ACCOUNT_PORTAL_STATUS_SOURCE_FIELD=Portal_Registration_Source__c
# ACCOUNT_PORTAL_LAST_SIGNED_IN_FIELD=Portal_Last_SignIn__c
# --- Salesforce Field Mappings - Product ---
# PRODUCT_SKU_FIELD=StockKeepingUnit
# PRODUCT_PORTAL_CATEGORY_FIELD=Product2Categories1__c
# PRODUCT_PORTAL_CATALOG_FIELD=Portal_Catalog__c
# PRODUCT_PORTAL_ACCESSIBLE_FIELD=Portal_Accessible__c
# PRODUCT_ITEM_CLASS_FIELD=Item_Class__c
# PRODUCT_BILLING_CYCLE_FIELD=Billing_Cycle__c
# PRODUCT_WHMCS_PRODUCT_ID_FIELD=WH_Product_ID__c
# PRODUCT_WHMCS_PRODUCT_NAME_FIELD=WH_Product_Name__c
# PRODUCT_INTERNET_PLAN_TIER_FIELD=Internet_Plan_Tier__c
# PRODUCT_INTERNET_OFFERING_TYPE_FIELD=Internet_Offering_Type__c
# PRODUCT_DISPLAY_ORDER_FIELD=Catalog_Order__c
# PRODUCT_BUNDLED_ADDON_FIELD=Bundled_Addon__c
# PRODUCT_IS_BUNDLED_ADDON_FIELD=Is_Bundled_Addon__c
# PRODUCT_SIM_DATA_SIZE_FIELD=SIM_Data_Size__c
# PRODUCT_SIM_PLAN_TYPE_FIELD=SIM_Plan_Type__c
# PRODUCT_SIM_HAS_FAMILY_DISCOUNT_FIELD=SIM_Has_Family_Discount__c
# PRODUCT_VPN_REGION_FIELD=VPN_Region__c
# --- Salesforce Field Mappings - Order ---
# ORDER_TYPE_FIELD=Type
# ORDER_ACTIVATION_TYPE_FIELD=Activation_Type__c
# ORDER_ACTIVATION_SCHEDULED_AT_FIELD=Activation_Scheduled_At__c
# ORDER_ACTIVATION_STATUS_FIELD=Activation_Status__c
# ORDER_SIM_TYPE_FIELD=SIM_Type__c
# ORDER_EID_FIELD=EID__c
# ORDER_MNP_APPLICATION_FIELD=MNP_Application__c
# ORDER_MNP_RESERVATION_FIELD=MNP_Reservation_Number__c
# ORDER_MNP_EXPIRY_FIELD=MNP_Expiry_Date__c
# ORDER_MNP_PHONE_FIELD=MNP_Phone_Number__c
# ORDER_WHMCS_ORDER_ID_FIELD=WHMCS_Order_ID__c
# --- Salesforce CDC Channels (Change Data Capture) ---
# SF_CATALOG_PRODUCT_CDC_CHANNEL=/data/Product2ChangeEvent
# SF_CATALOG_PRICEBOOKENTRY_CDC_CHANNEL=/data/PricebookEntryChangeEvent
# SF_ORDER_CDC_CHANNEL=/data/OrderChangeEvent
# SF_ORDER_ITEM_CDC_CHANNEL=/data/OrderItemChangeEvent

View File

@ -1,8 +1,18 @@
# =============================================================================
# Customer Portal Frontend (Next.js) - Production Environment
# =============================================================================
# Copy to portal-frontend.env
# Note: NEXT_PUBLIC_* variables are embedded at build time
# =============================================================================
NODE_ENV=production
# Frontend (Next.js) — public runtime vars only
# Application name shown in UI
NEXT_PUBLIC_APP_NAME=Assist Solutions Portal
NEXT_PUBLIC_APP_VERSION=1.0.0
# Same-origin API behind Plesk proxy rule
# API endpoint - use /api for same-origin requests behind reverse proxy
NEXT_PUBLIC_API_BASE=/api
# Disable React Query devtools in production
NEXT_PUBLIC_ENABLE_DEVTOOLS=false

View File

@ -35,6 +35,7 @@
"dependencies": {
"@customer-portal/domain": "workspace:*",
"@nestjs/common": "^11.1.6",
"nestjs-pino": "^4.4.0",
"nestjs-zod": "^5.0.1",
"zod": "^4.1.9"
},
@ -58,7 +59,6 @@
"jest": "^30.0.5",
"@types/jest": "^30.0.0",
"nestjs-zod": "^5.0.1",
"nestjs-pino": "^4.4.0",
"express": "^5.1.0",
"@types/express": "^5.0.3"
}

6
pnpm-lock.yaml generated
View File

@ -382,6 +382,9 @@ importers:
'@nestjs/common':
specifier: ^11.1.6
version: 11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)
nestjs-pino:
specifier: ^4.4.0
version: 4.4.0(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(pino-http@10.5.0)(pino@9.9.5)(rxjs@7.8.2)
nestjs-zod:
specifier: ^5.0.1
version: 5.0.1(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/swagger@11.2.0(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.6)(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2))(rxjs@7.8.2)(zod@4.1.9)
@ -404,9 +407,6 @@ importers:
jest:
specifier: ^30.0.5
version: 30.1.3(@types/node@24.3.1)(ts-node@10.9.2(@types/node@24.3.1)(typescript@5.9.2))
nestjs-pino:
specifier: ^4.4.0
version: 4.4.0(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(pino-http@10.5.0)(pino@9.9.5)(rxjs@7.8.2)
react:
specifier: 19.1.1
version: 19.1.1

View File

@ -0,0 +1 @@
d56f8408ed1de76e225abd6a8ddb741c32f96102f03b0caf8fef089a30de317b /home/barsa/projects/customer_portal/customer-portal/portal-backend.20251201-1dafa73.tar

View File

@ -0,0 +1 @@
4510c9159622868d3cbbf8212274e08bb374e541876406ba7d0f2d7d4d93983a /home/barsa/projects/customer_portal/customer-portal/portal-frontend.20251201-1dafa73.tar

View File

@ -116,11 +116,17 @@ if [[ "${SAVE_TARS}" -eq 1 ]]; then
docker save -o "$FRONT_TAR_TAGGED" "${IMAGE_FRONTEND_NAME}:${IMAGE_TAG}"
docker save -o "$BACK_TAR_TAGGED" "${IMAGE_BACKEND_NAME}:${IMAGE_TAG}"
log "🔐 Generating checksums for integrity verification..."
sha256sum "$FRONT_TAR_TAGGED" > "${FRONT_TAR_TAGGED}.sha256"
sha256sum "$BACK_TAR_TAGGED" > "${BACK_TAR_TAGGED}.sha256"
log "✅ Wrote:"
echo " - $FRONT_TAR_LATEST"
echo " - $BACK_TAR_LATEST"
echo " - $FRONT_TAR_TAGGED"
echo " - $BACK_TAR_TAGGED"
echo " - ${FRONT_TAR_TAGGED}.sha256"
echo " - ${BACK_TAR_TAGGED}.sha256"
fi
if [[ -n "${PUSH_REMOTE}" ]]; then
@ -142,5 +148,13 @@ if [[ -n "${PUSH_REMOTE}" ]]; then
docker push "$BE_REMOTE_TAGGED"
fi
log "🎉 Done. Use config/docker/compose-plesk.yaml in Plesk and upload the .tar files."
log "🎉 Done!"
log ""
log "Next steps:"
log " 1. Upload .tar files to your Plesk server"
log " 2. Load images: docker load -i portal-frontend.${IMAGE_TAG}.tar"
log " 3. Verify checksums: sha256sum -c portal-frontend.${IMAGE_TAG}.tar.sha256"
log " 4. Update Portainer stack with new image tag: ${IMAGE_TAG}"
log ""
log "See docker/portainer/PORTAINER-GUIDE.md for detailed instructions."