My-Weekly-ToDo-List/src/app/api/user/export-data/route.ts
mARTin 4c18d37bec feat: account number display, weekly goals export/import, calendar event fixes
- 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>
2026-03-08 19:56:21 +01:00

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 });
}
}