Complete system reference covering architecture, API, database schema, mobile apps, remote desktop (TTFDesk), deployment, and security.
TouchToFix v2 (vnext) is a microservice-oriented platform deployed on Google Cloud Platform. All components run in Docker containers orchestrated by Docker Compose behind an nginx reverse proxy with TLS termination.
/ws routes.Browser / Mobile App
↓ HTTPS (443)
nginx (nginx:alpine)
↓ reverse proxy by Host header
├── api.touchtofix.com → api:3100 (NestJS)
│ ↕ Prisma ORM
│ ├── postgres:5432
│ └── redis:6379 (WebSocket relay)
├── app.touchtofix.com → static Flutter web (customer)
├── expert.touchtofix.com → static Flutter web (expert)
├── admin.touchtofix.com → admin:80 (React SPA)
├── fixme.touchtofix.com → fixme:80 (FixMe Portal)
└── touchtofix.com → website:80 (Marketing)
| Layer | Technology | Version | Purpose |
|---|---|---|---|
| API Framework | NestJS + Fastify | ^10.x | REST API + WebSocket server |
| ORM | Prisma | ^5.x | Type-safe database access |
| Database | PostgreSQL | 16 | Primary data store |
| Cache / WS | Redis | 7 | WebSocket message relay, session cache |
| Mobile / Web | Flutter + Dart | 3.x | Customer + Expert apps (iOS/Android/Web) |
| Admin Panel | React + Vite | 18 / 5.x | Operations dashboard |
| Language (API) | TypeScript | 5.x | Strict type-safety across backend |
| Reverse Proxy | nginx | alpine | TLS termination, routing, WebSocket upgrade |
| Container Runtime | Docker + Compose | 26.x | Service orchestration |
| AI / ML | Ollama (LLaVA) | latest | Multimodal AI diagnosis engine |
| Remote Control | RustDesk Server | latest | TTFDesk self-hosted relay (hbbs + hbbr) |
| Screen Share | WebRTC (browser native) | — | FixMe Portal peer-to-peer stream |
| TURN Server | coturn | 4.6 | NAT traversal for WebRTC |
| TLS Certs | Let's Encrypt / Certbot | — | Auto-renewing HTTPS certificates |
| Cloud | Google Cloud Platform | — | VM: e2-standard-2, us-central1-f |
Base URL: https://api.touchtofix.com · All authenticated routes require: Authorization: Bearer {jwt}
POST /auth/otp/request → POST /auth/otp/verify). Tokens are long-lived JWTs stored securely on-device.{"phone": "60123456789", "phoneSuffix": "+60"}{"phone": "...", "phoneSuffix": "...", "code": "123456"}. Returns {"token": "jwt...", "user": {...}}{"token", "otpCode", "expiresAt", "url"} where url is the QR destination.{"token": "...", "otpCode": "123456"}. Returns {"token": "jwt...", "user": {...}} — auto-logs in the PC browser.{"title", "description", "os", "priority": "URGENT|HIGH|MEDIUM|LOW"}{"status": "ACTIVE_SESSION|RESOLVED|CLOSED"}{"content": "...", "type": "TEXT|IMAGE|FILE|rustdesk_request"}rustdesk_request chat message. Returns {"sessionId", "ttfdeskId", "rustdeskOtp", "status"}{"enabled": true|false}{"token", "otpCode", "otpExpiresAt"}{"description": "...", "imageBase64": "..."} (image optional). Async — returns request ID; poll for result.{"status": "PENDING|COMPLETED|FAILED", "result": {...}}All admin routes are protected by AdminAuthGuard. Pass admin API key in header: x-admin-key: {key}
?page=1&limit=50&role=CUSTOMER{"status": "APPROVED|REJECTED", "reason": "..."}{"name", "adminUserId", "slaPolicyId"}{"status": "APPROVED|REJECTED"}Connect to: wss://api.touchtofix.com/ws with JWT in query: ?token={jwt}
| Event | Direction | Payload |
|---|---|---|
message:new | Server → Client | New chat message object |
ticket:updated | Server → Client | Ticket status changed |
session:started | Server → Client | Remote session created |
session:ended | Server → Client | Session terminated |
notification:push | Server → Client | Push notification payload |
PostgreSQL database managed by Prisma ORM. Migrations located at prisma/migrations/. Schema file: prisma/schema.prisma.
60123456789)+60){"ttfdeskId","rustdeskOtp","relayServer","downloadUrl"}Both apps share the same Flutter codebase style, design system, and dependency set. They are compiled to iOS (IPA), Android (APK/AAB), and Web (static HTML).
ttf_customer_vnextttf_expert_vnext# Customer web app
cd version2/mobile/ttf_customer_vnext
flutter build web --release
# Output: build/web/
# Expert web app
cd version2/mobile/ttf_expert_vnext
flutter build web --release
# Output: build/web/
# Android APK
flutter build apk --release
# iOS IPA (requires macOS + Xcode)
flutter build ipa --release
Both apps read from lib/config/app_config.dart:
class AppConfig {
static const String apiBaseUrl = 'https://api.touchtofix.com';
static const String wsBaseUrl = 'wss://api.touchtofix.com';
static const String fixmeBaseUrl = 'https://fixme.touchtofix.com';
static const String appName = 'TouchToFix';
}
1. Client → POST /auth/otp/request { phone, phoneSuffix }
Server: generates 6-digit OTP, stores with 5-min TTL, sends via SMS
2. Client → POST /auth/otp/verify { phone, phoneSuffix, code }
Server: validates OTP, returns { token: JWT, user: {...} }
3. All subsequent requests: Authorization: Bearer {JWT}
1. Mobile app → POST /auth/desktop-transfer
Server: creates 32-byte hex token + 6-digit OTP, expires in 5 min
Returns: { token, otpCode, expiresAt, url }
url = https://app.touchtofix.com?dt={token}&otp={otpCode}
2. Mobile app: displays QR code of url, shows OTP with countdown
3. PC browser opens url (scans QR or manual):
Flutter web app detects ?dt + ?otp params in Uri.base
→ POST /auth/desktop-transfer/verify { token, otpCode }
→ Server validates, marks token used, returns { token: JWT, user }
→ SessionStore.saveSession(token, userId)
→ Navigates to /home as logged-in user
| Role | Capabilities |
|---|---|
CUSTOMER | Create tickets, chat, remote sessions, AI diagnosis, profile management |
EXPERT | Accept tickets, chat, TTFDesk remote control, toggle recording |
COMPANY_ADMIN | Manage company team, view company tickets, company billing |
ADMIN | Full platform access, user management, config, billing, audit logs |
TTFDesk is our branded self-hosted remote control infrastructure built on RustDesk open-source (AGPL-3.0). It provides full keyboard and mouse control of the customer's PC from the expert's PC.
| Component | Service | Ports | Purpose |
|---|---|---|---|
| ID Server | rustdesk-hbbs | 21115, 21116 (TCP+UDP), 21118 | ID registration, peer discovery, NAT traversal coordination |
| Relay Server | rustdesk-hbbr | 21117, 21119 | Traffic relay when direct P2P fails |
The customer's TTFDesk ID is automatically derived from their phone number, eliminating the need for manual ID lookup or sharing:
// In vnc.service.ts → requestRustdesk()
const customer = await prisma.user.findUnique({
where: { id: session.userId },
select: { phone: true, phoneSuffix: true }
});
const sessionCount = await prisma.vncSession.count({
where: { userId: session.userId }
});
const counter = String(sessionCount).padStart(4, "0");
const phoneDigits = (customer.phoneSuffix ?? "").replace(/\D/g, "")
+ (customer.phone ?? "").replace(/\D/g, "");
const ttfdeskId = phoneDigits
? `${phoneDigits}-${counter}` // e.g. "60123456789-0001"
: `ttf-${session.userId.slice(0,8)}-${counter}`;
1. Expert taps "Request Remote Control (TTFDesk)" in expert app
2. Backend: derives ttfdeskId, generates OTP, stores in VncSession
3. Backend sends chat message { type: "rustdesk_request", ttfdeskId, rustdeskOtp, relayServer, downloadUrl }
4. Customer sees "TTFDesk Remote Control Request" card in chat:
Step 1: Download TTFDesk from touchtofix.com/ttfdesk
Step 2: In TTFDesk Settings → ID Server → set: api.touchtofix.com
Step 3: In TTFDesk Settings → set Custom ID to: {ttfdeskId}
Step 4: Set password to: {otp}
Step 5: Confirm — expert will connect shortly
5. Expert sees: Customer TTFDesk ID + Password in styled card
6. Expert opens TTFDesk on PC, enters ID, connects with OTP
| Setting | Value |
|---|---|
| ID Server | api.touchtofix.com |
| Relay Server | api.touchtofix.com:21117 |
| Public Key | 85klgfZt5P916CGhvBwiDdKljRSavIHZ2ERa3V7eriI= |
| Custom ID format | {phoneSuffix digits}{phone}-{0001} |
For lighter interactions, the FixMe Portal provides browser-based screen sharing without software installation:
fixme.touchtofix.com on PC browserdurationMinutes = ceil((endedAt - startedAt) / 60_000)
slabs = ceil(durationMinutes / 15)
grossAmount = slabs * (expertRate / 4) // rate is per hour ÷ 4
platformFee = grossAmount * 0.20
expertPayout = grossAmount * 0.80
// B2B discount applied before calculation:
grossAmount *= (1 - company.discountPercentage / 100)
GET /admin/billing/summary — Platform-wide revenue reportPOST /admin/customers/:id/balance/collect — Initiate payment collectionPOST /admin/customers/:id/balance/override — Manual balance adjustmentPUT /admin/payments/:id/status — Update payment status (PENDING → PAID → DISPUTED)gcloud compute ssh ttfnewv02 --zone=us-central1-f
# Working directory: /opt/ttf
# Compose file: docker-compose.prod.yml
# 1. Build locally
cd version2/backend/ttf_backend_vnext
npm run build
# 2. Package dist
tar -czf /tmp/backend_dist.tar.gz dist/ package.json package-lock.json
# 3. Upload to VM
gcloud compute scp /tmp/backend_dist.tar.gz ttfnewv02:/tmp/ --zone=us-central1-f
# 4. On VM: extract + rebuild container
cd /opt/ttf/backend/ttf_backend_vnext
tar -xzf /tmp/backend_dist.tar.gz
cd /opt/ttf
docker compose -f docker-compose.prod.yml up -d --build api
# 5. Restart nginx
docker compose -f docker-compose.prod.yml restart nginx
# Build
flutter build web --release
# Package + upload
tar -czf /tmp/customer_web.tar.gz -C ttf_customer_vnext/build web
gcloud compute scp /tmp/customer_web.tar.gz ttfnewv02:/tmp/ --zone=us-central1-f
# On VM: deploy
sudo rm -rf /opt/ttf/web/customer/*
mkdir -p /tmp/customer_web_tmp
tar -xzf /tmp/customer_web.tar.gz -C /tmp/customer_web_tmp
sudo cp -r /tmp/customer_web_tmp/web/* /opt/ttf/web/customer/
# Development (interactive)
npx prisma migrate dev --name your_migration_name
# Production (non-interactive)
npx prisma migrate deploy
# Seed database
node prisma/seed.js
cd /opt/ttf
docker compose -f docker-compose.prod.yml ps
curl https://api.touchtofix.com/health
docker logs ttf-api-1 --tail=50
| Port | Protocol | Service | Exposure |
|---|---|---|---|
| 80 | TCP | nginx (HTTP → HTTPS redirect) | Public |
| 443 | TCP | nginx (HTTPS, all web traffic) | Public |
| 3100 | TCP | NestJS API | Internal (proxied) |
| 5432 | TCP | PostgreSQL | Internal only |
| 6379 | TCP | Redis | Internal only |
| 3478 | TCP+UDP | coturn TURN/STUN | Public (WebRTC) |
| 5349 | TCP | coturn TURNS (TLS) | Public (WebRTC) |
| 21115 | TCP | TTFDesk hbbs (ID server) | Public (TTFDesk) |
| 21116 | TCP+UDP | TTFDesk hbbs (relay signal) | Public (TTFDesk) |
| 21117 | TCP | TTFDesk hbbr (relay data) | Public (TTFDesk) |
| 21118 | TCP | TTFDesk hbbs web client | Public (TTFDesk) |
| 21119 | TCP | TTFDesk hbbr web client | Public (TTFDesk) |
| Domain | Backend Service | Purpose |
|---|---|---|
| touchtofix.com | website:80 | Marketing website |
| api.touchtofix.com | api:3100 | REST API + WebSocket |
| app.touchtofix.com | static files | Customer Flutter web app |
| expert.touchtofix.com | static files | Expert Flutter web app |
| admin.touchtofix.com | admin:80 | Admin React SPA |
| fixme.touchtofix.com | fixme:80 | FixMe screen share portal |
GET https://api.touchtofix.com/health returns {"status":"ok"}. All TTFDesk ports confirmed open in GCP firewall rule ttf-rustdesk.TouchToFix Technologies · Technical Documentation v2.0 · March 2026 · Confidential