{"slug":"operations/WORK_ROUTER_MIGRATION","title":"Work Router - Migration & Enhancement Guide","section":"operations","sectionLabel":"Operations","status":"unclassified","type":"document","path":"operations/WORK_ROUTER_MIGRATION.mdx","bytes":14809,"description":null,"updated":null,"source":"integratewise/Integratewise-docs-wiki","content":"\nThis guide explains how to evolve the Work Router system from development (InMemory) to production (Drizzle + Neon), and how to add real data/features.\n\n## Phase 1: Auth Integration (Required First)\n\n### Current State\n\n- Demo user hardcoded in pages\n- No real Descope integration\n\n### Migration Steps\n\n#### Step 1: Create Auth Helper\n\nFile: `lib/auth.ts`\n\n```typescript\n\nexport async function getAuthenticatedUser() {\n  try {\n    const user = await getSessionUser();\n    if (!user) return null;\n\n    return {\n      userId: user.loginIds?.[0] || user.userId,\n      email: user.email,\n      tenantId: user.customAttributes?.tenantId || \"default-tenant\",\n      name: user.name || \"User\",\n    };\n  } catch (error) {\n    console.error(\"[v0] Auth error:\", error);\n    return null;\n  }\n}\n```\n\n#### Step 2: Update Onboarding Page\n\nFile: `app/onboarding/page.tsx`\n\n```typescript\n\n  const user = await getAuthenticatedUser();\n\n  if (!user) {\n    redirect('/auth/login');\n  }\n\n  return (\n    <OnboardingContainer\n      userId={user.userId}\n      tenantId={user.tenantId}\n      initialName={user.name}\n      initialEmail={user.email}\n    />\n  );\n}\n```\n\n#### Step 3: Update Work Page\n\nFile: `app/work/page.tsx`\n\n```typescript\n\n  const user = await getAuthenticatedUser();\n\n  if (!user) {\n    redirect('/auth/login');\n  }\n\n  // Convert to server component\n  return <WorkViewContainer userId={user.userId} tenantId={user.tenantId} />;\n}\n```\n\n**Test:** Visit `/onboarding` → Descope login → Complete flow → Redirects to `/work`\n\n---\n\n## Phase 2: Real Sections (Medium Priority)\n\n### Current State\n\n- 50+ placeholder section components\n- All sections show \"coming soon\" text\n\n### Migration Strategy\n\n### Step 1: Identify Section Templates\n\nFind existing dashboard/section implementations in the codebase to use as reference:\n\n- Sales dashboard template\n- Customer success section\n- Financial dashboard\n- Operations queue\n\n### Step 2: Create Section Components\n\nFile structure:\n\n```\ncomponents/work/sections/\n├── company-overview.tsx\n├── revenue.tsx\n├── pipeline.tsx\n├── accounts.tsx\n├── opportunities.tsx\n├── campaigns.tsx\n├── operations/\n│   ├── tasks.tsx\n│   ├── queues.tsx\n│   └── approvals.tsx\n└── admin/\n    ├── users.tsx\n    ├── roles.tsx\n    └── audit.tsx\n```\n\n### Step 3: Update Section Map\n\nFile: `app/work/page.tsx`\n\n```typescript\n// ... import all sections\n\nconst SECTION_COMPONENTS: Record<string, React.ReactNode> = {\n  'company-overview': <CompanyOverviewSection />,\n  'revenue': <RevenueSection />,\n  'pipeline': <PipelineSection />,\n  // ... all sections\n};\n```\n\n### Example Section Component\n\n```typescript\n// components/work/sections/revenue.tsx\n\n  const [data, setData] = useState(null);\n\n  useEffect(() => {\n    async function loadData() {\n      // Call your API\n      const response = await fetch('/api/metrics/revenue');\n      setData(await response.json());\n    }\n    loadData();\n  }, []);\n\n  return (\n    <div className=\"space-y-4\">\n      <div className=\"grid grid-cols-2 gap-4\">\n        <div className=\"p-3 bg-secondary rounded\">\n          <div className=\"text-xs text-muted-foreground\">MRR</div>\n          <div className=\"text-lg font-semibold\">${data?.mrr || '—'}</div>\n        </div>\n        <div className=\"p-3 bg-secondary rounded\">\n          <div className=\"text-xs text-muted-foreground\">ARR</div>\n          <div className=\"text-lg font-semibold\">${data?.arr || '—'}</div>\n        </div>\n      </div>\n      \n    </div>\n  );\n}\n```\n\n---\n\n## Phase 3: Database Migration (Production)\n\n### Current State\n\n- InMemoryDataProvider (ephemeral)\n- No persistence across sessions\n\n### Goal\n\n- DrizzleDataProvider with Neon Postgres\n- Persistent user profiles and audit logs\n\n### Migration Steps\n\n#### Step 1: Setup Neon\n\nAlready configured in project (env vars present)\n\n#### Step 2: Create Drizzle Schema\n\nFile: `lib/data/schema.ts`\n\n```typescript\n\n  id: uuid(\"id\").primaryKey().defaultRandom(),\n  tenantId: uuid(\"tenant_id\").notNull(),\n  descopeUserId: text(\"descope_user_id\").notNull(),\n  name: text(\"name\"),\n  email: text(\"email\"),\n  avatarUrl: text(\"avatar_url\"),\n  onboardedAt: timestamp(\"onboarded_at\"),\n  createdAt: timestamp(\"created_at\").defaultNow(),\n  updatedAt: timestamp(\"updated_at\").defaultNow(),\n});\n\n  id: uuid(\"id\").primaryKey().defaultRandom(),\n  userId: uuid(\"user_id\").notNull(),\n  tenantId: uuid(\"tenant_id\").notNull(),\n  name: text(\"name\").notNull(),\n  email: text(\"email\").notNull(),\n  role: varchar(\"role\", { length: 50 }).notNull(),\n  department: varchar(\"department\", { length: 50 }).notNull(),\n  industry: varchar(\"industry\", { length: 50 }).notNull(),\n  onboardedAt: timestamp(\"onboarded_at\").notNull(),\n  createdAt: timestamp(\"created_at\").defaultNow(),\n  updatedAt: timestamp(\"updated_at\").defaultNow(),\n});\n\n  id: uuid(\"id\").primaryKey().defaultRandom(),\n  tenantId: uuid(\"tenant_id\").notNull(),\n  actorId: uuid(\"actor_id\").notNull(),\n  action: text(\"action\").notNull(),\n  entity: text(\"entity\").notNull(),\n  entityId: text(\"entity_id\"),\n  metadata: jsonb(\"metadata\"),\n  createdAt: timestamp(\"created_at\").defaultNow(),\n});\n\n// ... other tables\n```\n\n#### Step 3: Create DrizzleDataProvider\n\nFile: `lib/data/drizzle-provider.ts`\n\n```typescript\nimport { db } from \"@/lib/db\"; // Your Drizzle instance\n\nexport class DrizzleDataProvider implements DataProvider {\n  profiles = {\n    async get(userId: string): Promise<UserProfile | null> {\n      const result = await db\n        .select()\n        .from(userProfiles)\n        .where(eq(userProfiles.userId, userId))\n        .limit(1);\n\n      return result[0] || null;\n    },\n\n    async create(profile: Omit<UserProfile, \"id\" | \"createdAt\" | \"updatedAt\">) {\n      const result = await db.insert(userProfiles).values(profile).returning();\n\n      return result[0];\n    },\n\n    // ... other methods\n  };\n\n  // ... other repos\n}\n```\n\n#### Step 4: Update Provider Factory\n\nFile: `lib/data/index.ts`\n\n```typescript\n\nlet provider: DataProvider | null = null;\n\n  if (!provider) {\n    const isProd = getEnv(\"NODE_ENV\") === \"production\";\n    provider = isProd ? new DrizzleDataProvider() : new InMemoryDataProvider();\n  }\n  return provider;\n}\n```\n\n#### Step 5: Create Migrations\n\nFile: `migrations/0001_initial.sql`\n\n```sql\n-- Create tenants table\nCREATE TABLE IF NOT EXISTS tenants (\n  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n  name VARCHAR(255) NOT NULL,\n  slug VARCHAR(255) UNIQUE NOT NULL,\n  industry VARCHAR(50),\n  environment VARCHAR(50),\n  created_at TIMESTAMP DEFAULT NOW(),\n  updated_at TIMESTAMP DEFAULT NOW()\n);\n\n-- Create users table\nCREATE TABLE IF NOT EXISTS users (\n  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n  tenant_id UUID NOT NULL REFERENCES tenants(id),\n  descope_user_id TEXT UNIQUE NOT NULL,\n  name TEXT,\n  email TEXT,\n  avatar_url TEXT,\n  onboarded_at TIMESTAMP,\n  created_at TIMESTAMP DEFAULT NOW(),\n  updated_at TIMESTAMP DEFAULT NOW()\n);\n\n-- Create user_profiles table\nCREATE TABLE IF NOT EXISTS user_profiles (\n  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n  user_id UUID NOT NULL REFERENCES users(id),\n  tenant_id UUID NOT NULL REFERENCES tenants(id),\n  name TEXT NOT NULL,\n  email TEXT NOT NULL,\n  role VARCHAR(50) NOT NULL,\n  department VARCHAR(50) NOT NULL,\n  industry VARCHAR(50) NOT NULL,\n  onboarded_at TIMESTAMP NOT NULL,\n  created_at TIMESTAMP DEFAULT NOW(),\n  updated_at TIMESTAMP DEFAULT NOW()\n);\n\n-- Create indexes\nCREATE INDEX idx_user_profiles_user_id ON user_profiles(user_id);\nCREATE INDEX idx_user_profiles_tenant_id ON user_profiles(tenant_id);\nCREATE INDEX idx_users_tenant_id ON users(tenant_id);\n```\n\n#### Step 6: Run Migrations\n\n```bash\n# Using Drizzle CLI\npnpm drizzle-kit push\n\n# Or manually via Neon console\n```\n\n#### Step 7: Test\n\n- Switch to production mode\n- Test onboarding flow\n- Verify data persists in Neon\n- Verify profile loads on subsequent visits\n\n---\n\n## Phase 4: Permissions Engine\n\n### Current State\n\n- Basic section gating by permissions\n- No fine-grained action control\n\n### Enhancement\n\n#### Step 1: Expand Permission Model\n\nFile: `lib/data/types.ts`\n\n```typescript\nexport interface Permission {\n  id: string;\n  key: string;\n  resource: string; // 'section', 'action', 'feature'\n  scope: \"global\" | \"tenant\" | \"user\";\n  description: string;\n  createdAt: string;\n}\n\nexport interface RolePermissionGrant {\n  id: string;\n  roleId: string;\n  permissionId: string;\n  deniedAt?: string; // Soft revoke\n  createdAt: string;\n}\n```\n\n#### Step 2: Update Router\n\nFile: `lib/work/router.ts`\n\n```typescript\n  permissions: Permission[],\n  resource: string,\n  action: string\n): boolean {\n  const key = `${resource}:${action}`;\n  return permissions.some((p) => p.key === key);\n}\n\n// In resolveWorkView:\nexport async function resolveWorkView(context: WorkContext): Promise<ResolvedWorkView> {\n  // ... existing code ...\n\n  // For each section, check if user can access\n  let filteredSections = roleDefinition.sections.filter((section) => {\n    // Existing permission checks...\n\n    // New fine-grained checks\n    if (!canUserAction(permissions, section.id, \"view\")) {\n      return false;\n    }\n\n    return true;\n  });\n\n  return {\n    /* ... */\n  };\n}\n```\n\n#### Step 3: Gate Actions in Components\n\n```typescript\n// components/work/sections/revenue.tsx\n  const canExport = permissions.some(p => p.key === 'revenue:export');\n  const canEdit = permissions.some(p => p.key === 'revenue:edit');\n\n  return (\n    <div>\n      \n      {canExport && <button>Export</button>}\n      {canEdit && <button>Edit Targets</button>}\n    </div>\n  );\n}\n```\n\n---\n\n## Phase 5: Settings & Preferences\n\n### Add Settings Page\n\nFile: `app/settings/page.tsx`\n\n```typescript\n  const user = await getAuthenticatedUser();\n  const provider = getDataProvider();\n\n  const profile = await provider.profiles.get(user.userId);\n\n  return (\n    <div>\n      <h1>Workspace Settings</h1>\n\n      \n      <UserProfileForm profile={profile} />\n\n      \n      <WorkspacePreferencesForm profile={profile} />\n\n      \n      <ActivityLog userId={user.userId} />\n    </div>\n  );\n}\n```\n\n---\n\n## Implementation Roadmap\n\n| Phase            | Priority | Effort   | Dependencies      | Blocker |\n| ---------------- | -------- | -------- | ----------------- | ------- |\n| Auth Integration | CRITICAL | 2-4h     | Descope SDK       | YES     |\n| Real Sections    | HIGH     | 3-5 days | Section templates | NO      |\n| Database         | HIGH     | 2-3 days | Neon setup        | NO      |\n| Permissions      | MEDIUM   | 1-2 days | DB schema         | NO      |\n| Settings         | MEDIUM   | 1 day    | Permissions       | NO      |\n| Audit Logging    | LOW      | 1 day    | DB schema         | NO      |\n\n---\n\n## Testing Checklist for Each Phase\n\n### Phase 1: Auth\n\n- [ ] Descope login redirects to onboarding\n- [ ] Auth token passed to backend\n- [ ] TenantId correctly set\n- [ ] Logout works and redirects to login\n\n### Phase 2: Sections\n\n- [ ] Each section renders without errors\n- [ ] Section data loads from API\n- [ ] Section state updates correctly\n- [ ] Error states display gracefully\n- [ ] All 50+ sections work\n\n### Phase 3: Database\n\n- [ ] Profile persists across sessions\n- [ ] Multiple users have separate profiles\n- [ ] Audit logs record all actions\n- [ ] Data is tenant-scoped\n- [ ] No data leaks between tenants\n\n### Phase 4: Permissions\n\n- [ ] Sections hidden when lacking permissions\n- [ ] Actions hidden when lacking permissions\n- [ ] Permission inheritance from role works\n- [ ] Permission revocation works\n- [ ] Upselling shows for restricted features\n\n### Phase 5: Settings\n\n- [ ] User can update profile\n- [ ] User can change preferences\n- [ ] Activity feed shows history\n- [ ] Changes audit logged\n- [ ] Permissions respected\n\n---\n\n## Troubleshooting\n\n### \"Profile not found\" error\n\n- [ ] Check Descope user is in tenant\n- [ ] Verify onboarding was completed\n- [ ] Check DataProvider is initialized\n- [ ] In Drizzle phase: verify DB migrations ran\n\n### \"Sections not rendering\"\n\n- [ ] Check section component imports\n- [ ] Verify SECTION_COMPONENTS map is complete\n- [ ] Check for TypeScript errors\n- [ ] Verify data API is responding\n\n### \"Permission denied\" errors\n\n- [ ] Check role-permission mappings in seed data\n- [ ] Verify permission keys match\n- [ ] In Drizzle phase: check DB seeding\n\n### Database connection errors\n\n- [ ] Verify Neon credentials in env\n- [ ] Check network connectivity\n- [ ] Verify migrations ran successfully\n- [ ] Check SQL syntax in schema\n\n---\n\n## Performance Optimization Tips\n\n1. **Caching** - Cache resolved views in Redis\n2. **Lazy Loading** - Load sections on-demand not all at once\n3. **Image Optimization** - Use Next.js Image for section icons\n4. **Data Fetching** - Use SWR for section data\n5. **Query Optimization** - Add indexes for frequently queried fields\n\n---\n\n## Deployment Checklist\n\n- [ ] All sections implemented\n- [ ] Auth integrated and tested\n- [ ] Database migrations deployed\n- [ ] Permissions configured\n- [ ] Audit logging enabled\n- [ ] Error monitoring set up\n- [ ] Performance tested\n- [ ] Security review passed\n- [ ] User documentation complete\n- [ ] Staged rollout plan created\n\n---\n\nFor questions or issues, refer to:\n\n- `WORK_ROUTER_IMPLEMENTATION.md` - Technical details\n- `WORK_ROUTER_QUICKSTART.md` - Quick reference\n- `WORK_ROUTER_DELIVERY.md` - What's delivered\n"}