102 lines
2.4 KiB
TypeScript
102 lines
2.4 KiB
TypeScript
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;
|
|
};
|
|
|
|
type XeroTenant = {
|
|
tenantId: string;
|
|
};
|
|
|
|
export async function GET(req: NextRequest) {
|
|
const cookieStore = await cookies();
|
|
const session = cookieStore.get("session");
|
|
|
|
// Must be logged in
|
|
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 }
|
|
);
|
|
}
|
|
|
|
// Exchange code for token
|
|
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;
|
|
|
|
// Fetch connected tenants (organisations)
|
|
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 }
|
|
);
|
|
}
|
|
|
|
// Save user ↔ tenant (minimal MVP)
|
|
await db
|
|
.insert(xeroConnections)
|
|
.values({
|
|
userId,
|
|
tenantId,
|
|
})
|
|
.onConflictDoUpdate({
|
|
target: xeroConnections.userId,
|
|
set: { tenantId },
|
|
});
|
|
|
|
return NextResponse.redirect(
|
|
new URL("/connect/xero/success", process.env.APP_URL)
|
|
);
|
|
}
|