Migration Plan
A four-phase migration from the WorkSpaceMan JXP/MongoDB stack at api.workshop17.co.za to the AI-powered Proximity Green platform on PostgreSQL, Cloudflare Workers, and Claude AI.
Stack Transformation
WorkSpaceMan (Current)
Proximity Green (Target)
Data Model Migration: 88 Models → ~45 Tables
The 88 MongoDB models at api.workshop17.co.za map to ~45 PostgreSQL tables. Many WSM models consolidate, merge, or become columns on parent tables in a properly normalised relational schema.
Core Entities (direct mapping)
Financial (normalised)
CRM (consolidated)
Infrastructure (adapter pattern)
Models that become columns or are eliminated
| MongoDB Model | In PostgreSQL |
|---|---|
balance | Eliminated — derived from SUM(ledger_entries) |
sentlineitem | Merged into invoice_line_items with sent_at timestamp |
tag | PostgreSQL array column on users |
industrysector | Enum or lookup table |
countries, language, i18n | Static config / i18n library |
spacetype, producttype, wallettype | Enum columns on parent tables |
source, link, pin | Columns on parent entities |
appupdate | Eliminated — handled by deployment pipeline |
config, instancesettings | Environment variables + settings table |
schedule | Eliminated — Inngest handles scheduling (no eval) |
xeroaccount, xeroorgaccount, xerocreditnote, xeropayment | Accounting adapter state in accounting_sync table |
payfasttoken, zapper | Payment adapter state in payment_tokens table |
apikey, token, refreshtoken, handovertoken | Managed by Clerk auth provider |
Migration Phases
Phase A: Data Export & Transform
Weeks 17–18 of build (parallel with Phase 5 frontend work)
Extract all data from the live MongoDB at api.workshop17.co.za and transform into PostgreSQL-compatible format.
Approach
- Use the existing JXP API to export data —
GET /api/{model}?limit=10000for each of the 88 models. The API already supports pagination and field selection. - Claude Code writes the transformation scripts: one per model group (core, financial, CRM, infra)
- Transformations handle: ObjectID → UUID mapping, denormalised fields → foreign keys, Mixed/schemaless fields → typed columns or jsonb
Key Transformations
| Challenge | Approach |
|---|---|
| ObjectID references | Build a global ID map (old ObjectID → new UUID). Process models in dependency order. |
| Encrypted PINs/passwords | Decrypt with legacy cipher (see Fix 1.3), re-encrypt with new method for PostgreSQL. |
| Mongoose Mixed fields | Store as jsonb columns in PostgreSQL. Migrate to typed columns in future iterations. |
| Computed fields in hooks | Recompute from source data during migration. Don't trust cached values (e.g. balance). |
| Xero IDs everywhere | Preserve in accounting_sync table for continuity. New records use the accounting adapter. |
| Wallet balances | Reconstruct from ledger entries, not from the balance model. The balance model is a cache. |
Phase B: API Compatibility Layer
Week 18 (1 week, built by Claude Code)
Build a thin translation layer that accepts JXP-style requests and routes them to the new PostgreSQL-backed API. This allows any existing integrations or scripts that hit api.workshop17.co.za to keep working during transition.
What It Translates
| JXP Pattern | New API Equivalent |
|---|---|
GET /api/user?filter[status]=active | GET /api/v1/users?status=active |
?autopopulate=1 | ?include=organisation,location,membership |
?filter[amount]=$gte:1000 | ?amount_min=1000 |
POST /query/invoice | GET /api/v1/invoices with query params |
POST /aggregate/ledger | GET /api/v1/reports/ledger-summary |
_id (ObjectID) | id (UUID) with old-to-new mapping |
Phase C: Parallel Run & Reconciliation
Weeks 19–20 (2 weeks)
Both systems run simultaneously. The new platform handles all reads and new writes. The old system remains available as a fallback.
Parallel Run Rules
- DNS: New platform goes live on a subdomain (e.g.
app.proximity.green) whilemy.workspaceman.nlstays active - Reads: New platform serves all read traffic from PostgreSQL
- Writes: New platform is the primary. Critical writes (payments, invoices) also logged to reconciliation queue
- Reconciliation: Daily automated check comparing user counts, wallet balances, invoice totals, and booking counts between old and new
Daily Reconciliation Script
// Runs via Inngest every 24 hours
async function reconcile() {
const oldUsers = await fetch('https://api.workshop17.co.za/count/user');
const newUsers = await db.select(count()).from(users);
const oldInvoiceTotal = await fetch(
'https://api.workshop17.co.za/aggregate/invoice',
{ body: [{ $group: { _id: null, total: { $sum: "$total" } } }] }
);
const newInvoiceTotal = await db
.select(sum(invoices.total))
.from(invoices);
// Compare and alert on discrepancies > 1%
if (Math.abs(oldTotal - newTotal) / oldTotal > 0.01) {
await alert('Invoice total mismatch', { oldTotal, newTotal });
}
}
Rollback Criteria
- If balance discrepancies exceed 1% for any user — pause and investigate
- If payment processing fails on the new platform — redirect payments to old system
- If more than 5 user-reported issues in first 48 hours — rollback DNS
Phase D: Cutover & Decommission
End of Week 20
Switch all traffic to the new platform and decommission WorkSpaceMan.
Cutover Sequence
- Final data sync: export any records created in old system during parallel run
- DNS switch:
api.workshop17.co.za→ compatibility layer on new platform - Member portal:
my.workspaceman.nlredirects toapp.proximity.green - CRM:
crm.workspaceman.nlredirects to new CRM interface - Monitor: 48-hour hypercare with on-call developer
- Remove compatibility layer after 30 days (all consumers migrated)
- Archive old MongoDB data (encrypted backup, retain for 12 months)
- Decommission old VPS infrastructure
api.workshop17.co.za docs become a historical reference. The new platform generates its own OpenAPI 3.1 spec at /api/v1/docs with interactive Swagger UI.
AI Features Activated at Launch
The new platform ships with AI capabilities from day one. Here's what's available at cutover vs what comes later:
At Launch (Week 20)
| Feature | AI Model | What It Does |
|---|---|---|
| Member Assistant | Claude Haiku 4.5 | Conversational booking, balance queries, team management |
| Smart Booking | Claude Haiku 4.5 | Room recommendations based on history, preferences, availability |
| Admin Briefing | Claude Sonnet 4.6 | Daily summary of priorities, anomalies, and action items |
| Invoice Anomalies | Claude Sonnet 4.6 | Flags unusual charges before invoice approval |
Post-Launch (Months 2–3)
| Feature | AI Model | Requires |
|---|---|---|
| Churn Prediction | Claude Sonnet 4.6 | 2+ months of check-in and payment data in new system |
| Lead Scoring | Claude Sonnet 4.6 | Historical conversion data from CRM migration |
| Predictive Occupancy | Claude Sonnet 4.6 | 3+ months of booking patterns in new system |
| Dynamic Pricing | Claude Sonnet 4.6 | 6+ months of demand data |
Integration Migration Map
Each external integration migrates through the adapter pattern. Existing service connections continue working; only the internal wiring changes.
| Integration | WSM Implementation | Proximity Green | Migration Impact |
|---|---|---|---|
| Xero | Direct API calls throughout codebase | AccountingProvider adapter | Re-auth OAuth tokens for new platform. All Xero IDs preserved. |
| PayFast | Direct API + stored tokens | PaymentProvider adapter | Stored tokens migrate to payment_tokens table. Customers don't re-enter cards. |
| FreeRADIUS | OpenRadius REST API | NetworkAccessProvider adapter | Same RADIUS server, new API client. Users keep WiFi credentials. |
| PaperCut | SSH commands (vulnerable) | PrintProvider adapter (REST) | Switch from SSH to PaperCut REST API. Print balances preserved. |
| Clay locks | Direct model storage | AccessProvider adapter | Access groups and tags migrate. Physical hardware unchanged. |
| Admyt parking | REST API | ParkingProvider adapter | Same API, new client. Car registrations preserved. |
| Google Pub/Sub | Message queue for jobs | Replaced by Inngest | No migration needed — new event system built from scratch. |
| Elasticsearch | Full-text search | Replaced by Typesense | Re-index from PostgreSQL. No data migration — search index rebuilt. |
| Sentry | Error tracking | Sentry (same) | New DSN, same service. Historical errors stay in old project. |
| SMTP (email) | Nodemailer | Replaced by Resend | Email templates recreated in new system. SMTP credentials not needed. |
Migration Timeline
| Week | Phase | Key Action | Risk Level |
|---|---|---|---|
| 17 | A: Export | Extract all 88 models via JXP API, begin transformation | Low |
| 18 | A+B: Transform + Compat | Load into PostgreSQL, build compatibility layer | Medium |
| 19 | C: Parallel | Both systems live, daily reconciliation, UAT | Medium |
| 20 | D: Cutover | DNS switch, 48-hr hypercare, decommission old | High |
Success Criteria
- All user wallet balances match within R1 (rounding tolerance)
- All active licenses, invoices, and bookings present in new system
- Xero sync continues without interruption
- PayFast stored tokens work for one-click payments
- WiFi and door access credentials function correctly
- Zero unplanned downtime during cutover
- AI assistant responds to 95%+ of common member queries