# Multi-Tenant Migration - Implementation Summary ## Overview The platform has been migrated from a single-database multi-tenant architecture to a **one database per tenant** architecture with subdomain-based tenant identification. ## Architecture Changes ### Database Layer - **Central Database** (Prisma): Stores tenant metadata, domain mappings, encrypted credentials - **Tenant Databases** (Knex.js + Objection.js): One MySQL database per tenant with isolated data ### Tenant Identification - **Before**: `x-tenant-id` header - **After**: Subdomain extraction from hostname (e.g., `acme.routebox.co` → tenant `acme`) - **Fallback**: `x-tenant-id` header for local development ### Technology Stack - **Central DB ORM**: Prisma 5.8.0 - **Tenant DB Migration**: Knex.js 3.x - **Tenant DB ORM**: Objection.js 3.x - **Database Driver**: mysql2 ## File Structure ### Backend - Tenant Management ``` src/tenant/ ├── tenant-database.service.ts # Knex connection manager with encryption ├── tenant-provisioning.service.ts # Create/destroy tenant databases ├── tenant-provisioning.controller.ts # API for tenant provisioning ├── tenant.middleware.ts # Subdomain extraction & tenant injection └── tenant.module.ts # Module configuration migrations/tenant/ # Knex migrations for tenant databases ├── 20250126000001_create_users_and_rbac.js ├── 20250126000002_create_object_definitions.js ├── 20250126000003_create_apps.js └── 20250126000004_create_standard_objects.js ``` ### Backend - Models (Objection.js) ``` src/models/ ├── base.model.ts # Base model with timestamps ├── user.model.ts # User with roles ├── role.model.ts # Role with permissions ├── permission.model.ts # Permission ├── user-role.model.ts # User-Role join table ├── role-permission.model.ts # Role-Permission join table ├── object-definition.model.ts # Dynamic object metadata ├── field-definition.model.ts # Field metadata ├── app.model.ts # Application ├── app-page.model.ts # Application pages └── account.model.ts # Standard Account object ``` ### Backend - Schema Management ``` src/object/ ├── schema-management.service.ts # Dynamic table creation from ObjectDefinitions └── object.service.ts # Object CRUD operations (needs migration) ``` ### Central Database Schema (Prisma) ``` prisma/ ├── schema-central.prisma # Tenant, Domain models └── migrations/ # Will be created when generating ``` ## Setup Instructions ### 1. Environment Configuration Copy `.env.example` to `.env` and configure: ```bash cd /root/neo/backend cp .env.example .env ``` Generate encryption key: ```bash node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" ``` Update `.env` with the generated key and database URLs: ```env CENTRAL_DATABASE_URL="mysql://user:password@platform-db:3306/central_platform" ENCRYPTION_KEY="" DB_ROOT_USER="root" DB_ROOT_PASSWORD="root" ``` ### 2. Central Database Setup Generate Prisma client and run migrations: ```bash cd /root/neo/backend npx prisma generate --schema=./prisma/schema-central.prisma npx prisma migrate dev --schema=./prisma/schema-central.prisma --name init ``` ### 3. Tenant Provisioning Create a new tenant via API: ```bash curl -X POST http://localhost:3000/setup/tenants \ -H "Content-Type: application/json" \ -d '{ "name": "Acme Corporation", "slug": "acme", "primaryDomain": "acme" }' ``` This will: 1. Create MySQL database `tenant_acme` 2. Create database user `tenant_acme_user` 3. Run all Knex migrations on the new database 4. Seed default roles and permissions 5. Store encrypted credentials in central database 6. Create domain mapping (`acme` → tenant) ### 4. Testing Subdomain Routing Update your hosts file or DNS to point subdomains to your server: ``` 127.0.0.1 acme.localhost 127.0.0.1 demo.localhost ``` Access the application: - Central setup: `http://localhost:3000/setup/tenants` - Tenant app: `http://acme.localhost:3000/` - Different tenant: `http://demo.localhost:3000/` ## Migration Status ### ✅ Completed - [x] Central database schema (Tenant, Domain models) - [x] Knex + Objection.js installation - [x] TenantDatabaseService with dynamic connections - [x] Password encryption/decryption (AES-256-CBC) - [x] Base Objection.js models (User, Role, Permission, etc.) - [x] Knex migrations for base tenant schema - [x] Tenant middleware with subdomain extraction - [x] Tenant provisioning service (create/destroy) - [x] Schema management service (dynamic table creation) ### 🔄 Pending - [ ] Generate Prisma client for central database - [ ] Run Prisma migrations for central database - [ ] Migrate AuthService from Prisma to Objection.js - [ ] Migrate RBACService from Prisma to Objection.js - [ ] Migrate ObjectService from Prisma to Objection.js - [ ] Migrate AppBuilderService from Prisma to Objection.js - [ ] Update frontend to work with subdomains - [ ] Test tenant provisioning flow - [ ] Test subdomain routing - [ ] Test database isolation ## Service Migration Guide ### Example: Migrating a Service from Prisma to Objection **Before (Prisma):** ```typescript async findUser(email: string) { return this.prisma.user.findUnique({ where: { email } }); } ``` **After (Objection + Knex):** ```typescript constructor(private readonly tenantDbService: TenantDatabaseService) {} async findUser(tenantId: string, email: string) { const knex = await this.tenantDbService.getTenantKnex(tenantId); return User.query(knex).findOne({ email }); } ``` ### Key Changes 1. Inject `TenantDatabaseService` instead of `PrismaService` 2. Get tenant Knex connection: `await this.tenantDbService.getTenantKnex(tenantId)` 3. Use Objection models: `User.query(knex).findOne({ email })` 4. Pass `tenantId` to all service methods (extract from request in controller) ## API Changes ### Tenant Provisioning Endpoints **Create Tenant** ``` POST /setup/tenants Content-Type: application/json { "name": "Company Name", "slug": "company-slug", "primaryDomain": "company", "dbHost": "platform-db", // optional "dbPort": 3306 // optional } Response: { "tenantId": "uuid", "dbName": "tenant_company-slug", "dbUsername": "tenant_company-slug_user", "dbPassword": "generated-password" } ``` **Delete Tenant** ``` DELETE /setup/tenants/:tenantId Response: { "success": true } ``` ## Security Considerations 1. **Encryption**: Tenant database passwords are encrypted with AES-256-CBC before storage 2. **Isolation**: Each tenant has a dedicated MySQL database and user 3. **Credentials**: Database credentials stored in central DB, never exposed to tenants 4. **Subdomain Validation**: Middleware validates tenant exists and is active before processing requests ## Troubleshooting ### Connection Issues Check tenant connection cache: ```typescript await this.tenantDbService.disconnectTenant(tenantId); const knex = await this.tenantDbService.getTenantKnex(tenantId); // Fresh connection ``` ### Migration Issues Run migrations manually: ```bash cd /root/neo/backend npx knex migrate:latest --knexfile=knexfile.js ``` ### Encryption Key Issues If `ENCRYPTION_KEY` is not set, generate one: ```bash node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" ``` ## Next Steps 1. **Generate Central DB Schema** ```bash npx prisma generate --schema=./prisma/schema-central.prisma npx prisma migrate dev --schema=./prisma/schema-central.prisma ``` 2. **Migrate Existing Services** - Start with `AuthService` (most critical) - Then `RBACService`, `ObjectService`, `AppBuilderService` - Update all controllers to extract `tenantId` from request 3. **Frontend Updates** - Update API calls to include subdomain - Test cross-tenant isolation - Update login flow to redirect to tenant subdomain 4. **Testing** - Create multiple test tenants - Verify data isolation - Test subdomain routing - Performance testing with multiple connections 5. **Production Deployment** - Set up wildcard DNS for subdomains - Configure SSL certificates for subdomains - Set up database backup strategy per tenant - Monitor connection pool usage