architecture/architecture.md

Technical Architecture

Tech Stack & Pinned Versions

Layer Technology Version Lý do / Notes
Node.js LTS 22.22.2 .nvmrc tại root, nvm use để switch
Backend NestJS (TypeScript) 11.0.1 Modular architecture khớp với industry plugin pattern, built-in DI
Language TypeScript 5.7.x Theo NestJS 11
Database PostgreSQL 16 (Alpine) JSONB cho metadata, RLS cho multi-tenant (deferred Phase 8+), ACID. Docker postgres:16-alpine
ORM Prisma 7.6.0 Type-safe; v7 dùng prisma-client-js generator + @prisma/adapter-pg bắt buộc, datasource URL config trong prisma.config.ts
Cache / Realtime Redis 7 (Alpine) Session, real-time state, pub/sub. Docker redis:7-alpine
Queue BullMQ latest Notification async, scheduled jobs (reminders, payment-expiry sweep, outbox publisher)
Customer Portal Next.js 16.x SSR cho SEO (subdomain booking page), fast load
Admin Portal Next.js 16.x Web-only theo brief, share codebase với customer portal
Owner App React Native (Expo) SDK 54 Cross-platform; React Native 0.81.5, React 19.1
Staff App React Native (Expo) SDK 54 Cùng codebase với Owner App, phân quyền bằng role
Realtime Socket.io (NestJS Gateway) Live booking updates, staff notifications
File Storage S3-compatible (MinIO) latest Avatar, salon branding assets, self-hosted
Payment Provider Bambora Classic MVP. Worldline Direct giữ trong providers/worldline-direct/ cho enterprise migration. Stripe / Vipps MobilePay / Nets / Adyen — roadmap post-MVP, drop-in qua PaymentProviderPort
Package Manager Yarn 1.22.x (Classic) KHÔNG dùng npm/npx

Prisma v7 — Breaking changes

  • Generator provider: prisma-client-js (CJS compatible)
  • Import: from '@prisma/client' (cách truyền thống)
  • Adapter bắt buộc: @prisma/adapter-pg — truyền vào PrismaClient({ adapter })
  • url không nằm trong datasource block — config trong prisma.config.ts qua defineConfig + env()
  • Generator prisma-client (mới) output ESM .ts files — KHÔNG compatible NestJS CJS, tránh dùng

Docker Compose ports

Service Host Port Container Port
PostgreSQL 5442 5432
Redis 6389 6379
MinIO API 9010 9000
MinIO Console 9011 9001
NestJS API 3010 3010

System Architecture

flowchart TB
    subgraph Edge [Edge layer]
        LB[Nginx / Caddy<br/>SSL + subdomain routing]
    end
    subgraph App [Application tier — PM2 process manager]
        Customer[Next.js Customer Portal<br/>'slug'.app.no]
        Admin[Next.js Admin Portal<br/>admin.app.no]
        API[NestJS API<br/>api.app.no — REST + WS]
        Docs[Docs static site<br/>documents.app.no]
    end
    subgraph Data [Data tier — internal only]
        PG[(PostgreSQL<br/>Tenants / Resources / Bookings / Customers / Payments)]
        Redis[(Redis<br/>Session / Cache / Pub-Sub / BullMQ)]
        MinIO[(MinIO<br/>Assets / Uploads)]
    end
    subgraph Mobile [React Native — Expo]
        Owner[Owner App<br/>iOS + Android]
        Staff[Staff App<br/>iOS + Android]
    end
    subgraph External [External]
        Bambora{{Bambora Classic<br/>checkout + webhook}}
    end

    LB --> Customer
    LB --> Admin
    LB --> API
    LB --> Docs
    API --> PG
    API --> Redis
    API --> MinIO
    Owner -->|REST + WebSocket| API
    Staff -->|REST + WebSocket| API
    API <--> Bambora

Repository Structure

Multi-repo với shared AI context. Mỗi app là git repo riêng, nằm trong 1 workspace folder.

