# Phase 3 API Wiring Implementation Plan

> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

**Goal:** Wire 32 API route files with `getDataSource()` guards so each domain can be switched from Prisma to Convex via `DS_<DOMAIN>=convex` without touching any existing Prisma code.

**Architecture:** Every handler gets an additive guard at its start — if `DS_DOMAIN=convex`, call `convexQuery(api.domain.queries.fn, args)` and return; otherwise fall through to existing Prisma code unchanged. Routes with no matching Convex function return a safe empty stub. All DS\_\* env vars default to `prisma` so nothing activates until explicitly flipped.

**Tech Stack:** Next.js App Router, `lib/data-source.ts` (`getDataSource`), `lib/convex-client.ts` (`convexQuery`), `convex/_generated/api` (`api`), TypeScript strict mode.

---

## Prerequisites

**Worktree path:** `/Users/Asim/Desktop/convex-phase3-worktree/mawidi-site/`
All file edits happen inside this worktree. Do NOT edit the main repo files.

**The guard pattern (copy-paste template):**

```typescript
// ADD at top of file (after existing imports):
import { getDataSource } from "@/lib/data-source";

// ADD inside each GET/POST handler, BEFORE the first line of existing code:
if (getDataSource("DOMAIN_NAME") === "convex") {
  try {
    const { convexQuery } = await import("@/lib/convex-client");
    const { api } = await import("@/convex/_generated/api");
    const result = await convexQuery(api.DOMAIN.queries.FUNCTION, { ...ARGS });
    if (result !== null) {
      return NextResponse.json({ data: result, success: true });
    }
  } catch {
    // Fall through to Prisma
  }
}
```

**Stub pattern (for routes with no matching Convex function):**

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

**Auth variable:** Most routes get `user` from `getAuthenticatedUser(request, auth)` or similar. The userId passed to Convex is `user.userId` (note: `.userId` not `.id` — check the actual variable name in each file before wiring).

---

## Task 0: Fix Existing Bookings String Bug

**Files:**

- Modify: `app/api/bookings/route.ts`
- Modify: `app/api/notifications/route.ts`

**Step 1: Read current bookings route**

```bash
grep -n "convexQuery\|bookings:get\|notifications:get" \
  app/api/bookings/route.ts \
  app/api/notifications/route.ts
```

**Step 2: Fix string-format bug in bookings/route.ts**

Find the line:

```typescript
const result = await convexQuery("bookings:getBookings", { userId: user.id });
```

Replace with:

```typescript
const { api } = await import("@/convex/_generated/api");
const result = await convexQuery(api.bookings.queries.getBookings, {
  userId: user.userId ?? user.id,
});
```

**Step 3: Fix same bug in notifications/route.ts if present**

Same pattern — replace any string function reference with `api.notifications.queries.FUNCTION`.

**Step 4: Verify TypeScript**

```bash
npx tsc --noEmit 2>&1 | grep -E "bookings|notifications" | head -10
```

Expected: no errors on these files.

---

## Task 1 (Parallel Group A) — Social Calendar (4 files)

**Files:**

- Modify: `app/api/social/calendar/route.ts`
- Modify: `app/api/social/calendar/drafts/route.ts`
- Modify: `app/api/social/calendar/upcoming/route.ts`
- Modify: `app/api/social/calendar/[itemId]/route.ts`

**Domain:** `'social'` | **Convex function:** `api.social.queries.getContentQueue`

**Step 1: Read each file to find the auth variable and handler structure**

```bash
head -40 app/api/social/calendar/route.ts
```

**Step 2: Wire `calendar/route.ts` GET handler**

Add import, then guard at start of GET:

```typescript
if (getDataSource("social") === "convex") {
  try {
    const { convexQuery } = await import("@/lib/convex-client");
    const { api } = await import("@/convex/_generated/api");
    const socialAccountId =
      request.nextUrl.searchParams.get("socialAccountId") ?? "";
    const status = request.nextUrl.searchParams.get("status") ?? undefined;
    const result = await convexQuery(api.social.queries.getContentQueue, {
      socialAccountId,
      status,
      limit: 50,
    });
    if (result !== null)
      return NextResponse.json({ items: result, success: true });
  } catch {
    /* fall through */
  }
}
```

**Step 3: Wire `calendar/drafts/route.ts`**

Same pattern, hardcode `status: 'DRAFT'`:

