File size: 4,166 Bytes
ceb943f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
081358f
 
 
 
 
 
 
ceb943f
081358f
 
ceb943f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
081358f
ceb943f
 
 
 
 
 
 
 
 
 
 
081358f
ceb943f
 
 
 
 
081358f
ceb943f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
import { NextResponse, NextRequest } from "next/server";
import { db } from "@/lib/db";
import { accounts } from "@/lib/db/schema";
import { eq, and } from "drizzle-orm";
import crypto from "crypto";

export async function GET(request: NextRequest) {
    const { searchParams } = new URL(request.url);
    const code = searchParams.get("code");
    const userId = searchParams.get("state");
    const error = searchParams.get("error");
    const appUrl = process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000";

    if (error) {
        return NextResponse.redirect(new URL(`/dashboard?error=${encodeURIComponent(error)}`, appUrl));
    }

    if (!code || !userId) {
        return NextResponse.redirect(new URL("/dashboard?error=Missing+code+or+state", appUrl));
    }

    try {
        const clientId = process.env.GOOGLE_CLIENT_ID!;
        const clientSecret = process.env.GOOGLE_CLIENT_SECRET!;
        const redirectUri = `${appUrl}/api/youtube/callback`;

        const tokenResponse = await fetch("https://oauth2.googleapis.com/token", {
            method: "POST",
            headers: { "Content-Type": "application/x-www-form-urlencoded" },
            body: new URLSearchParams({
                code,
                client_id: clientId,
                client_secret: clientSecret,
                redirect_uri: redirectUri,
                grant_type: "authorization_code",
            }).toString(),
        });

        const tokens = await tokenResponse.json();
        console.log("[YouTube callback] Tokens received:", {
            has_access_token: !!tokens.access_token,
            has_refresh_token: !!tokens.refresh_token,
            expires_in: tokens.expires_in,
            scope: tokens.scope,
        });

        if (!tokens.access_token) {
            console.error("[YouTube callback] Token exchange failed:", tokens);
            throw new Error(`Token exchange failed: ${tokens.error_description || tokens.error || JSON.stringify(tokens)}`);
        }

        // Extract Google account ID from id_token (JWT sub claim)
        let googleAccountId = "google";
        if (tokens.id_token) {
            try {
                const payload = JSON.parse(
                    Buffer.from(tokens.id_token.split(".")[1], "base64").toString()
                );
                googleAccountId = payload.sub;
            } catch {
                // Fall back to generic accountId
            }
        }

        const now = new Date();
        const tokenFields = {
            accessToken: tokens.access_token,
            refreshToken: tokens.refresh_token || null,
            scope: tokens.scope || null,
            accessTokenExpiresAt: tokens.expires_in
                ? new Date(Date.now() + tokens.expires_in * 1000)
                : null,
        };

        // Upsert: update if the account row exists, insert if a prior disconnect deleted it
        const existing = await db.query.accounts.findFirst({
            where: and(eq(accounts.userId, userId), eq(accounts.providerId, "google")),
        });

        if (existing) {
            console.log("[YouTube callback] Updating existing account record for user:", userId);
            await db
                .update(accounts)
                .set({ ...tokenFields, updatedAt: now })
                .where(eq(accounts.id, existing.id));
        } else {
            console.log("[YouTube callback] Creating new account record for user:", userId);
            await db.insert(accounts).values({
                id: crypto.randomUUID(),
                accountId: googleAccountId,
                providerId: "google",
                userId,
                ...tokenFields,
                createdAt: now,
                updatedAt: now,
            });
        }

        // Redirect back to dashboard to trigger channel sync
        return NextResponse.redirect(new URL("/dashboard?sync_youtube=true", appUrl));
    } catch (err) {
        console.error("[YouTube callback] Error:", err);
        return NextResponse.redirect(
            new URL(`/dashboard?error=${encodeURIComponent("Failed to complete YouTube connection")}`, appUrl)
        );
    }
}