- Show Account Number in Settings > Account for user identification - Add weekly goals to JSON export/import with upsert-based merge - Fix calendar events not appearing instantly (await cache ops, prevent provider force-refresh from overwriting optimistic updates) - Add Sign Out button in Settings > Account for mobile accessibility - Fix dateVerticalAlign persistence in profile API - Fix quote API error handling for non-JSON responses - Add Synology to CalendarEvent source type union v1.20.0 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
137 lines
4.2 KiB
TypeScript
137 lines
4.2 KiB
TypeScript
import { NextResponse } from 'next/server';
|
|
import { getServerSession } from 'next-auth';
|
|
import { authOptions } from "@/lib/auth";
|
|
import { prisma } from '@/lib/prisma';
|
|
|
|
export async function GET() {
|
|
const session = await getServerSession(authOptions);
|
|
|
|
if (!session || !session.user?.email) {
|
|
return new NextResponse('Unauthorized', { status: 401 });
|
|
}
|
|
|
|
try {
|
|
const user = await prisma.user.findUnique({
|
|
where: { email: session.user.email },
|
|
});
|
|
|
|
if (!user) {
|
|
return new NextResponse('User not found', { status: 404 });
|
|
}
|
|
|
|
// Fetch someday lists
|
|
const somedayLists = await prisma.somedayList.findMany({
|
|
where: { userId: user.id },
|
|
orderBy: { order: 'asc' },
|
|
select: {
|
|
id: true,
|
|
title: true,
|
|
order: true,
|
|
createdAt: true,
|
|
updatedAt: true,
|
|
},
|
|
});
|
|
|
|
// Fetch projects
|
|
const projects = await prisma.project.findMany({
|
|
where: { userId: user.id },
|
|
orderBy: { order: 'asc' },
|
|
select: {
|
|
id: true,
|
|
name: true,
|
|
icon: true,
|
|
color: true,
|
|
description: true,
|
|
order: true,
|
|
createdAt: true,
|
|
updatedAt: true,
|
|
},
|
|
});
|
|
|
|
// Fetch all non-deleted tasks (top-level and subtasks)
|
|
const allTasks = await prisma.task.findMany({
|
|
where: {
|
|
userId: user.id,
|
|
deletedAt: null,
|
|
},
|
|
orderBy: { order: 'asc' },
|
|
select: {
|
|
id: true,
|
|
title: true,
|
|
description: true,
|
|
markdownContent: true,
|
|
completed: true,
|
|
isRolling: true,
|
|
order: true,
|
|
dayOfWeek: true,
|
|
scheduledDate: true,
|
|
somedayListId: true,
|
|
originalDate: true,
|
|
startTime: true,
|
|
endTime: true,
|
|
isRecurring: true,
|
|
recurrenceInterval: true,
|
|
recurrenceUnit: true,
|
|
recurrenceTime: true,
|
|
recurrenceEndDate: true,
|
|
createdAt: true,
|
|
updatedAt: true,
|
|
parentTaskId: true,
|
|
somedaySlotIndex: true,
|
|
projectId: true,
|
|
},
|
|
});
|
|
|
|
// Build a tree: nest subtasks under their parents
|
|
const taskMap = new Map<string, any>();
|
|
const topLevelTasks: any[] = [];
|
|
|
|
for (const task of allTasks) {
|
|
taskMap.set(task.id, { ...task, subTasks: [] });
|
|
}
|
|
|
|
for (const task of allTasks) {
|
|
const taskWithSubs = taskMap.get(task.id)!;
|
|
if (task.parentTaskId && taskMap.has(task.parentTaskId)) {
|
|
taskMap.get(task.parentTaskId)!.subTasks.push(taskWithSubs);
|
|
} else {
|
|
topLevelTasks.push(taskWithSubs);
|
|
}
|
|
}
|
|
|
|
// Fetch weekly goals
|
|
const weeklyGoals = await prisma.weeklyGoal.findMany({
|
|
where: { userId: user.id },
|
|
orderBy: { weekStart: 'asc' },
|
|
select: {
|
|
id: true,
|
|
weekStart: true,
|
|
text: true,
|
|
createdAt: true,
|
|
updatedAt: true,
|
|
},
|
|
});
|
|
|
|
const exportData = {
|
|
exportVersion: 1,
|
|
exportDate: new Date().toISOString(),
|
|
somedayLists,
|
|
projects,
|
|
tasks: topLevelTasks,
|
|
weeklyGoals,
|
|
};
|
|
|
|
const jsonContent = JSON.stringify(exportData, null, 2);
|
|
|
|
return new NextResponse(jsonContent, {
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Content-Disposition': `attachment; filename="weekly_todo_backup_${new Date().toISOString().split('T')[0]}.json"`,
|
|
},
|
|
});
|
|
} catch (error) {
|
|
console.error('Export data error:', error);
|
|
return new NextResponse('Internal Server Error', { status: 500 });
|
|
}
|
|
}
|