```typescript
if (getDataSource("social") === "convex") {
  try {
    const { convexQuery } = await import("@/lib/convex-client");
    const { api } = await import("@/convex/_generated/api");
    const socialAccountId =
      request.nextUrl.searchParams.get("socialAccountId") ?? "";
    const result = await convexQuery(api.social.queries.getContentQueue, {
      socialAccountId,
      status: "DRAFT",
      limit: 50,
    });
    if (result !== null)
      return NextResponse.json({ drafts: result, success: true });
  } catch {
    /* fall through */
  }
}
```

**Step 4: Wire `calendar/upcoming/route.ts`**

Same, hardcode `status: 'SCHEDULED'`:

```typescript
if (getDataSource("social") === "convex") {
  try {
    const { convexQuery } = await import("@/lib/convex-client");
    const { api } = await import("@/convex/_generated/api");
    const socialAccountId =
      request.nextUrl.searchParams.get("socialAccountId") ?? "";
    const result = await convexQuery(api.social.queries.getContentQueue, {
      socialAccountId,
      status: "SCHEDULED",
      limit: 20,
    });
    if (result !== null)
      return NextResponse.json({ items: result, success: true });
  } catch {
    /* fall through */
  }
}
```

**Step 5: Wire `calendar/[itemId]/route.ts` — stub**

```typescript
if (getDataSource("social") === "convex") {
  return NextResponse.json({ item: null, success: true });
}
```

---

## Task 2 (Parallel Group A) — Social Inbox + Trends (4 files)

**Files:**

- Modify: `app/api/social/inbox/route.ts`
- Modify: `app/api/social/inbox/unread-counts/route.ts`
- Modify: `app/api/social/trends/route.ts`
- Modify: `app/api/social/ai/history/route.ts`

**Domain:** `'social'` | **Convex function:** `api.social.queries.getMessages`

**Step 1: Wire `inbox/route.ts` GET**

```typescript
if (getDataSource("social") === "convex") {
  try {
    const { convexQuery } = await import("@/lib/convex-client");
    const { api } = await import("@/convex/_generated/api");
    const socialAccountId =
      request.nextUrl.searchParams.get("socialAccountId") ?? "";
    const result = await convexQuery(api.social.queries.getMessages, {
      socialAccountId,
      limit: 50,
    });
    if (result !== null)
      return NextResponse.json({ conversations: result, success: true });
  } catch {
    /* fall through */
  }
}
```

**Step 2: Stub `inbox/unread-counts/route.ts`**

```typescript
if (getDataSource("social") === "convex") {
  return NextResponse.json({ counts: {}, total: 0, success: true });
}
```

**Step 3: Stub `trends/route.ts`**

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

**Step 4: Stub `ai/history/route.ts`**

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

---

## Task 3 (Parallel Group B) — Leads Core (4 files)

**Files:**

- Modify: `app/api/leads/route.ts`
- Modify: `app/api/leads/[id]/route.ts`
- Modify: `app/api/leads/pipeline/route.ts`
- Modify: `app/api/leads/stats/route.ts`

**Domain:** `'leads'` | **Convex functions:** `api.leads.queries.getLeads`, `api.leads.queries.getById`

**Step 1: Wire `leads/route.ts` GET**

```typescript
if (getDataSource("leads") === "convex") {
  try {
    const { convexQuery } = await import("@/lib/convex-client");
    const { api } = await import("@/convex/_generated/api");
    const status = request.nextUrl.searchParams.get("status") ?? undefined;
    const result = await convexQuery(api.leads.queries.getLeads, {
      userId: user.userId,
      status,
      limit: 50,
    });
    if (result !== null)
      return NextResponse.json({
        leads: result,
        total: result.length,
        success: true,
      });
  } catch {
    /* fall through */
  }
}
```

**Step 2: Wire `leads/[id]/route.ts` GET**

```typescript
if (getDataSource("leads") === "convex") {
  try {
    const { convexQuery } = await import("@/lib/convex-client");
    const { api } = await import("@/convex/_generated/api");
    const result = await convexQuery(api.leads.queries.getById, {
      id: params.id as any,
    });
    if (result !== null)
      return NextResponse.json({ lead: result, success: true });
  } catch {
    /* fall through */
  }
}
```

**Step 3: Stub `pipeline/route.ts`**

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

