Documentation
/

Club London Docs

Technical documentation for the premium luxury club mobile application.

clublondon.app Expo SDK 54 NestJS 11 PostgreSQL 16 Turborepo

Architecture Overview

High-level infrastructure and service topology.

                 Cloudflare CDN
          clublondon.app  ·  *.clublondon.app

        ┌────────────────────────┐
        │    Hetzner CPX22        │
        │    Helsinki (cl-app)    │
        │                         │
        │  ┌────────────────┐   │
        │  │  Traefik v3     │   │
        │  │  (SSL auto)     │   │
        │  └────┬──────┬────┘   │
        │       │       │        │
        │  ┌────┴─┐ ┌──┴────┐ │
        │  │  API  │ │ Admin  │ │
        │  │ :3000 │ │  :80   │ │
        │  └───┬──┘ └───────┘ │
        │      │                 │
        │  ┌───┴───┐ ┌──────┐ │
        │  │Postgres│ │Redis │ │
        │  │  16    │ │  7   │ │
        │  └───────┘ └──────┘ │
        └────────────────────────┘

        ┌────────────────────────┐
        │   Expo / EAS Build    │
        │  iOS + Android apps    │
        │  → App Store / Play    │
        └────────────────────────┘

Domains

DomainService
clublondon.appLanding page
api.clublondon.appNestJS API
admin.clublondon.appReact admin panel
staging-api.clublondon.appStaging API

📁 Monorepo Structure

Turborepo-powered workspace organization.

clublondon/
├── apps/
│   ├── mobile/              # Expo SDK 54 / React Native
│   │   ├── app/             # Expo Router screens (30 screens)
│   │   │   ├── (auth)/      # Login, Register, Onboarding
│   │   │   ├── (tabs)/      # Home, Events, Travel, Matching, Profile
│   │   │   └── *.tsx        # Modal screens (wallet, concierge, etc.)
│   │   ├── assets/images/   # Generated hero images
│   │   ├── lib/             # API client, theme, stores
│   │   └── app.json         # Expo configuration
│   │
│   ├── admin/               # React 19 + Vite 6 admin panel
│   │   ├── src/
│   │   │   ├── App.tsx      # All pages + hash router
│   │   │   ├── api.ts       # API client
│   │   │   └── main.tsx     # Entry point
│   │   ├── Dockerfile       # Multi-stage → nginx
│   │   └── nginx.conf       # SPA routing config
│   │
│   └── api/                 # NestJS 11 + Fastify
│       ├── src/
│       │   ├── modules/     # Auth, Users, Events, Travel, etc.
│       │   ├── drizzle/     # ORM schema + migrations
│       │   ├── seed.ts      # Test data seeder
│       │   ├── app.module.ts
│       │   └── main.ts      # Bootstrap + CORS
│       └── Dockerfile       # Multi-stage build
│
├── packages/
│   ├── shared-types/        # TypeScript interfaces
│   └── validators/          # Zod schemas
│
├── infrastructure/
│   ├── docker/              # docker-compose.prod.yml + dev.yml
│   ├── cloudflare/          # DNS setup scripts
│   └── hetzner/             # Server provisioning
│
├── skills/
│   └── clublondon-deploy/   # Deploy automation (9 scripts)
│
├── .github/workflows/       # CI + deploy + EAS build
├── eas.json                 # EAS Build profiles
├── turbo.json               # Turborepo pipeline config
├── pnpm-workspace.yaml      # Workspace definition
└── CLAUDE.md                # AI assistant context

Technology Stack

Complete list of technologies across all layers.

Mobile App

LayerTechnologyVersion
FrameworkExpo SDK54.0.33
UIReact Native0.81.5
NavigationExpo Router6.0.23
StateZustand5.0.0
Data FetchingTanStack Query5.60.0
FormsReact Hook Form7.53.0
ValidationZod3.24.0
IconsLucide React Native0.460.0
AnimationsReanimated4.1.7
Secure Storageexpo-secure-store15.0.8
Real-timeSocket.io Client4.8.0

Admin Panel

LayerTechnologyVersion
FrameworkReact19.0.0
BuildVite6.0.0
RoutingHash-based (custom)-
Data FetchingTanStack Query5.60.0
CSSInline styles (dark theme)-
IconsLucide React0.460.0

API Backend

