fix: paginate task sync and unblock connections endpoint
- Google Tasks and Microsoft To-Do sync now paginate through all results instead of capping at 100 tasks per list. - Synology calendar pruning in GET /connections is now fire-and-forget so a slow/unreachable NAS doesn't block the entire response. v1.57.12 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
a5295c5dd7
commit
a009a4953a
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "my-weekly-todo-list",
|
||||
"version": "1.57.11",
|
||||
"version": "1.57.12",
|
||||
"description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
||||
@ -32,7 +32,7 @@ export async function GET(request: NextRequest) {
|
||||
|
||||
// Return calendar connections (without sensitive tokens)
|
||||
// Prune deleted calendars from providers that support live listing
|
||||
const connections = await Promise.all(user.calendarConnections.map(async (conn) => {
|
||||
const connections = user.calendarConnections.map((conn) => {
|
||||
let calendars = conn.calendars as any[] | null;
|
||||
|
||||
// For Apple connections, filter out VTODO/Reminders collections
|
||||
@ -42,31 +42,31 @@ export async function GET(request: NextRequest) {
|
||||
);
|
||||
}
|
||||
|
||||
// For Synology connections, prune calendars deleted on the server
|
||||
// For Synology connections, fire-and-forget prune of deleted calendars
|
||||
if (conn.provider === 'synology' && Array.isArray(calendars) && calendars.length > 0) {
|
||||
try {
|
||||
const [username, password] = conn.accessToken.split(':');
|
||||
const serverUrl = conn.refreshToken;
|
||||
if (username && password && serverUrl) {
|
||||
const freshCalendars = await getSynologyCalendars(serverUrl, username, password);
|
||||
const freshIds = new Set(freshCalendars.map(c => c.id));
|
||||
const storedIds = calendars.map((c: any) => c.id);
|
||||
const staleIds = storedIds.filter((id: string) => !freshIds.has(id));
|
||||
if (staleIds.length > 0) {
|
||||
console.log('[CONNECTIONS] Synology stale calendars to prune:', staleIds);
|
||||
}
|
||||
const pruned = calendars.filter((c: any) => freshIds.has(c.id));
|
||||
if (pruned.length < calendars.length) {
|
||||
calendars = pruned;
|
||||
await prisma.calendarConnection.update({
|
||||
where: { id: conn.id },
|
||||
data: { calendars: pruned },
|
||||
});
|
||||
const connId = conn.id;
|
||||
const storedCalendars = calendars;
|
||||
(async () => {
|
||||
try {
|
||||
const [username, password] = conn.accessToken.split(':');
|
||||
const serverUrl = conn.refreshToken;
|
||||
if (username && password && serverUrl) {
|
||||
const freshCalendars = await getSynologyCalendars(serverUrl, username, password);
|
||||
const freshIds = new Set(freshCalendars.map(c => c.id));
|
||||
const staleIds = storedCalendars.filter((c: any) => !freshIds.has(c.id)).map((c: any) => c.id);
|
||||
if (staleIds.length > 0) {
|
||||
console.log('[CONNECTIONS] Synology stale calendars to prune:', staleIds);
|
||||
const pruned = storedCalendars.filter((c: any) => freshIds.has(c.id));
|
||||
await prisma.calendarConnection.update({
|
||||
where: { id: connId },
|
||||
data: { calendars: pruned },
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[CONNECTIONS] Synology calendar pruning failed:', err);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[CONNECTIONS] Synology calendar pruning failed:', err);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
return {
|
||||
@ -76,7 +76,7 @@ export async function GET(request: NextRequest) {
|
||||
createdAt: conn.createdAt,
|
||||
expiresAt: conn.expiresAt,
|
||||
};
|
||||
}));
|
||||
});
|
||||
|
||||
return NextResponse.json({ connections });
|
||||
} catch (error) {
|
||||
|
||||
@ -153,27 +153,35 @@ export const deleteGoogleTask = async (client: OAuth2Client, taskListId: string,
|
||||
export const fetchGoogleTasksForSync = async (client: OAuth2Client, taskListId: string, updatedMin?: string): Promise<GoogleTask[]> => {
|
||||
const service = google.tasks({ version: 'v1', auth: client });
|
||||
try {
|
||||
const params: any = {
|
||||
tasklist: taskListId,
|
||||
showCompleted: true,
|
||||
showHidden: true,
|
||||
maxResults: 100,
|
||||
};
|
||||
if (updatedMin) {
|
||||
params.updatedMin = updatedMin;
|
||||
}
|
||||
const allTasks: GoogleTask[] = [];
|
||||
let pageToken: string | undefined;
|
||||
|
||||
const response = await service.tasks.list(params);
|
||||
do {
|
||||
const params: any = {
|
||||
tasklist: taskListId,
|
||||
showCompleted: true,
|
||||
showHidden: true,
|
||||
maxResults: 100,
|
||||
};
|
||||
if (updatedMin) params.updatedMin = updatedMin;
|
||||
if (pageToken) params.pageToken = pageToken;
|
||||
|
||||
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!,
|
||||
parent: (item as any).parent || undefined,
|
||||
}));
|
||||
const response = await service.tasks.list(params);
|
||||
|
||||
const items = (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!,
|
||||
parent: (item as any).parent || undefined,
|
||||
}));
|
||||
allTasks.push(...items);
|
||||
pageToken = response.data.nextPageToken || undefined;
|
||||
} while (pageToken);
|
||||
|
||||
return allTasks;
|
||||
} catch (error: any) {
|
||||
// On quota exceeded (429), return empty array instead of crashing
|
||||
if (error?.code === 429 || error?.status === 429) {
|
||||
|
||||
@ -127,31 +127,35 @@ export const fetchMsTodoTasksForSync = async (
|
||||
listId: string,
|
||||
modifiedSince?: string
|
||||
): Promise<MicrosoftTodoTask[]> => {
|
||||
const params = new URLSearchParams({
|
||||
'$top': '100'
|
||||
});
|
||||
if (modifiedSince) {
|
||||
params.set('$filter', `lastModifiedDateTime gt ${modifiedSince}`);
|
||||
}
|
||||
const allTasks: MicrosoftTodoTask[] = [];
|
||||
let url: string | null = (() => {
|
||||
const params = new URLSearchParams({ '$top': '100' });
|
||||
if (modifiedSince) {
|
||||
params.set('$filter', `lastModifiedDateTime gt ${modifiedSince}`);
|
||||
}
|
||||
return `${GRAPH_ENDPOINT}/me/todo/lists/${encodeURIComponent(listId)}/tasks?${params.toString()}`;
|
||||
})();
|
||||
|
||||
const response = await fetch(
|
||||
`${GRAPH_ENDPOINT}/me/todo/lists/${encodeURIComponent(listId)}/tasks?${params.toString()}`,
|
||||
{
|
||||
while (url) {
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${accessToken}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const err = await response.text();
|
||||
console.error(`Error fetching sync tasks from list ${listId}:`, err);
|
||||
throw new Error(`Failed to fetch To-Do tasks for sync: ${response.status} ${response.statusText}`);
|
||||
if (!response.ok) {
|
||||
const err = await response.text();
|
||||
console.error(`Error fetching sync tasks from list ${listId}:`, err);
|
||||
throw new Error(`Failed to fetch To-Do tasks for sync: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
allTasks.push(...((data.value || []) as MicrosoftTodoTask[]));
|
||||
url = data['@odata.nextLink'] || null;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return (data.value || []) as MicrosoftTodoTask[];
|
||||
return allTasks;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Loading…
Reference in New Issue
Block a user