Add soft-delete support for tasks with deletedAt field and migration. Remove Apple Reminders iCloud integration entirely (API routes, lib, UI modal, and Python script) in favor of CalDAV approach. Add periodic pull-sync from Google Tasks every 2 minutes with deletion detection. Fix orphaned someday task rescue and cross-list task toggle/edit. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
139 lines
4.3 KiB
TypeScript
139 lines
4.3 KiB
TypeScript
// Bidirectional synchronization logic for calendars and tasks
|
|
import { CalendarEvent, getCalendarEvents, mergeWithTasks, filterEventsByDateRange } from './calendar-events';
|
|
import { Task } from '@/types/task';
|
|
|
|
export interface CalendarConnection {
|
|
id: string;
|
|
provider: 'google' | 'apple' | 'outlook';
|
|
accessToken: string;
|
|
refreshToken?: string;
|
|
expiresAt?: Date;
|
|
calendars: Array<{
|
|
id: string;
|
|
title: string;
|
|
isPrimary?: boolean;
|
|
}>;
|
|
}
|
|
|
|
export interface SyncResult {
|
|
syncedEvents: number;
|
|
syncedTasks: number;
|
|
conflicts: Array<{
|
|
type: 'duplicate' | 'conflict';
|
|
message: string;
|
|
}>;
|
|
}
|
|
|
|
/**
|
|
* Sync calendar events to tasks
|
|
*/
|
|
export const syncCalendarEvents = async (
|
|
connections: CalendarConnection[],
|
|
timeMin: string,
|
|
timeMax: string,
|
|
existingTasks: Task[]
|
|
): Promise<SyncResult> => {
|
|
try {
|
|
// Fetch events from all connected calendars
|
|
const events = await getCalendarEvents(connections, timeMin, timeMax);
|
|
|
|
// Filter events to the current week range
|
|
const filteredEvents = filterEventsByDateRange(events, new Date(timeMin), new Date(timeMax));
|
|
|
|
// Merge with existing tasks to avoid duplicates
|
|
const uniqueEvents = mergeWithTasks(filteredEvents, existingTasks);
|
|
|
|
// In a real implementation, this would create actual Task records in the DB
|
|
// For now, we'll simulate this with a database operation
|
|
// Assuming we have a function to create tasks in the database
|
|
const createdTasks = await createTasksFromEvents(uniqueEvents);
|
|
|
|
// Placeholder result - in a real implementation, this would sync with actual database
|
|
return {
|
|
syncedEvents: uniqueEvents.length,
|
|
syncedTasks: createdTasks.length,
|
|
conflicts: []
|
|
};
|
|
} catch (error) {
|
|
console.error('Error syncing calendar events:', error);
|
|
throw new Error('Failed to sync calendar events');
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Helper function to create tasks from calendar events (placeholder implementation)
|
|
*/
|
|
const createTasksFromEvents = async (events: CalendarEvent[]): Promise<Task[]> => {
|
|
// In a real implementation, this would connect to the database and create tasks
|
|
// For now, just return an array with the same length as events
|
|
return events.map(event => ({
|
|
id: `task-${Date.now()}-${Math.random()}`,
|
|
title: event.title,
|
|
description: event.description,
|
|
completed: false,
|
|
startTime: event.start.dateTime,
|
|
endTime: event.end.dateTime,
|
|
dayOfWeek: event.start.dateTime ? new Date(event.start.dateTime).getDay() : undefined,
|
|
createdAt: new Date(),
|
|
updatedAt: new Date()
|
|
}));
|
|
};
|
|
|
|
/**
|
|
* Sync tasks to calendar events
|
|
*/
|
|
export const syncCalendarTasks = async (
|
|
connections: CalendarConnection[],
|
|
tasks: Task[],
|
|
timeMin: string,
|
|
timeMax: string
|
|
): Promise<SyncResult> => {
|
|
try {
|
|
// In a real implementation, this would convert tasks to calendar events
|
|
// and update the connected calendars
|
|
|
|
// In a real implementation, this would make API calls to calendar providers
|
|
// For now, we'll simulate by just returning the count
|
|
const syncedEvents = tasks.length;
|
|
|
|
// Placeholder - in a real implementation, this would make API calls to calendar providers
|
|
console.log(`Converting ${tasks.length} tasks to calendar events`);
|
|
|
|
return {
|
|
syncedEvents: syncedEvents,
|
|
syncedTasks: tasks.length,
|
|
conflicts: []
|
|
};
|
|
} catch (error) {
|
|
console.error('Error syncing calendar tasks:', error);
|
|
throw new Error('Failed to sync calendar tasks');
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Full bidirectional sync operation
|
|
*/
|
|
export const syncCalendarBidirectional = async (
|
|
connections: CalendarConnection[],
|
|
timeMin: string,
|
|
timeMax: string,
|
|
existingTasks: Task[]
|
|
): Promise<SyncResult> => {
|
|
try {
|
|
// Sync calendar events to tasks
|
|
const eventsResult = await syncCalendarEvents(connections, timeMin, timeMax, existingTasks);
|
|
|
|
// Sync tasks to calendar events
|
|
const tasksResult = await syncCalendarTasks(connections, existingTasks, timeMin, timeMax);
|
|
|
|
// Combine results
|
|
return {
|
|
syncedEvents: eventsResult.syncedEvents,
|
|
syncedTasks: tasksResult.syncedTasks,
|
|
conflicts: [...eventsResult.conflicts, ...tasksResult.conflicts]
|
|
};
|
|
} catch (error) {
|
|
console.error('Error performing bidirectional sync:', error);
|
|
throw new Error('Failed to perform bidirectional sync');
|
|
}
|
|
}; |