import { cookies } from "next/headers"; import { NextRequest, NextResponse } from "next/server"; import { db } from "@/lib/db"; import { xeroConnections } from "@/lib/schema/xeroConnections"; import { eq } from "drizzle-orm"; type XeroTokenResponse = { access_token: string; refresh_token: string; expires_in: number; // seconds }; type XeroTenant = { tenantId: string; }; export async function GET(req: NextRequest) { const cookieStore = await cookies(); const session = cookieStore.get("session"); if (!session) { return NextResponse.redirect( new URL("/login", process.env.APP_URL) ); } const userId = session.value; const { searchParams } = new URL(req.url); const code = searchParams.get("code"); if (!code) { return NextResponse.json( { error: "Missing OAuth code" }, { status: 400 } ); } // 1️⃣ Exchange code for tokens const tokenRes = await fetch("https://identity.xero.com/connect/token", { method: "POST", headers: { Authorization: "Basic " + Buffer.from( `${process.env.XERO_CLIENT_ID}:${process.env.XERO_CLIENT_SECRET}` ).toString("base64"), "Content-Type": "application/x-www-form-urlencoded", }, body: new URLSearchParams({ grant_type: "authorization_code", code, redirect_uri: process.env.XERO_REDIRECT_URI!, }), }); if (!tokenRes.ok) { const text = await tokenRes.text(); console.error("Xero token exchange failed:", text); return NextResponse.redirect( new URL("/connect/xero?error=token_failed", process.env.APP_URL) ); } const tokenData = (await tokenRes.json()) as XeroTokenResponse; // 2️⃣ Fetch tenant const tenantRes = await fetch("https://api.xero.com/connections", { headers: { Authorization: `Bearer ${tokenData.access_token}`, }, }); const tenants = (await tenantRes.json()) as XeroTenant[]; const tenantId = tenants[0]?.tenantId; if (!tenantId) { return NextResponse.json( { error: "No Xero organisation found" }, { status: 400 } ); } const expiresAt = new Date(Date.now() + tokenData.expires_in * 1000); // 3️⃣ Persist EVERYTHING (this is the fix) await db .insert(xeroConnections) .values({ userId, tenantId, accessToken: tokenData.access_token, refreshToken: tokenData.refresh_token, expiresAt, updatedAt: new Date(), }) .onConflictDoUpdate({ target: xeroConnections.userId, set: { tenantId, accessToken: tokenData.access_token, refreshToken: tokenData.refresh_token, expiresAt, updatedAt: new Date(), }, }); return NextResponse.redirect( new URL("/connect/xero/success", process.env.APP_URL) ); }