- Add JSON export/import for all tasks, anyday lists, and projects (Settings > Account > Backup & Restore) - Add merge and replace import modes with confirmation for destructive replace - Add resizable notes sidebar with drag handle on left edge - Add subtask count indicator (indigo pill) on tasks that toggles subtask list - Add note indicator (amber pill) on tasks that toggles inline notes - Show Account ID in Settings > Account as read-only identifier - Fix goal save bug: saved goals matching default text are no longer ignored - Fix calendar week number using Monday within visible range instead of middle day - Fix calendar events not appearing instantly by delaying sync refresh 3s - Grey out URL field when creating events on Google/Outlook calendars v1.12.0 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
123 lines
3.8 KiB
TypeScript
123 lines
3.8 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);
|
|
}
|
|
}
|
|
|
|
const exportData = {
|
|
exportVersion: 1,
|
|
exportDate: new Date().toISOString(),
|
|
somedayLists,
|
|
projects,
|
|
tasks: topLevelTasks,
|
|
};
|
|
|
|
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 });
|
|
}
|
|
}
|