LayerTechnologyVersion
FrameworkNestJS11.0.0
HTTPFastify-
ORMDrizzle ORM0.38.0
DatabasePostgreSQL16
CacheRedis7
QueueBullMQ5.0.0
AuthPassport JWT4.0.0
StorageCloudflare R2 (S3)-
Real-timeSocket.io-
ValidationZod + class-validator-

Infrastructure

ComponentTechnology
ServerHetzner CPX22 (Helsinki)
Reverse ProxyTraefik v3 (auto SSL)
CDN/DNSCloudflare
ContainerDocker + Docker Compose
CI/CDGitHub Actions
Mobile BuildsEAS Build
Package Managerpnpm 9.15.0
MonorepoTurborepo 2.3.0

Getting Started

Set up your local development environment.

Prerequisites

  • Node.js >= 20
  • pnpm >= 9.15.0
  • Docker & Docker Compose
  • Expo CLI (npm install -g eas-cli)

Installation

# Clone repository
git clone https://github.com/alegkoshyk/clublondon.git
cd clublondon

# Install dependencies
pnpm install

# Start local database & Redis
docker compose -f infrastructure/docker/docker-compose.dev.yml up -d

# Copy env file
cp .env.example .env.local
# Edit .env.local with your values

# Push database schema
pnpm db:push

# Seed test data
cd apps/api && npx ts-node src/seed.ts && cd ../..

# Start all apps
pnpm dev

Development URLs

AppURL
Mobile (web)http://localhost:8081
Adminhttp://localhost:5173
APIhttp://localhost:3000/api/v1
Drizzle Studiopnpm db:studio

Test Accounts

EmailPasswordRole
[email protected]Admin123!Admin
[email protected]Test123!Member
[email protected]Test123!Member

📱 Mobile App (Expo)

30 screens built with Expo SDK 54 and React Native.

Auth Flow

ScreenFileStatus
Login(auth)/login.tsxAPI connected
Register(auth)/register.tsxAPI connected
Onboarding(auth)/onboarding.tsxSaves interests to API

Tab Navigation (5 tabs)

TabFileData Source
Home/Explore(tabs)/index.tsxGET /events?limit=3
Events(tabs)/events.tsxGET /events?limit=20
Travel(tabs)/travel.tsxGET /travel?limit=20
Matching(tabs)/matching.tsxGET /users?limit=10
Profile(tabs)/profile.tsxAuth store (user data)

Detail Screens

ScreenFileAPI
Event Detailevent-detail.tsxGET /events/:id + POST /events/:id/rsvp
Trip Detailtrip-detail.tsxGET /travel/:id + POST /travel/:id/book
Match Profilematch-profile.tsxUser data from navigation params

Feature Screens

ScreenFileAPI
Walletwallet.tsxGET /wallet + GET /wallet/balance
Conciergeconcierge.tsxGET /concierge/my
New Requestnew-request.tsxPOST /concierge
Notificationsnotifications.tsxGET /notifications + POST /notifications/read-all
Edit Profileedit-profile.tsxPATCH /users/me
Loyaltyloyalty.tsxAuth store (points, tier)
Club Cardclub-card.tsxAuth store (user data)
Settingssettings.tsxLocal state + logout

Other Screens

Explore

explore.tsx

Messages

messages.tsx

Match Chat

match-chat.tsx

Concierge Chat

concierge-chat.tsx

Send Payment

send-payment.tsx

Crypto Vault

crypto-vault.tsx

Payment Methods

payment-methods.tsx

Special Offers

special-offers.tsx

World Agenda

world-agenda.tsx

Booking Confirmed

booking-confirmed.tsx

Request Membership

request-membership.tsx

State Management

  • Auth: Zustand store with JWT in SecureStore
  • Data: TanStack Query for server state
  • Forms: React Hook Form + Zod validation

Theme

const colors = {
  bg:    '#0D0D1A',       // Primary background
  bg2:   '#1A1A2E',       // Secondary
  bg3:   '#16213E',       // Tertiary
  gold:  '#C4A35A',       // Primary accent
  gold2: '#D4B96A',       // Light gold
  white: '#FFFFFF',
  green: '#4CAF50',       // Success
  red:   '#F44336',       // Error
}

const fonts = {
  display: 'Cormorant Garamond',  // Headings
  body:    'DM Sans',               // Body text
}

EAS Build Profiles

