# Phase 3 API Wiring Design — Convex Data-Source Routing

**Date:** 2026-02-26
**Branch:** feature/convex-phase3 (worktree at `/Users/Asim/Desktop/convex-phase3-worktree`)
**Status:** Approved

---

## Goal

Wire ~32 remaining API route files with `getDataSource()` guards so each domain can be switched from Prisma to Convex via a single env var (`DS_<DOMAIN>=convex`). All DS\_\* vars remain `prisma` by default — nothing activates until explicitly flipped.

---

## Architecture

### The Pattern (identical for every file)

```typescript
import { getDataSource } from "@/lib/data-source";

// Added BEFORE existing Prisma code in each handler:
if (getDataSource("domain") === "convex") {
  try {
    const { convexQuery } = await import("@/lib/convex-client");
    const { api } = await import("@/convex/_generated/api");
    const result = await convexQuery(api.domain.queries.fn, { ...args });
    if (result !== null)
      return NextResponse.json({ data: result, success: true });
  } catch {
    // Fall through to Prisma on any error
  }
}
// ... existing Prisma code unchanged below ...
```

### Stub pattern (routes with no matching Convex function yet)

```typescript
if (getDataSource("domain") === "convex") {
  return NextResponse.json({ data: [], success: true });
}
```

### Key properties

- **Additive only** — zero Prisma code modified
- **Safe fallback** — try/catch ensures Prisma path runs on any Convex failure
- **Zero activation** — DS\_\* default to `prisma`, no behaviour change until env var flipped
- **Dynamic imports** — Convex client only loaded when `DS_DOMAIN=convex` is set

---

## Agent Assignments (8 parallel agents, 4 files each)

### A1 — social-calendar

| File                                        | Convex function                                                                |
| ------------------------------------------- | ------------------------------------------------------------------------------ |
| `app/api/social/calendar/route.ts`          | `api.social.queries.getContentQueue({ socialAccountId, status?, limit? })`     |
| `app/api/social/calendar/drafts/route.ts`   | `api.social.queries.getContentQueue({ socialAccountId, status: 'DRAFT' })`     |
| `app/api/social/calendar/upcoming/route.ts` | `api.social.queries.getContentQueue({ socialAccountId, status: 'SCHEDULED' })` |
| `app/api/social/calendar/[itemId]/route.ts` | stub `{ item: null, success: true }`                                           |

### A2 — social-inbox-trends

| File                                          | Convex function                                               |
| --------------------------------------------- | ------------------------------------------------------------- |
| `app/api/social/inbox/route.ts`               | `api.social.queries.getMessages({ socialAccountId, limit? })` |
| `app/api/social/inbox/unread-counts/route.ts` | stub `{ counts: {}, total: 0 }`                               |
| `app/api/social/trends/route.ts`              | stub `{ trends: [], success: true }`                          |
| `app/api/social/ai/history/route.ts`          | stub `{ history: [], success: true }`                         |

### A3 — leads-core

| File                              | Convex function                                           |
| --------------------------------- | --------------------------------------------------------- |
| `app/api/leads/route.ts`          | `api.leads.queries.getLeads({ userId, status?, limit? })` |
| `app/api/leads/[id]/route.ts`     | `api.leads.queries.getById({ id })`                       |
| `app/api/leads/pipeline/route.ts` | stub `{ stages: [], leads: [] }`                          |
| `app/api/leads/stats/route.ts`    | stub `{ total: 0, byStatus: {} }`                         |

### A4 — leads-sub

| File                                     | Convex function                                           |
| ---------------------------------------- | --------------------------------------------------------- |
| `app/api/leads/[id]/activities/route.ts` | `api.leads.queries.getActivities({ leadId: id, limit? })` |
| `app/api/leads/scoring/rules/route.ts`   | stub `{ rules: [] }`                                      |
| `app/api/leads/nurture/route.ts`         | stub `{ sequences: [] }`                                  |
| `app/api/leads/capture-forms/route.ts`   | stub `{ forms: [] }`                                      |

### A5 — billing-support

| File                               | Convex function                                                    |
| ---------------------------------- | ------------------------------------------------------------------ |
| `app/api/billing/current/route.ts` | `api.billing.queries.getSubscription({ userId })`                  |
| `app/api/billing/history/route.ts` | `api.billing.queries.getBillingHistory({ customerId, limit: 20 })` |
| `app/api/support/tickets/route.ts` | `api.support.queries.getTickets({ userId, status?, limit? })`      |
| `app/api/support/faqs/route.ts`    | stub `{ faqs: [] }`                                                |

### A6 — support-voice

| File                                             | Convex function                                           |
| ------------------------------------------------ | --------------------------------------------------------- |
| `app/api/support/tickets/[id]/route.ts`          | `api.support.queries.getTicketById({ id })`               |
| `app/api/support/tickets/[id]/messages/route.ts` | `api.support.queries.getTicketMessages({ ticketId: id })` |
| `app/api/voice-agent/config/route.ts`            | `api.voice_agents.queries.getPhoneConfig({ userId })`     |
| `app/api/voice-agent/analytics/route.ts`         | stub `{ analytics: [], success: true }`                   |

### A7 — quotations-reviews

| File                                         | Convex function                                                     |
| -------------------------------------------- | ------------------------------------------------------------------- |
| `app/api/dashboard/quotations/route.ts`      | `api.quotations.queries.getQuotations({ userId, status?, limit? })` |
| `app/api/dashboard/quotations/[id]/route.ts` | `api.quotations.queries.getById({ id })`                            |
| `app/api/dashboard/reviews/route.ts`         | `api.reviews.queries.getReviews({ userId, limit? })`                |
| `app/api/dashboard/reviews/stats/route.ts`   | stub `{ total: 0, averageRating: 0 }`                               |

### A8 — expenses-realestate

| File                                      | Convex function                                             |
| ----------------------------------------- | ----------------------------------------------------------- |
| `app/api/dashboard/expenses/route.ts`     | `api.expenses.queries.getExpenses({ userId, limit? })`      |
| `app/api/real-estate/properties/route.ts` | `api.real_estate.queries.getProperties({ userId, limit? })` |
| `app/api/real-estate/brokers/route.ts`    | `api.real_estate.queries.getBrokers({ organizationId })`    |
| `app/api/analytics/events/route.ts`       | `api.analytics.queries.getEvents({ userId, limit? })`       |

---

## Routes Excluded (by design)

| Route type                                   | Reason                                                        |
| -------------------------------------------- | ------------------------------------------------------------- |
| `webhooks/*`, `cron/*`                       | External triggers — must stay in Next.js/Prisma               |
| `social/cron/*`, `social/auth/*`             | Cron jobs + OAuth callbacks — external I/O                    |
| `voice-agent/server-tools/*`                 | n8n tool endpoints — external callers                         |
| `voice-agent/webhook`, `voice-agent/kb-sync` | ElevenLabs integration — external I/O                         |
| `integrations/whatsapp/webhook`              | Twilio webhook — external caller                              |
| `staff/analytics/*`                          | Internal staff tools — low priority, no Convex equivalent yet |

---

## Completion Criteria

1. All 32 files edited with Convex guard
2. `npx tsc --noEmit` in worktree — 0 new errors
3. Fix string-format bug in `app/api/bookings/route.ts` (`'bookings:getBookings'` → `api.bookings.queries.getBookings`)
4. Commit: `feat(convex): wire phase-3 API route data-source routing (32 routes)`
5. Merge worktree branch → `performance-fixes-local`
