# Sandbox Mode Implementation Summary ## Overview Sandbox/Test mode provides complete data isolation for testing. Users can toggle between Live and Test modes via a switch in the header. Each mode has its own: - Database schema (for tenant-specific data like appointments, resources, services) - Customer records (filtered by `is_sandbox` flag on User model) ## Architecture ### Backend Components 1. **Tenant Model** (`core/models.py`) - `sandbox_schema_name`: PostgreSQL schema for sandbox data (e.g., `demo_sandbox`) - `sandbox_enabled`: Boolean to enable/disable sandbox for tenant - Auto-generates sandbox schema name on save 2. **SandboxModeMiddleware** (`core/middleware.py:16-118`) - Switches database schema based on: - API token prefix (`ss_test_*` = sandbox, `ss_live_*` = live) - `X-Sandbox-Mode: true` header - Session value `sandbox_mode` - Sets `request.sandbox_mode = True/False` for views to use - MUST run AFTER `SessionMiddleware` in middleware order 3. **User Model** (`smoothschedule/users/models.py`) - `is_sandbox`: Boolean field to mark sandbox customers - Live customers have `is_sandbox=False`, test customers have `is_sandbox=True` 4. **API Endpoints** (`schedule/api_views.py`) - `GET /api/sandbox/status/` - Get current sandbox state - `POST /api/sandbox/toggle/` - Toggle sandbox mode (sets session) 5. **CustomerViewSet** (`schedule/views.py:199-249`) - Filters customers by `request.sandbox_mode` - `perform_create` sets `is_sandbox` based on current mode 6. **StaffViewSet** (`schedule/views.py:302-366`) - Filters staff by `request.sandbox_mode` - Staff created via invitations inherit sandbox mode from request 7. **TicketViewSet** (`tickets/views.py:65-167`) - Filters tickets by `request.sandbox_mode` (except PLATFORM tickets) - `perform_create` sets `is_sandbox` based on current mode - PLATFORM tickets are always created in live mode 8. **PublicCustomerViewSet** (`public_api/views.py:888-968`) - Also filters by sandbox mode for API customers 9. **APIToken Model** (`public_api/models.py`) - `is_sandbox`: Boolean for token type - Key prefixes: `ss_test_*` (sandbox) or `ss_live_*` (live) ### Frontend Components 1. **SandboxContext** (`contexts/SandboxContext.tsx`) - Provides `isSandbox`, `sandboxEnabled`, `toggleSandbox`, `isToggling` - Syncs state to localStorage for API client 2. **SandboxToggle** (`components/SandboxToggle.tsx`) - Toggle switch component with Live/Test labels 3. **SandboxBanner** (`components/SandboxBanner.tsx`) - Orange warning banner shown in test mode 4. **API Client** (`api/client.ts:23-51`) - Reads `localStorage.getItem('sandbox_mode')` - Adds `X-Sandbox-Mode: true` header when in sandbox 5. **BusinessLayout** (`layouts/BusinessLayout.tsx`) - Wrapped with `SandboxProvider` - Shows `SandboxBanner` when in test mode 6. **TopBar** (`components/TopBar.tsx`) - Includes `SandboxToggle` component ### Configuration 1. **CORS** (`config/settings/local.py:75-78`) - `x-sandbox-mode` added to `CORS_ALLOW_HEADERS` 2. **Middleware Order** (`config/settings/multitenancy.py:89-122`) - SandboxModeMiddleware MUST come AFTER SessionMiddleware ## Database Schemas Each tenant has two schemas: - `{tenant_name}` - Live data (e.g., `demo`) - `{tenant_name}_sandbox` - Test data (e.g., `demo_sandbox`) Schemas created via: `python manage.py create_sandbox_schemas` ## What's Isolated | Data Type | Isolation Method | |-----------|------------------| | Appointments/Events | Schema switching (automatic) | | Resources | Schema switching (automatic) | | Services | Schema switching (automatic) | | Payments | Schema switching (automatic) | | Notifications | Schema switching (automatic) | | Communication | Schema switching (automatic) | | Customers | `is_sandbox` field on User model | | Staff Members | `is_sandbox` field on User model | | Tickets (CUSTOMER/STAFF_REQUEST/INTERNAL) | `is_sandbox` field on Ticket model | | Tickets (PLATFORM) | NOT isolated (always live - platform support) | | Business Settings (Tenant) | NOT isolated (shared between modes) | ## Key Files Modified ### Backend - `core/models.py` - Tenant sandbox fields - `core/middleware.py` - SandboxModeMiddleware - `smoothschedule/users/models.py` - User.is_sandbox field - `smoothschedule/users/api_views.py` - accept_invitation_view sets is_sandbox - `schedule/views.py` - CustomerViewSet and StaffViewSet sandbox filtering - `schedule/api_views.py` - sandbox_status_view, sandbox_toggle_view - `tickets/models.py` - Ticket.is_sandbox field - `tickets/views.py` - TicketViewSet sandbox filtering - `public_api/models.py` - APIToken.is_sandbox - `public_api/views.py` - PublicCustomerViewSet sandbox filtering - `config/settings/local.py` - CORS headers - `config/settings/multitenancy.py` - Middleware order, tickets in SHARED_APPS ### Frontend - `src/api/sandbox.ts` - API functions - `src/api/client.ts` - X-Sandbox-Mode header - `src/hooks/useSandbox.ts` - React Query hooks - `src/contexts/SandboxContext.tsx` - Context provider - `src/components/SandboxToggle.tsx` - Toggle UI - `src/components/SandboxBanner.tsx` - Warning banner - `src/components/TopBar.tsx` - Added toggle - `src/layouts/BusinessLayout.tsx` - Provider + banner - `src/i18n/locales/en.json` - Translations ## Migrations ```bash # Migrations for User.is_sandbox and Ticket.is_sandbox fields cd /home/poduck/Desktop/smoothschedule2/smoothschedule docker compose -f docker-compose.local.yml exec django python manage.py migrate ``` ## Current State - ✅ Sandbox mode toggle works - ✅ CORS configured for X-Sandbox-Mode header - ✅ Customer isolation by is_sandbox field implemented - ✅ Staff isolation by is_sandbox field implemented - ✅ Ticket isolation by is_sandbox field implemented (except PLATFORM tickets) - ✅ Appointments/Events/Resources/Services automatically isolated via schema switching - ✅ Existing users are `is_sandbox=False` (live) - ✅ Existing tickets are `is_sandbox=False` (live) - ✅ Test mode shows empty data (clean sandbox)