ProfileDistributionUse Case
developmentInternalDev client with debugger
previewInternalQA testing
productionStoreApp Store / Google Play
# Build commands
eas build --profile preview --platform all
eas build --profile production --platform ios
eas submit --platform ios --latest
eas update --branch production --message "hotfix: ..."

💻 Admin Panel (React)

React 19 admin dashboard with hash-based routing.

URL Routing

URLPageDescription
#/dashboardDashboardKPIs, activity chart, tier distribution
#/membersMembersTable + edit (tier/points/status)
#/eventsEventsFull CRUD + publish/unpublish
#/travelTravelFull CRUD + publish/unpublish
#/conciergeConciergeStatus management (6 statuses)
#/loyaltyLoyaltyTier distribution, top holders
#/matchingMatchingActive profiles grid
#/notificationsNotificationsRead/unread management
#/cmsCMSPages, Blog, Media (coming soon)
#/settingsSettingsGeneral config + infrastructure

CRUD Operations

EntityCreateReadUpdateDeletePublish
EventsYesYesYesYesYes
TravelYesYesYesYesYes
Members-Yestier/points--
Concierge-Yesstatus--

Login

  • URL: admin.clublondon.app
  • Auth: JWT (stored in localStorage)
  • Credentials: same as API accounts

API Backend (NestJS)

NestJS 11 with Fastify adapter and modular architecture.

Modules

ModuleController PrefixDescription
AuthModule/authLogin, register, refresh, logout
UsersModule/usersUser CRUD, profile, stats
EventsModule/eventsEvents CRUD, RSVP, stats
TravelModule/travelTrips CRUD, booking
ConciergeModule/conciergeRequests, status updates
WalletModule/walletBalance, transactions
NotificationsModule/notificationsCRUD, mark read

Middleware & Security

  • CORS: Configured for admin, mobile, localhost, Expo
  • Helmet: Security headers
  • Validation: Global ValidationPipe (whitelist + transform)
  • Auth: JWT Bearer tokens via Passport.js
  • Global Prefix: /api/v1

🗃 Database Schema

PostgreSQL 16 with Drizzle ORM. 9 tables total.

users

ColumnTypeNotes
idUUIDPrimary key
emailVARCHARUnique
passwordHashVARCHARbcrypt
firstName, lastNameVARCHAR
phone, avatarUrl, bio, locationVARCHAROptional
membershipTierVARCHARstandard / silver / gold / platinum / diamond
loyaltyPointsINTEGERDefault: 0
interestsJSONBArray of strings
isVerified, isActiveBOOLEAN
createdAt, updatedAt, deletedAtTIMESTAMPSoft delete

events

ColumnTypeNotes
idUUIDPrimary key
title, descriptionVARCHAR/TEXT
location, addressVARCHAR
startDate, endDateTIMESTAMP
categoryVARCHAR
tagsJSONBArray of strings
maxGuests, priceGbp, pricePointsINTEGER
isFeatured, isPublishedBOOLEAN
rsvpCountINTEGERComputed from rsvps table

trips

ColumnTypeNotes
idUUIDPrimary key
title, description, destination, locationVARCHAR
startDate, endDateTIMESTAMP
tagsJSONBArray
maxGuests, priceGbp, pricePointsINTEGER
itineraryJSONBArray of {day, description}
isFeatured, isPublishedBOOLEAN

rsvps

ColumnTypeNotes
userId, eventIdUUIDForeign keys
statusVARCHARconfirmed / cancelled
guestCountINTEGERDefault: 1

tripBookings

ColumnTypeNotes
userId, tripIdUUIDForeign keys
statusVARCHARconfirmed / cancelled
totalPaidINTEGER

conciergeRequests

ColumnTypeNotes
userId, agentIdUUIDForeign keys
serviceType, title, descriptionVARCHAR
priorityVARCHARnormal / high / urgent
statusVARCHARpending / assigned / in_progress / confirmed / completed / cancelled

notifications

ColumnTypeNotes
userIdUUIDForeign key
type, title, bodyVARCHAR
dataJSONBExtra metadata
isReadBOOLEAN

walletTransactions

ColumnTypeNotes
userIdUUIDForeign key
typeVARCHARcredit / debit / swap
currencyVARCHARGBP / BTC / ETH / USDT
amountTEXTString for precision
statusVARCHARcompleted / pending / failed

sessions

ColumnTypeNotes
userIdUUIDForeign key
refreshTokenVARCHAR
userAgent, ipAddressVARCHAR
expiresAtTIMESTAMP