**Step 4: Stub `stats/route.ts`**

```typescript
if (getDataSource("leads") === "convex") {
  return NextResponse.json({ total: 0, byStatus: {}, success: true });
}
```

---

## Task 4 (Parallel Group B) — Leads Sub-routes (4 files)

**Files:**

- Modify: `app/api/leads/[id]/activities/route.ts`
- Modify: `app/api/leads/scoring/rules/route.ts`
- Modify: `app/api/leads/nurture/route.ts`
- Modify: `app/api/leads/capture-forms/route.ts`

**Domain:** `'leads'` | **Convex function:** `api.leads.queries.getActivities`

**Step 1: Wire `[id]/activities/route.ts` GET**

```typescript
if (getDataSource("leads") === "convex") {
  try {
    const { convexQuery } = await import("@/lib/convex-client");
    const { api } = await import("@/convex/_generated/api");
    const result = await convexQuery(api.leads.queries.getActivities, {
      leadId: params.id,
      limit: 50,
    });
    if (result !== null)
      return NextResponse.json({ activities: result, success: true });
  } catch {
    /* fall through */
  }
}
```

**Step 2: Stub remaining 3 files**

`scoring/rules/route.ts`: `return NextResponse.json({ rules: [], success: true });`
`nurture/route.ts`: `return NextResponse.json({ sequences: [], success: true });`
`capture-forms/route.ts`: `return NextResponse.json({ forms: [], success: true });`

---

## Task 5 (Parallel Group C) — Billing + Support Top-level (4 files)

**Files:**

- Modify: `app/api/billing/current/route.ts`
- Modify: `app/api/billing/history/route.ts`
- Modify: `app/api/support/tickets/route.ts`
- Modify: `app/api/support/faqs/route.ts`

**Domains:** `'billing'`, `'support'`

**Step 1: Wire `billing/current/route.ts` GET**

```typescript
if (getDataSource("billing") === "convex") {
  try {
    const { convexQuery } = await import("@/lib/convex-client");
    const { api } = await import("@/convex/_generated/api");
    const result = await convexQuery(api.billing.queries.getSubscription, {
      userId: user.userId,
    });
    if (result !== null)
      return NextResponse.json({ subscription: result, success: true });
  } catch {
    /* fall through */
  }
}
```

**Step 2: Wire `billing/history/route.ts` GET**

```typescript
if (getDataSource("billing") === "convex") {
  try {
    const { convexQuery } = await import("@/lib/convex-client");
    const { api } = await import("@/convex/_generated/api");
    const result = await convexQuery(api.billing.queries.getBillingHistory, {
      customerId: user.stripeCustomerId ?? user.userId,
      limit: 20,
    });
    if (result !== null)
      return NextResponse.json({ history: result, success: true });
  } catch {
    /* fall through */
  }
}
```

**Step 3: Wire `support/tickets/route.ts` GET**

```typescript
if (getDataSource("support") === "convex") {
  try {
    const { convexQuery } = await import("@/lib/convex-client");
    const { api } = await import("@/convex/_generated/api");
    const status = request.nextUrl.searchParams.get("status") ?? undefined;
    const result = await convexQuery(api.support.queries.getTickets, {
      userId: user.userId,
      status,
      limit: 20,
    });
    if (result !== null)
      return NextResponse.json({
        tickets: result,
        total: result.length,
        success: true,
      });
  } catch {
    /* fall through */
  }
}
```

**Step 4: Stub `support/faqs/route.ts`**

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

---

## Task 6 (Parallel Group C) — Support Sub + Voice Config (4 files)

**Files:**

- Modify: `app/api/support/tickets/[id]/route.ts`
- Modify: `app/api/support/tickets/[id]/messages/route.ts`
- Modify: `app/api/voice-agent/config/route.ts`
- Modify: `app/api/voice-agent/analytics/route.ts`

**Domains:** `'support'`, `'voice_agents'`

**Step 1: Wire `tickets/[id]/route.ts` GET**

```typescript
if (getDataSource("support") === "convex") {
  try {
    const { convexQuery } = await import("@/lib/convex-client");
    const { api } = await import("@/convex/_generated/api");
    const result = await convexQuery(api.support.queries.getTicketById, {
      id: params.id as any,
    });
    if (result !== null)
      return NextResponse.json({ ticket: result, success: true });
  } catch {
    /* fall through */
  }
}
```