booking/                            # Workspace folder (không phải git repo)
├── booking-api/                    # Git repo — NestJS backend
│   ├── src/
│   │   ├── core/                   # Core booking engine (Resource-agnostic)
│   │   │   ├── booking/
│   │   │   ├── resource/
│   │   │   ├── service/
│   │   │   ├── customer/
│   │   │   ├── notification/
│   │   │   ├── payment/
│   │   │   └── tenant/
│   │   ├── industries/             # Industry plugins
│   │   │   ├── beauty/             # MVP: Staff, beauty-specific logic
│   │   │   └── restaurant/         # Future: table booking
│   │   ├── admin/                  # Admin-only endpoints
│   │   ├── auth/                   # Authentication & authorization
│   │   └── common/                 # Guards, interceptors, filters, pipes
│   ├── prisma/
│   │   ├── schema.prisma
│   │   └── migrations/
│   ├── .env
│   └── package.json
│
├── booking-web/                    # Git repo — Next.js
│   ├── src/
│   │   ├── app/
│   │   │   ├── (customer)/         # Customer-facing booking pages
│   │   │   └── (admin)/            # Admin portal pages
│   │   ├── components/
│   │   └── generated/              # Auto-generated API client
│   ├── .env
│   └── package.json
│
├── booking-mobile/                 # Git repo — React Native (Expo)
│   ├── src/
│   │   ├── features/
│   │   │   ├── booking/
│   │   │   ├── schedule/
│   │   │   ├── pos/
│   │   │   └── staff/
│   │   ├── navigation/
│   │   │   ├── owner/              # Owner-specific screens
│   │   │   └── staff/              # Staff-specific screens
│   │   ├── components/
│   │   └── generated/              # Auto-generated API client
│   ├── .env
│   └── package.json
│
├── docs/                           # Shared documentation
│   └── architecture.md
├── docker-compose.yml              # Shared: PostgreSQL + Redis + MinIO
├── product-brief-salon-booking-2026-03-10.md
└── CLAUDE.md                       # AI context cho toàn bộ workspace

Type Sharing Strategy

OpenAPI codegen — API là single source of truth cho types.

booking-api (NestJS + Swagger decorators)
  │
  ├── npm run generate:openapi → openapi.json
  │
  ▼
openapi.json ──► openapi-typescript-codegen (hoặc orval)
                      │
                ┌─────┴──────┐
                ▼            ▼
          booking-web/   booking-mobile/
          generated/     generated/
          api-client.ts  api-client.ts
  • NestJS Swagger plugin tự generate openapi.json từ decorators
  • Web/Mobile chạy codegen script → import typed API client
  • Không cần npm package, không cần monorepo tooling

NestJS Module Architecture

AppModule
├── CoreModule (forRoot)
│   ├── TenantModule            # Multi-tenant resolution, tenant context
│   ├── ResourceModule          # CRUD Resource (abstract)
│   ├── ServiceModule           # CRUD Service catalog
│   ├── BookingModule           # Booking engine, scheduling logic
│   ├── CustomerModule          # Customer management
│   ├── NotificationModule      # SMS, push, email templates
│   ├── PaymentModule           # Payment tracking (MVP: manual)
│   └── AuditModule             # Mandatory audit trail
│
├── IndustryModule
│   ├── BeautyModule            # MVP
│   │   ├── StaffResource       # Staff extends Resource
│   │   ├── BeautyService       # Nail, haircut, etc.
│   │   ├── WalkInHandler       # Walk-in specific logic
│   │   ├── SelfPickService     # Staff self-pick bookings
│   │   └── BeautyPosService    # POS checkout flow
│   └── (future modules...)
│
├── AuthModule
│   ├── JWT strategy
│   ├── Role-based access (owner, staff, customer, admin)
│   └── Tenant-scoped guards
│
└── AdminModule                 # Super-admin: create tenants, support access

Multi-tenancy Strategy

Shared database, shared schema, tenant_id discriminator.

  • Mọi query tự động filter by tenant_id (NestJS middleware/interceptor)
  • Prisma middleware hoặc custom decorator inject tenant context
  • Row-level security (RLS) ở PostgreSQL level làm safety net
// Mọi entity có tenant_id
interface TenantScoped {
  tenantId: string;
}

// Request context mang tenant info
interface RequestContext {
  tenant: { id: string; industryType: string; settings: TenantSettings };
  user: { id: string; role: Role };
}

Subdomain routing:

  • nailsbyanna.app.no → resolve tenant → load branding + services
  • api.app.no + X-Tenant-ID header (hoặc từ JWT)

Offline-First Strategy (Mobile)

Brief yêu cầu offline mode cho staff app.

  • WatermelonDB (SQLite-based, React Native) cho local data
  • Sync protocol: last-write-wins với conflict detection
  • Critical offline operations: view schedule, check-in customer, mark payment
  • Sync khi online: queue changes → bulk push → pull latest state

Authentication Flow

Customer: Email/phone → OTP verification → JWT
Staff:    Invite link from owner → set password → JWT  
Owner:    Email + password → JWT (+ optional 2FA)
Admin:    Email + password + 2FA mandatory → JWT

All JWTs contain: userId, tenantId, role, permissions

Notification Architecture

Template-based, per industry:

Template: "booking_confirmed"
├── beauty: "Hei {customer}! Din time hos {staff} er bekreftet for {date} kl {time}"
├── restaurant: "Hei {customer}! Bord for {partySize} er bekreftet for {date} kl {time}"

Channels: SMS (primary for Norway), Push notification, Email (secondary) Provider: Configurable (Twilio, MessageBird, or local Norwegian SMS provider)