🔒 Authentication

JWT-based authentication with access and refresh tokens.

Flow

1. POST /auth/login { email, password }
    { accessToken, refreshToken, user }

2. All requests: Authorization: Bearer {accessToken}

3. Token expired  POST /auth/refresh { refreshToken }
    { accessToken, refreshToken }

4. POST /auth/logout { refreshToken }

Token Configuration

TokenExpirationStorage
Access Token15 minutesMobile: SecureStore, Admin: localStorage
Refresh Token7 daysMobile: SecureStore, Admin: localStorage

🔗 API Endpoints

Complete REST API reference. Base URL: /api/v1

Auth

MethodEndpointBodyAuth
POST/auth/login{email, password}No
POST/auth/register{email, password, firstName, lastName}No
POST/auth/refresh{refreshToken}No
POST/auth/logout{refreshToken}Yes

Users

MethodEndpointDescriptionAuth
GET/usersList all usersYes
GET/users/meGet current userYes
GET/users/statsUser statisticsYes
GET/users/:idGet user by IDYes
PATCH/users/meUpdate own profileYes
PATCH/users/:idUpdate user (admin)Yes

Events

MethodEndpointDescriptionAuth
GET/eventsList events (?limit=N)Yes
GET/events/statsEvent statisticsYes
GET/events/:idGet event detailsYes
POST/eventsCreate eventYes
PATCH/events/:idUpdate eventYes
DELETE/events/:idDelete eventYes
POST/events/:id/rsvpRSVP to eventYes

Travel

MethodEndpointDescriptionAuth
GET/travelList tripsYes
GET/travel/:idGet trip detailsYes
POST/travelCreate tripYes
PATCH/travel/:idUpdate tripYes
DELETE/travel/:idDelete tripYes
POST/travel/:id/bookBook tripYes

Concierge

MethodEndpointDescriptionAuth
GET/conciergeList all requestsYes
GET/concierge/myMy requestsYes
GET/concierge/statsStatisticsYes
POST/conciergeCreate requestYes
PATCH/concierge/:id/statusUpdate statusYes

Wallet

MethodEndpointDescriptionAuth
GET/walletGet transactionsYes
GET/wallet/balanceGet balanceYes

Notifications

MethodEndpointDescriptionAuth
GET/notificationsList notificationsYes
POST/notifications/read-allMark all readYes

Infrastructure

Server setup, Docker containers, and Cloudflare configuration.

Server: Hetzner CPX22

  • Location: Helsinki, Finland
  • Resources: 3 vCPU, 4 GB RAM, 80 GB NVMe
  • OS: Ubuntu 24.04

Docker Containers (Production)

ContainerImagePortRole
cl-traefiktraefik:v3.380, 443Reverse proxy + SSL
cl-apicl-api:latest3000NestJS backend
cl-admincl-admin:latest80React admin (nginx)
cl-postgrespostgres:16-alpine5432Database
cl-redisredis:7-alpine6379Cache + queues

Cloudflare

  • DNS Records: A records for @, api, admin, staging-api
  • SSL: Full (strict)
  • Storage: R2 bucket cl-media

🚀 CI/CD Pipelines

GitHub Actions workflows for testing, building, and deploying.

GitHub Actions Workflows

1. CI (ci.yml)

  • Trigger: PR to develop/main, push to develop
  • Steps: Install → Lint → TypeCheck → Build → Test

2. Deploy Staging (deploy-staging.yml)

  • Trigger: Push to develop
  • Steps: Build → Push images (:staging) → SSH deploy → Health check

3. Deploy Production (deploy-production.yml)

  • Trigger: Push to main
  • Steps: Build → Push images (:latest + :sha) → SSH deploy → Health check

4. EAS Build (eas-build.yml)

  • Trigger: Manual dispatch
  • Inputs: platform (ios/android/all), profile (dev/preview/prod)

Git Branching

main        production deploys
  
develop     staging deploys (auto)
  
