My-Weekly-ToDo-List/src/app/api/calendar/google/oauth/route.ts

127 lines
4.8 KiB
TypeScript

import { NextRequest, NextResponse } from 'next/server';
import { getServerSession } from 'next-auth';
import { google } from 'googleapis';
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
// Google Calendar OAuth callback endpoint
export async function GET(request: NextRequest) {
try {
const session = await getServerSession();
const { searchParams } = new URL(request.url);
const code = searchParams.get('code');
const state = searchParams.get('state'); // User email passed from start route
if (!code) {
return NextResponse.redirect(new URL('/tasks?error=oauth_code_missing', request.url));
}
// Get user from session or state parameter
const userEmail = session?.user?.email || state;
if (!userEmail) {
return NextResponse.redirect(new URL('/auth/login?error=session_expired', request.url));
}
// Find user in database
const user = await prisma.user.findUnique({
where: { email: userEmail }
});
if (!user) {
return NextResponse.redirect(new URL('/auth/login?error=user_not_found', request.url));
}
// Initialize Google OAuth client
const clientId = process.env.GOOGLE_CLIENT_ID || '';
const clientSecret = process.env.GOOGLE_CLIENT_SECRET || '';
const redirectUri = process.env.GOOGLE_REDIRECT_URI || `${process.env.NEXTAUTH_URL}/api/calendar/google/oauth`;
const oauth2Client = new google.auth.OAuth2(clientId, clientSecret, redirectUri);
// Exchange authorization code for access token
const { tokens } = await oauth2Client.getToken(code);
oauth2Client.setCredentials(tokens);
// Fetch user calendars to store in connection settings
// Dynamic import to avoid circular dep issues in some envs, or just standard import?
// Standard import is better but we are inside function scope to check diff.
// I'll assume the import is added at top level or I force it here if possible.
// I will add import at top level in separate chunk if needed?
// Replace whole file content is unsafe. I'll use multi-replace.
// We need getUserCalendars. I'll use require or assume import added.
// Actually, I'll allow ReplaceFileContent to manage imports? No.
// I'll use multi_replace to add import AND update logic.
// WAIT, better approach: Just implement the fetch logic here locally to avoid import issues or dependency on lib if it changes.
// But duplicate code is bad.
// I'll add the import at the top.
// Logic:
const calendar = google.calendar({ version: 'v3', auth: oauth2Client });
const response = await calendar.calendarList.list();
const remoteCalendars = response.data.items?.map((item: any) => ({
id: item.id,
title: item.summary,
isPrimary: item.primary,
backgroundColor: item.backgroundColor, // Store calendar color for event fallback
selected: true // Default newly found to true
})) || [];
// Check if connection already exists
const existingConnection = await prisma.calendarConnection.findFirst({
where: {
userId: user.id,
provider: 'google'
}
});
let finalCalendars = remoteCalendars;
if (existingConnection) {
// Merge with existing selection
if (existingConnection.calendars && Array.isArray(existingConnection.calendars)) {
const existingList = existingConnection.calendars as any[];
finalCalendars = remoteCalendars.map(remote => {
const match = existingList.find(e => e.id === remote.id);
return {
...remote,
selected: match ? match.selected : true // Preserve selection
};
});
}
// Update existing connection
await prisma.calendarConnection.update({
where: { id: existingConnection.id },
data: {
accessToken: tokens.access_token || '',
refreshToken: tokens.refresh_token || existingConnection.refreshToken,
expiresAt: tokens.expiry_date ? new Date(tokens.expiry_date) : null,
calendars: finalCalendars, // Store calendars
updatedAt: new Date()
}
});
} else {
// Create new connection
await prisma.calendarConnection.create({
data: {
userId: user.id,
provider: 'google',
accessToken: tokens.access_token || '',
refreshToken: tokens.refresh_token || null,
expiresAt: tokens.expiry_date ? new Date(tokens.expiry_date) : null,
calendars: finalCalendars, // Store calendars
}
});
}
// Redirect to tasks page with success message
return NextResponse.redirect(new URL('/tasks?calendar=connected', request.url));
} catch (error) {
console.error('Google OAuth error:', error);
return NextResponse.redirect(new URL('/tasks?error=oauth_failed', request.url));
}
}