**Step 2: Wire `tickets/[id]/messages/route.ts` GET**

```typescript
if (getDataSource("support") === "convex") {
  try {
    const { convexQuery } = await import("@/lib/convex-client");
    const { api } = await import("@/convex/_generated/api");
    const result = await convexQuery(api.support.queries.getTicketMessages, {
      ticketId: params.id,
      limit: 50,
    });
    if (result !== null)
      return NextResponse.json({ messages: result, success: true });
  } catch {
    /* fall through */
  }
}
```

**Step 3: Wire `voice-agent/config/route.ts` GET**

```typescript
if (getDataSource("voice_agents") === "convex") {
  try {
    const { convexQuery } = await import("@/lib/convex-client");
    const { api } = await import("@/convex/_generated/api");
    const result = await convexQuery(api.voice_agents.queries.getPhoneConfig, {
      userId: user.userId,
    });
    if (result !== null)
      return NextResponse.json({ config: result, success: true });
  } catch {
    /* fall through */
  }
}
```

**Step 4: Stub `voice-agent/analytics/route.ts`**

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

---

## Task 7 (Parallel Group D) — Quotations + Reviews (4 files)

**Files:**

- Modify: `app/api/dashboard/quotations/route.ts`
- Modify: `app/api/dashboard/quotations/[id]/route.ts`
- Modify: `app/api/dashboard/reviews/route.ts`
- Modify: `app/api/dashboard/reviews/stats/route.ts`

**Domains:** `'quotations'`, `'reviews'`

**Step 1: Wire `quotations/route.ts` GET**

```typescript
if (getDataSource("quotations") === "convex") {
  try {
    const { convexQuery } = await import("@/lib/convex-client");
    const { api } = await import("@/convex/_generated/api");
    const status = request.nextUrl.searchParams.get("status") ?? undefined;
    const result = await convexQuery(api.quotations.queries.getQuotations, {
      userId: user.userId,
      status,
      limit: 50,
    });
    if (result !== null)
      return NextResponse.json({ quotations: result, success: true });
  } catch {
    /* fall through */
  }
}
```

**Step 2: Wire `quotations/[id]/route.ts` GET**

```typescript
if (getDataSource("quotations") === "convex") {
  try {
    const { convexQuery } = await import("@/lib/convex-client");
    const { api } = await import("@/convex/_generated/api");
    const result = await convexQuery(api.quotations.queries.getById, {
      id: params.id as any,
    });
    if (result !== null)
      return NextResponse.json({ quotation: result, success: true });
  } catch {
    /* fall through */
  }
}
```

**Step 3: Wire `reviews/route.ts` GET**

```typescript
if (getDataSource("reviews") === "convex") {
  try {
    const { convexQuery } = await import("@/lib/convex-client");
    const { api } = await import("@/convex/_generated/api");
    const result = await convexQuery(api.reviews.queries.getReviews, {
      userId: user.userId,
      limit: 50,
    });
    if (result !== null)
      return NextResponse.json({ reviews: result, success: true });
  } catch {
    /* fall through */
  }
}
```

**Step 4: Stub `reviews/stats/route.ts`**

```typescript
if (getDataSource("reviews") === "convex") {
  return NextResponse.json({ total: 0, averageRating: 0, success: true });
}
```

---

## Task 8 (Parallel Group D) — Expenses + Real Estate + Analytics (4 files)

**Files:**

- Modify: `app/api/dashboard/expenses/route.ts`
- Modify: `app/api/real-estate/properties/route.ts`
- Modify: `app/api/real-estate/brokers/route.ts`
- Modify: `app/api/analytics/events/route.ts`

**Domains:** `'expenses'`, `'real_estate'`, `'analytics'`

**Step 1: Wire `expenses/route.ts` GET**

```typescript
if (getDataSource("expenses") === "convex") {
  try {
    const { convexQuery } = await import("@/lib/convex-client");
    const { api } = await import("@/convex/_generated/api");
    const result = await convexQuery(api.expenses.queries.getExpenses, {
      userId: user.userId,
      limit: 50,
    });
    if (result !== null)
      return NextResponse.json({ expenses: result, success: true });
  } catch {
    /* fall through */
  }
}
```

**Step 2: Wire `real-estate/properties/route.ts` GET**

