2026-04-05 23:00:48 +00:00
|
|
|
import { NextResponse } from "next/server";
|
|
|
|
|
import { createClient } from "@/lib/supabase/server";
|
|
|
|
|
|
|
|
|
|
export async function POST() {
|
|
|
|
|
const supabase = await createClient();
|
|
|
|
|
const { data: userData, error: userError } = await supabase.auth.getUser();
|
|
|
|
|
|
|
|
|
|
if (userError || !userData.user) {
|
|
|
|
|
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const apiKey = process.env.UNIPILE_API_KEY;
|
|
|
|
|
const dsn = process.env.UNIPILE_DSN;
|
|
|
|
|
const appUrl = process.env.NEXT_PUBLIC_APP_URL;
|
|
|
|
|
|
|
|
|
|
if (!apiKey || !dsn || !appUrl) {
|
|
|
|
|
return NextResponse.json(
|
|
|
|
|
{ error: "Unipile is not configured. Please add UNIPILE_API_KEY, UNIPILE_DSN, and NEXT_PUBLIC_APP_URL to your environment." },
|
|
|
|
|
{ status: 500 }
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if user already has an account (reconnect flow)
|
|
|
|
|
const { data: profile } = await supabase
|
|
|
|
|
.from("profiles")
|
|
|
|
|
.select("unipile_account_id")
|
|
|
|
|
.eq("id", userData.user.id)
|
|
|
|
|
.single();
|
|
|
|
|
|
|
|
|
|
const type = profile?.unipile_account_id ? "reconnect" : "create";
|
|
|
|
|
|
2026-04-05 23:30:24 +00:00
|
|
|
// expiresOn: 1 hour from now (links also expire on daily restart regardless)
|
|
|
|
|
const expiresOn = new Date(Date.now() + 60 * 60 * 1000).toISOString();
|
|
|
|
|
// api_url must be the full Unipile server URL derived from the DSN
|
|
|
|
|
const api_url = `https://${dsn}`;
|
|
|
|
|
|
2026-04-05 23:00:48 +00:00
|
|
|
const body: Record<string, unknown> = {
|
|
|
|
|
type,
|
|
|
|
|
providers: ["LINKEDIN"],
|
2026-04-05 23:30:24 +00:00
|
|
|
expiresOn,
|
|
|
|
|
api_url,
|
2026-04-05 23:00:48 +00:00
|
|
|
notify_url: `${appUrl}/api/linkedin/callback`,
|
|
|
|
|
success_redirect_url: `${appUrl}/dashboard/settings/account?linkedin=connected`,
|
|
|
|
|
failure_redirect_url: `${appUrl}/dashboard/settings/account?linkedin=failed`,
|
|
|
|
|
// 'name' is returned as-is in the webhook payload, used to map account_id → user
|
|
|
|
|
name: userData.user.id,
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-05 23:30:24 +00:00
|
|
|
// For reconnect, the field is reconnect_account (not account_id)
|
2026-04-05 23:00:48 +00:00
|
|
|
if (type === "reconnect" && profile?.unipile_account_id) {
|
2026-04-05 23:30:24 +00:00
|
|
|
body.reconnect_account = profile.unipile_account_id;
|
2026-04-05 23:00:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const response = await fetch(`https://${dsn}/api/v1/hosted/accounts/link`, {
|
|
|
|
|
method: "POST",
|
|
|
|
|
headers: {
|
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
|
"X-API-KEY": apiKey,
|
|
|
|
|
},
|
|
|
|
|
body: JSON.stringify(body),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
const text = await response.text();
|
|
|
|
|
console.error("Unipile connect error:", text);
|
|
|
|
|
return NextResponse.json(
|
|
|
|
|
{ error: "Failed to generate LinkedIn connection link" },
|
|
|
|
|
{ status: 502 }
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
return NextResponse.json({ url: data.url });
|
|
|
|
|
}
|