From f1a3a31a943b9813be077928bc80d93315ee0ff6 Mon Sep 17 00:00:00 2001 From: jason Date: Fri, 13 Mar 2026 16:00:33 -0500 Subject: [PATCH] fixes --- .claude/worktrees/suspicious-wilson | 1 + src/app/api/reports/[id]/export/route.ts | 18 ++++---- src/lib/google-drive.ts | 57 +++++++++++++++++++++--- 3 files changed, 59 insertions(+), 17 deletions(-) create mode 160000 .claude/worktrees/suspicious-wilson diff --git a/.claude/worktrees/suspicious-wilson b/.claude/worktrees/suspicious-wilson new file mode 160000 index 0000000..707f632 --- /dev/null +++ b/.claude/worktrees/suspicious-wilson @@ -0,0 +1 @@ +Subproject commit 707f632d34166de233c723f0907c992d8347383b diff --git a/src/app/api/reports/[id]/export/route.ts b/src/app/api/reports/[id]/export/route.ts index 390799e..59ef02e 100644 --- a/src/app/api/reports/[id]/export/route.ts +++ b/src/app/api/reports/[id]/export/route.ts @@ -5,7 +5,7 @@ export const runtime = "nodejs"; import { getServerSession } from "next-auth/next"; import { authOptions } from "@/lib/auth"; import { prisma } from "@/lib/prisma"; -import { uploadToDrive, updateDriveFile, generateReportMarkdown } from "@/lib/google-drive"; +import { uploadToDrive, updateDriveFile, generateReportMarkdown, getGoogleAuth } from "@/lib/google-drive"; export async function POST( req: Request, @@ -18,13 +18,11 @@ export async function POST( return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } - // With database sessions (not JWT), the Google access token lives in the - // Account table — getToken() returns null in this strategy. - const account = await prisma.account.findFirst({ - where: { userId: session.user.id, provider: "google" }, - }); - - if (!account?.access_token) { + let auth; + try { + auth = await getGoogleAuth(session.user.id); + } catch (error) { + console.error("Failed to get Google Auth:", error); return NextResponse.json({ error: "Unauthorized or missing Google token" }, { status: 401 }); } @@ -50,10 +48,10 @@ export async function POST( if (report.driveFileId) { // Update the existing Drive file in place - driveFile = await updateDriveFile(account.access_token, report.driveFileId, markdown); + driveFile = await updateDriveFile(auth, report.driveFileId, markdown); } else { // First export — create a new Drive file and store its ID - driveFile = await uploadToDrive(account.access_token, fileName, markdown, folderSetting?.value); + driveFile = await uploadToDrive(auth, fileName, markdown, folderSetting?.value); await prisma.report.update({ where: { id }, data: { driveFileId: driveFile.id }, diff --git a/src/lib/google-drive.ts b/src/lib/google-drive.ts index a0b9175..1809992 100644 --- a/src/lib/google-drive.ts +++ b/src/lib/google-drive.ts @@ -1,10 +1,56 @@ import { google } from 'googleapis'; import { Readable } from 'stream'; +import { prisma } from './prisma'; -export async function uploadToDrive(accessToken: string, fileName: string, content: string, folderId?: string) { - const auth = new google.auth.OAuth2(); - auth.setCredentials({ access_token: accessToken }); +export async function getGoogleAuth(userId: string) { + const account = await prisma.account.findFirst({ + where: { userId, provider: 'google' }, + }); + if (!account) { + throw new Error('Google account not found'); + } + + const auth = new google.auth.OAuth2( + process.env.GOOGLE_CLIENT_ID, + process.env.GOOGLE_CLIENT_SECRET + ); + + auth.setCredentials({ + access_token: account.access_token, + refresh_token: account.refresh_token, + expiry_date: account.expires_at ? account.expires_at * 1000 : null, + }); + + // Check if the token is expired or will expire in the next 1 minute + // NextAuth stores expires_at in seconds + const isExpired = account.expires_at ? (account.expires_at * 1000) < (Date.now() + 60000) : true; + + if (isExpired && account.refresh_token) { + try { + const { credentials } = await auth.refreshAccessToken(); + auth.setCredentials(credentials); + + // Update database with new tokens + await prisma.account.update({ + where: { id: account.id }, + data: { + access_token: credentials.access_token, + refresh_token: credentials.refresh_token || account.refresh_token, + expires_at: credentials.expiry_date ? Math.floor(credentials.expiry_date / 1000) : null, + }, + }); + console.log('Successfully refreshed Google access token for user:', userId); + } catch (error) { + console.error('Error refreshing access token:', error); + // If refresh fails, we still return the auth object, but requests will fail with 401 + } + } + + return auth; +} + +export async function uploadToDrive(auth: any, fileName: string, content: string, folderId?: string) { const drive = google.drive({ version: 'v3', auth }); const fileMetadata: any = { @@ -34,10 +80,7 @@ export async function uploadToDrive(accessToken: string, fileName: string, conte } } -export async function updateDriveFile(accessToken: string, fileId: string, content: string) { - const auth = new google.auth.OAuth2(); - auth.setCredentials({ access_token: accessToken }); - +export async function updateDriveFile(auth: any, fileId: string, content: string) { const drive = google.drive({ version: 'v3', auth }); const media = {