```typescript
if (getDataSource("real_estate") === "convex") {
  try {
    const { convexQuery } = await import("@/lib/convex-client");
    const { api } = await import("@/convex/_generated/api");
    const result = await convexQuery(api.real_estate.queries.getProperties, {
      userId: user.userId,
      limit: 50,
    });
    if (result !== null)
      return NextResponse.json({ properties: result, success: true });
  } catch {
    /* fall through */
  }
}
```

**Step 3: Wire `real-estate/brokers/route.ts` GET**

```typescript
if (getDataSource("real_estate") === "convex") {
  try {
    const { convexQuery } = await import("@/lib/convex-client");
    const { api } = await import("@/convex/_generated/api");
    const result = await convexQuery(api.real_estate.queries.getBrokers, {
      organizationId: user.organizationId ?? "",
    });
    if (result !== null)
      return NextResponse.json({ brokers: result, success: true });
  } catch {
    /* fall through */
  }
}
```

**Step 4: Wire `analytics/events/route.ts` GET**

```typescript
if (getDataSource("analytics") === "convex") {
  try {
    const { convexQuery } = await import("@/lib/convex-client");
    const { api } = await import("@/convex/_generated/api");
    const result = await convexQuery(api.analytics.queries.getEvents, {
      userId: user.userId,
      limit: 100,
    });
    if (result !== null)
      return NextResponse.json({ events: result, success: true });
  } catch {
    /* fall through */
  }
}
```

---

## Task 9: TypeScript Validation

**Step 1: Run tsc in worktree**

```bash
cd /Users/Asim/Desktop/convex-phase3-worktree/mawidi-site
npx tsc --noEmit 2>&1 | grep -v "node_modules" | head -40
```

Expected: 0 new errors introduced by the wiring (pre-existing errors are OK).

**Step 2: Fix any new errors**

Common issues:

- `user.userId` vs `user.id` — check the actual auth variable shape in each file
- `params.id as any` — Convex IDs are branded types; `as any` suppresses until Phase 4 type alignment
- Missing `NextResponse` import — add if file doesn't already import it
- `getDataSource` import already present — don't add duplicate

**Step 3: Re-run until clean**

```bash
npx tsc --noEmit 2>&1 | grep -v "node_modules" | grep -v "error TS" | head -5
```

---

## Task 10: Commit and Merge

**Step 1: Stage all wired files in worktree**

```bash
cd /Users/Asim/Desktop/convex-phase3-worktree
git add mawidi-site/app/api/
git status --short | head -40
```

**Step 2: Commit**

```bash
git commit -m "$(cat <<'EOF'
feat(convex): wire phase-3 API route data-source routing (32 routes)

Adds getDataSource() guards to all major domain API routes.
All DS_* vars remain 'prisma' — nothing activates until env var is flipped.
Domains covered: social, leads, billing, support, voice_agents,
quotations, reviews, expenses, real_estate, analytics.

Also fixes string-format bug in bookings/notifications routes
(was passing 'domain:fn' string, now passes api.domain.queries.fn object).
EOF
)"
```

**Step 3: Merge into main branch**

```bash
cd /Users/Asim/Desktop/mawidi_codex
git merge feature/convex-phase3
```

**Step 4: Verify merge**

```bash
git log --oneline -3
grep -rl "getDataSource" mawidi-site/app/api --include="*.ts" | wc -l
```

Expected: 34+ files (32 new + 2 existing bookings/notifications).

**Step 5: Clean up worktree**

```bash
git worktree remove /Users/Asim/Desktop/convex-phase3-worktree
git branch -d feature/convex-phase3
```

---

## Execution Notes

- **Tasks 1-8 are fully parallel** — all 8 can run simultaneously (zero file overlap)
- **Task 0** (bookings fix) must run before Task 10 (commit) but is independent of Tasks 1-8
- **Task 9** (TS check) must run after Tasks 0-8 complete
- **Task 10** (commit + merge) must run after Task 9 passes

## What Comes Next (Phase 1 completion)

After this lands, the next priority is **completing Phase 1**:

1. Rewrite `lib/auth.ts` as Clerk compatibility layer (same interface, backed by Clerk + Convex)
2. Add `createRouteMatcher` + `auth.protect()` to `middleware.ts` for `/dashboard` routes
3. Enable `DS_AUTH=convex` in `.env.local`
4. Validate all 242 routes respond correctly via Clerk sessions
