- Replace broken CloudKit/pyicloud Apple Reminders integration with CalDAV-based approach (getAppleReminderLists, fetchTasks from apple-calendar.ts) in tasks/lists, tasks/import, and tasks/sync routes - Restore calendar date picker icon in header for jump-to-date navigation - Add goalScope field to User model with migration - Force-dynamic Google OAuth routes to fix redirect issues - Update Google Tasks client and sync logic Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
152 lines
5.0 KiB
TypeScript
152 lines
5.0 KiB
TypeScript
import { google } from 'googleapis';
|
|
import { OAuth2Client } from 'google-auth-library';
|
|
|
|
export interface GoogleTaskList {
|
|
id: string;
|
|
title: string;
|
|
updated: string;
|
|
}
|
|
|
|
export interface GoogleTask {
|
|
id: string;
|
|
title: string;
|
|
notes?: string;
|
|
status: string;
|
|
due?: string;
|
|
updated: string;
|
|
}
|
|
|
|
/**
|
|
* Create an authenticated Google OAuth2 client
|
|
*/
|
|
export const createGoogleClient = (accessToken: string, refreshToken?: string): OAuth2Client => {
|
|
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);
|
|
oauth2Client.setCredentials({
|
|
access_token: accessToken,
|
|
refresh_token: refreshToken
|
|
});
|
|
|
|
return oauth2Client;
|
|
};
|
|
|
|
/**
|
|
* Fetch all task lists for the user
|
|
*/
|
|
export const fetchGoogleTaskLists = async (client: OAuth2Client): Promise<GoogleTaskList[]> => {
|
|
const service = google.tasks({ version: 'v1', auth: client });
|
|
try {
|
|
const response = await service.tasklists.list();
|
|
return (response.data.items || []).map(item => ({
|
|
id: item.id!,
|
|
title: item.title!,
|
|
updated: item.updated!
|
|
}));
|
|
} catch (error) {
|
|
console.error('Error fetching Google Task lists:', error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Fetch tasks from a specific task list
|
|
*/
|
|
export const fetchGoogleTasks = async (client: OAuth2Client, taskListId: string): Promise<GoogleTask[]> => {
|
|
const service = google.tasks({ version: 'v1', auth: client });
|
|
try {
|
|
const response = await service.tasks.list({
|
|
tasklist: taskListId,
|
|
showCompleted: false, // We usually only want active tasks for import
|
|
showHidden: false
|
|
});
|
|
|
|
return (response.data.items || []).map(item => ({
|
|
id: item.id!,
|
|
title: item.title!,
|
|
notes: item.notes || undefined,
|
|
status: item.status!,
|
|
due: item.due || undefined,
|
|
updated: item.updated!
|
|
}));
|
|
} catch (error) {
|
|
console.error(`Error fetching Google Tasks from list ${taskListId}:`, error);
|
|
throw error;
|
|
}
|
|
};
|
|
/**
|
|
* Update a Google Task status
|
|
*/
|
|
export const updateGoogleTask = async (client: OAuth2Client, taskListId: string, taskId: string, updates: { title?: string; notes?: string; status?: 'needsAction' | 'completed'; due?: string | null }): Promise<GoogleTask> => {
|
|
const service = google.tasks({ version: 'v1', auth: client });
|
|
try {
|
|
const requestBody: any = {};
|
|
if (updates.title !== undefined) requestBody.title = updates.title;
|
|
if (updates.notes !== undefined) requestBody.notes = updates.notes;
|
|
if (updates.due !== undefined) requestBody.due = updates.due;
|
|
if (updates.status !== undefined) {
|
|
requestBody.status = updates.status;
|
|
requestBody.completed = updates.status === 'completed' ? new Date().toISOString() : null;
|
|
}
|
|
const response = await service.tasks.patch({
|
|
tasklist: taskListId,
|
|
task: taskId,
|
|
requestBody,
|
|
});
|
|
const item = response.data;
|
|
return {
|
|
id: item.id!,
|
|
title: item.title!,
|
|
notes: item.notes || undefined,
|
|
status: item.status!,
|
|
due: item.due || undefined,
|
|
updated: item.updated!
|
|
};
|
|
} catch (error) {
|
|
console.error(`Error updating Google Task ${taskId}:`, error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
export const deleteGoogleTask = async (client: OAuth2Client, taskListId: string, taskId: string): Promise<void> => {
|
|
const service = google.tasks({ version: 'v1', auth: client });
|
|
try {
|
|
await service.tasks.delete({
|
|
tasklist: taskListId,
|
|
task: taskId,
|
|
});
|
|
} catch (error) {
|
|
console.error(`Error deleting Google Task ${taskId}:`, error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
export const updateGoogleTaskStatus = async (client: OAuth2Client, taskListId: string, taskId: string, status: 'needsAction' | 'completed'): Promise<GoogleTask> => {
|
|
const service = google.tasks({ version: 'v1', auth: client });
|
|
try {
|
|
const response = await service.tasks.patch({
|
|
tasklist: taskListId,
|
|
task: taskId,
|
|
requestBody: {
|
|
status: status,
|
|
completed: status === 'completed' ? new Date().toISOString() : null
|
|
}
|
|
});
|
|
|
|
const item = response.data;
|
|
return {
|
|
id: item.id!,
|
|
title: item.title!,
|
|
notes: item.notes || undefined,
|
|
status: item.status!,
|
|
due: item.due || undefined,
|
|
updated: item.updated!
|
|
};
|
|
} catch (error) {
|
|
console.error(`Error updating Google Task ${taskId} in list ${taskListId}:`, error);
|
|
throw error;
|
|
}
|
|
};
|