feature/*   development (squash merge to develop)
hotfix/*    from main (merge commit)

🔧 Deploy Scripts

Automation scripts in skills/clublondon-deploy/scripts/

ScriptTriggerDescription
git-push.sh [msg]pushGit add → commit → push → offer PR
eas-build.sh [profile] [platform]buildEAS build (dev/preview/prod)
deploy.sh [staging|prod]deployDocker deploy to Hetzner
eas-submit.sh [platform]submitSubmit to App Store / Play
eas-update.sh [branch] [msg]OTAOver-the-air update
release.sh [msg]releaseFull release pipeline
status.shstatusHealth check all services
cloudflare-dns.sh [ip]DNSUpsert DNS records
hetzner-setup.shserversProvision servers

Manual Deploy

# Build images locally
docker build --platform linux/amd64 -t cl-api:latest -f apps/api/Dockerfile .
docker build --platform linux/amd64 -t cl-admin:latest -f apps/admin/Dockerfile .

# Save, transfer, load
docker save cl-api:latest | gzip | ssh -i ~/.ssh/cl-deploy root@server "gunzip | docker load"
docker save cl-admin:latest | gzip | ssh -i ~/.ssh/cl-deploy root@server "gunzip | docker load"

# Restart containers on server
ssh -i ~/.ssh/cl-deploy root@server "cd /opt/clublondon && docker compose up -d"

🔑 Environment Variables

Required and optional configuration variables.

Required

# Database
DATABASE_URL=postgresql://clublondon:password@localhost:5432/clublondon

# Redis
REDIS_URL=redis://:password@localhost:6379

# JWT
JWT_SECRET=your-secret-key
JWT_REFRESH_SECRET=your-refresh-secret

# Cloudflare R2
CLOUDFLARE_R2_ACCESS_KEY_ID=
CLOUDFLARE_R2_SECRET_ACCESS_KEY=
CLOUDFLARE_R2_BUCKET=cl-media

# Expo (mobile)
EXPO_PUBLIC_API_URL=https://api.clublondon.app

Optional

# App
NODE_ENV=development
PORT=3000
APP_URL=https://clublondon.app
ADMIN_URL=https://admin.clublondon.app

# Stripe (future)
STRIPE_SECRET_KEY=
STRIPE_WEBHOOK_SECRET=

# Hetzner (deploy)
HCLOUD_TOKEN=

# Cloudflare (DNS)
CLOUDFLARE_API_TOKEN=
CLOUDFLARE_ZONE_ID=
CLOUDFLARE_ACCOUNT_ID=

# EAS (builds)
EXPO_TOKEN=

🎨 Design System

Colors, typography, spacing, and component patterns.

Colors

TokenValueSwatchUsage
bg#0D0D1APrimary background
bg2#1A1A2ECards, sidebar
bg3#16213ETertiary surfaces
gold#C4A35APrimary accent
gold2#D4B96ALight gold variant
white#FFFFFFPrimary text
green#4CAF50Success
red#F44336Error/danger

Typography

FontUsage
Cormorant GaramondHeadings, display text
DM SansBody text, UI elements

Spacing

TokenValue
xs4px
sm8px
md12px
lg16px
xl20px
xxl24px
xxxl32px

Border Radius

TokenValue
sm8px
md12px
lg16px
xl20px
pill100px

Component Patterns

  • Cards: rgba(255,255,255,0.04) bg + rgba(255,255,255,0.06) border + 16px radius
  • Badges: Colored text + tinted background + pill radius
  • Buttons Primary: Gold bg + dark text + 600 weight
  • Buttons Secondary: Transparent bg + white border + white text
  • Inputs: rgba(255,255,255,0.04) bg + 8px radius + white text
  • Tables: Collapsed borders + hover rgba(255,255,255,0.02)

Key Commands Reference

Quick reference for all development, build, and deploy commands.

# Development
pnpm dev              # All apps parallel
pnpm dev:api          # API only (port 3000)
pnpm dev:mobile       # Mobile Expo (port 8081)
pnpm dev:admin        # Admin Vite (port 5173)

# Database
pnpm db:generate      # Generate Drizzle migrations
pnpm db:push          # Push schema to PostgreSQL
pnpm db:studio        # Open Drizzle Studio GUI

# Build
pnpm build            # Build all
pnpm build:api        # Build API
pnpm build:admin      # Build Admin

# Quality
pnpm lint             # ESLint all apps
pnpm typecheck        # TypeScript check all
pnpm test             # Run tests

# Mobile
eas build --profile preview --platform all
eas submit --platform ios --latest
eas update --branch production --message "fix"

# Deploy
bash skills/clublondon-deploy/scripts/status.sh
bash skills/clublondon-deploy/scripts/deploy.sh prod