Skip to content

Architecture — Foot Factory

System Overview

graph TB
    subgraph Clients
        C[foot-factory.dotevolve.net\nClient App\nReact/Vite]
        A[foot-factory-admin.dotevolve.net\nAdmin Dashboard\nReact/Vite]
    end

    subgraph Platform
        P[portal.dotevolve.net\nEnd-User Portal]
        ADM[admin.dotevolve.net\nAdmin Portal]
        PA[portal-api.dotevolve.net\nPortal API]
        SB[Supabase Auth\nJWT IdP]
    end

    subgraph Foot_Factory_Backend
        API[foot-factory-api.dotevolve.net\nNode.js / Express]
        MONGO[(MongoDB Atlas\nTenant Data)]
        REDIS[(Upstash Redis\nPlan Limits Cache)]
    end

    subgraph Infrastructure
        VER[Vercel API\nDomain Registration]
        CF[Cloudflare DNS\nCNAME Records]
    end

    C -->|Bearer JWT| API
    A -->|Bearer JWT| API
    P -->|SSO Link| C
    ADM -->|Manage Tenants| PA
    PA -->|Provision Subdomains| VER
    PA -->|Provision Subdomains| CF
    PA -->|Plan Limits| API
    SB -->|JWT| C
    SB -->|JWT| A
    API -->|Queries scoped by activeTenantId| MONGO
    API -->|Cache plan limits| REDIS
    API -->|Fetch plan limits| PA

Request Flow

Authenticated App Request

sequenceDiagram
    participant Client as Foot Factory Client
    participant API as Foot Factory API
    participant SB as Supabase Auth
    participant Mongo as MongoDB

    Client->>API: GET /api/v1/worker (Bearer JWT)
    API->>SB: supabase.auth.getUser(token)
    SB-->>API: { user: { app_metadata: { activeTenantId, activeRole } } }
    API->>API: tenantMiddleware injects { tenantId: activeTenantId }
    API->>Mongo: Worker.find({ tenantId: activeTenantId })
    Mongo-->>API: Workers for this tenant only
    API-->>Client: 200 OK { data: workers }

Plan Limit Check

sequenceDiagram
    participant Client as Foot Factory Client
    participant API as Foot Factory API
    participant Redis as Upstash Redis
    participant PA as Portal API

    Client->>API: POST /api/v1/worker/create
    API->>Redis: GET plan:{tenantId}:limits
    alt Cache hit
        Redis-->>API: { maxWorkers: 100, ... }
    else Cache miss
        API->>PA: GET /api/v1/internal/tenants/:id/plan-limits\n(x-service-secret)
        PA-->>API: { maxWorkers: 100, ... }
        API->>Redis: SET plan:{tenantId}:limits (TTL 15min)
    end
    API->>API: Check currentWorkerCount < maxWorkers
    API->>Mongo: Worker.create(...)
    API-->>Client: 201 Created

Service Responsibilities

Service Responsibility
dot-foot-factory-api REST API, tenant isolation, JWT validation, wage/ledger calculation, plan limit enforcement
dot-foot-factory-client Factory floor UI — job creation, step updates, worker assignment, dynamic schema rendering
dot-foot-factory-admin Super Admin UI — tenant schema builder, SSO deep link entry point
dot-portal-api Subdomain provisioning, plan management, tenant/user CRUD, billing
Supabase Auth JWT issuance, session management, password reset
MongoDB Atlas All operational data (jobs, workers, articles, ledger, transactions)
Upstash Redis Plan limits cache, per-tenant rate limiting

Tenant Isolation

Foot Factory uses a shared database, shared collection model. All tenant data lives in the same MongoDB collections, isolated by tenantId.

graph LR
    JWT[JWT\napp_metadata.activeTenantId] --> MW[tenantMiddleware]
    MW --> Q1[Worker.find\n{ tenantId }]
    MW --> Q2[Job.find\n{ tenantId }]
    MW --> Q3[Article.find\n{ tenantId }]
    MW --> Q4[Ledger.find\n{ tenantId }]

The tenantPlugin Mongoose plugin injects { tenantId } into every query automatically. It is impossible for a non-super-admin user to read or write data belonging to another tenant.

What Moved to Portal API

As part of the Central Portal migration, the following concerns moved out of Foot Factory:

Concern Old location New location
Subdomain provisioning dot-foot-factory-api (vercelClient, cloudflareClient) dot-portal-api
Plans / subscription tiers MongoDB Plan collection Supabase plans table via Portal API
CORS origin management MongoDB GlobalConfig Derived from tenant_app_assignments in Central Registry
Authentication Custom JWT (signToken, signRefreshToken) Supabase Auth
Password management MongoDB User model Supabase Auth