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 |