fix: resolve stale session ID in someday-lists and calendar/events routes
All handlers in someday-lists/route.ts and calendar/events/route.ts were using (session.user as any).id which can be stale after DB restore/migration. Fixed to look up by session.user.email instead — matching the pattern already established in tasks/route.ts and calendar/sync/route.ts. This was causing other users to see empty someday lists while their tasks (fetched via the already-fixed tasks route) still had somedayListId references, triggering false RESCUE recoveries on every load. Also fixed outlook/callback/route.ts fallback which could still use stale ID. v1.81.19
This commit is contained in:
parent
4cab83e545
commit
0371532e7d
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "my-weekly-todo-list",
|
"name": "my-weekly-todo-list",
|
||||||
"version": "1.81.18",
|
"version": "1.81.19",
|
||||||
"description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view",
|
"description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@ -48,13 +48,17 @@ export async function POST(request: NextRequest) {
|
|||||||
return NextResponse.json({ error: 'Missing required fields' }, { status: 400 });
|
return NextResponse.json({ error: 'Missing required fields' }, { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const userId = (session.user as any).id;
|
const connection = await findConnectionForCalendar(session.user.email, calendarId);
|
||||||
const connection = await findConnectionForCalendar(userId, calendarId);
|
|
||||||
|
|
||||||
if (!connection) {
|
if (!connection) {
|
||||||
return NextResponse.json({ error: 'Calendar connection not found' }, { status: 404 });
|
return NextResponse.json({ error: 'Calendar connection not found' }, { status: 404 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resolve real DB user ID for cache operations
|
||||||
|
const dbUser = await prisma.user.findUnique({ where: { email: session.user.email }, select: { id: true } });
|
||||||
|
const userId = dbUser?.id;
|
||||||
|
if (!userId) return NextResponse.json({ error: 'User not found' }, { status: 404 });
|
||||||
|
|
||||||
const event = await createCalendarEvent(connection, calendarId, {
|
const event = await createCalendarEvent(connection, calendarId, {
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
@ -114,13 +118,16 @@ export async function PATCH(request: NextRequest) {
|
|||||||
return NextResponse.json({ error: 'Missing required fields' }, { status: 400 });
|
return NextResponse.json({ error: 'Missing required fields' }, { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const userId = (session.user as any).id;
|
const connection = await findConnectionForCalendar(session.user.email, calendarId);
|
||||||
const connection = await findConnectionForCalendar(userId, calendarId);
|
|
||||||
|
|
||||||
if (!connection) {
|
if (!connection) {
|
||||||
return NextResponse.json({ error: 'Calendar connection not found' }, { status: 404 });
|
return NextResponse.json({ error: 'Calendar connection not found' }, { status: 404 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const dbUser2 = await prisma.user.findUnique({ where: { email: session.user.email }, select: { id: true } });
|
||||||
|
const userId = dbUser2?.id;
|
||||||
|
if (!userId) return NextResponse.json({ error: 'User not found' }, { status: 404 });
|
||||||
|
|
||||||
// For "all events" edit mode on recurring events, use the series master ID
|
// For "all events" edit mode on recurring events, use the series master ID
|
||||||
let targetEventId = eventId;
|
let targetEventId = eventId;
|
||||||
if (editMode === 'all' && recurringEventId) {
|
if (editMode === 'all' && recurringEventId) {
|
||||||
@ -191,13 +198,16 @@ export async function DELETE(request: NextRequest) {
|
|||||||
return NextResponse.json({ error: 'Missing required fields' }, { status: 400 });
|
return NextResponse.json({ error: 'Missing required fields' }, { status: 400 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const userId = (session.user as any).id;
|
const connection = await findConnectionForCalendar(session.user.email, calendarId);
|
||||||
const connection = await findConnectionForCalendar(userId, calendarId);
|
|
||||||
|
|
||||||
if (!connection) {
|
if (!connection) {
|
||||||
return NextResponse.json({ error: 'Calendar connection not found' }, { status: 404 });
|
return NextResponse.json({ error: 'Calendar connection not found' }, { status: 404 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const dbUser3 = await prisma.user.findUnique({ where: { email: session.user.email }, select: { id: true } });
|
||||||
|
const userId = dbUser3?.id;
|
||||||
|
if (!userId) return NextResponse.json({ error: 'User not found' }, { status: 404 });
|
||||||
|
|
||||||
await deleteCalendarEvent(connection, calendarId, eventId, deleteMode);
|
await deleteCalendarEvent(connection, calendarId, eventId, deleteMode);
|
||||||
|
|
||||||
// Remove from cache - await to ensure consistency
|
// Remove from cache - await to ensure consistency
|
||||||
|
|||||||
@ -38,10 +38,8 @@ export async function GET(request: NextRequest) {
|
|||||||
// Fetch user's calendars to store initial list
|
// Fetch user's calendars to store initial list
|
||||||
const calendars = await getUserCalendars(accessToken);
|
const calendars = await getUserCalendars(accessToken);
|
||||||
|
|
||||||
const userId = (session.user as any).id;
|
// Always look up by email — session ID can be stale after DB restore/migration
|
||||||
const user = userId
|
const user = await prisma.user.findUnique({ where: { email: session.user.email } });
|
||||||
? await prisma.user.findUnique({ where: { id: userId } })
|
|
||||||
: await prisma.user.findUnique({ where: { email: session.user.email } });
|
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return NextResponse.redirect(new URL('/auth/login', baseUrl));
|
return NextResponse.redirect(new URL('/auth/login', baseUrl));
|
||||||
|
|||||||
@ -6,18 +6,29 @@ import { notifyUser } from '@/lib/sse';
|
|||||||
|
|
||||||
const prisma = new PrismaClient();
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
|
async function resolveUserId(email: string): Promise<string | null> {
|
||||||
|
const user = await prisma.user.findUnique({
|
||||||
|
where: { email },
|
||||||
|
select: { id: true }
|
||||||
|
});
|
||||||
|
return user?.id ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
export async function GET(request: NextRequest) {
|
export async function GET(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const session = await getServerSession(authOptions);
|
const session = await getServerSession(authOptions);
|
||||||
|
|
||||||
if (!session?.user) {
|
if (!session?.user?.email) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: 'Unauthorized' },
|
{ error: 'Unauthorized' },
|
||||||
{ status: 401 }
|
{ status: 401 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const userId = (session.user as any).id;
|
const userId = await resolveUserId(session.user.email);
|
||||||
|
if (!userId) {
|
||||||
|
return NextResponse.json({ error: 'User not found' }, { status: 404 });
|
||||||
|
}
|
||||||
|
|
||||||
const lists = await prisma.somedayList.findMany({
|
const lists = await prisma.somedayList.findMany({
|
||||||
where: { userId },
|
where: { userId },
|
||||||
@ -44,14 +55,18 @@ export async function POST(request: NextRequest) {
|
|||||||
try {
|
try {
|
||||||
const session = await getServerSession(authOptions);
|
const session = await getServerSession(authOptions);
|
||||||
|
|
||||||
if (!session?.user) {
|
if (!session?.user?.email) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: 'Unauthorized' },
|
{ error: 'Unauthorized' },
|
||||||
{ status: 401 }
|
{ status: 401 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const userId = (session.user as any).id;
|
const userId = await resolveUserId(session.user.email);
|
||||||
|
if (!userId) {
|
||||||
|
return NextResponse.json({ error: 'User not found' }, { status: 404 });
|
||||||
|
}
|
||||||
|
|
||||||
const { title } = await request.json();
|
const { title } = await request.json();
|
||||||
|
|
||||||
if (!title) {
|
if (!title) {
|
||||||
@ -92,13 +107,18 @@ export async function DELETE(request: NextRequest) {
|
|||||||
try {
|
try {
|
||||||
const session = await getServerSession(authOptions);
|
const session = await getServerSession(authOptions);
|
||||||
|
|
||||||
if (!session?.user) {
|
if (!session?.user?.email) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: 'Unauthorized' },
|
{ error: 'Unauthorized' },
|
||||||
{ status: 401 }
|
{ status: 401 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const userId = await resolveUserId(session.user.email);
|
||||||
|
if (!userId) {
|
||||||
|
return NextResponse.json({ error: 'User not found' }, { status: 404 });
|
||||||
|
}
|
||||||
|
|
||||||
const { searchParams } = new URL(request.url);
|
const { searchParams } = new URL(request.url);
|
||||||
const id = searchParams.get('id');
|
const id = searchParams.get('id');
|
||||||
|
|
||||||
@ -114,27 +134,13 @@ export async function DELETE(request: NextRequest) {
|
|||||||
where: { id }
|
where: { id }
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!list || list.userId !== (session.user as any).id) {
|
if (!list || list.userId !== userId) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: 'List not found or unauthorized' },
|
{ error: 'List not found or unauthorized' },
|
||||||
{ status: 404 }
|
{ status: 404 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete list (tasks cascade delete is not set in schema for tasks->list, check schema)
|
|
||||||
// In schema: tasks defined as `tasks Task[]`.
|
|
||||||
// We updated schema: `user User ... onDelete: Cascade`. `tasks` are separate.
|
|
||||||
// We need to verify if deleting list deletes tasks or unlinks them.
|
|
||||||
// Schema: `somedayList SomedayList? @relation...`
|
|
||||||
// If we want cascade delete tasks in the list, we should check relations.
|
|
||||||
// Prisma default is usually not cascade for optional relations unless specified.
|
|
||||||
// Let's assume we want to keep tasks or delete them? Usually delete list = delete tasks in it.
|
|
||||||
// Let's explicitly delete tasks first or rely on schema if configured.
|
|
||||||
// Schema update I did: `tasks Task[]`. `Task` has `somedayListId`.
|
|
||||||
// I didn't add `onDelete: Cascade` to the `somedayList` relation in `Task`.
|
|
||||||
// So I should clean up tasks manually or update schema.
|
|
||||||
// For now, let's delete tasks in the list.
|
|
||||||
|
|
||||||
// Soft-delete tasks in this list (they can be recovered from trash)
|
// Soft-delete tasks in this list (they can be recovered from trash)
|
||||||
await prisma.task.updateMany({
|
await prisma.task.updateMany({
|
||||||
where: { somedayListId: id },
|
where: { somedayListId: id },
|
||||||
@ -145,7 +151,7 @@ export async function DELETE(request: NextRequest) {
|
|||||||
where: { id }
|
where: { id }
|
||||||
});
|
});
|
||||||
|
|
||||||
notifyUser((session.user as any).id, "list-changed", { action: "deleted" });
|
notifyUser(userId, "list-changed", { action: "deleted" });
|
||||||
return NextResponse.json({ success: true });
|
return NextResponse.json({ success: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error deleting someday list:', error);
|
console.error('Error deleting someday list:', error);
|
||||||
@ -160,32 +166,34 @@ export async function PATCH(request: NextRequest) {
|
|||||||
try {
|
try {
|
||||||
const session = await getServerSession(authOptions);
|
const session = await getServerSession(authOptions);
|
||||||
|
|
||||||
if (!session?.user) {
|
if (!session?.user?.email) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: 'Unauthorized' },
|
{ error: 'Unauthorized' },
|
||||||
{ status: 401 }
|
{ status: 401 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const userId = await resolveUserId(session.user.email);
|
||||||
|
if (!userId) {
|
||||||
|
return NextResponse.json({ error: 'User not found' }, { status: 404 });
|
||||||
|
}
|
||||||
|
|
||||||
const body = await request.json();
|
const body = await request.json();
|
||||||
|
|
||||||
// Handle Reordering (Array of { id, order })
|
// Handle Reordering (Array of { id, order })
|
||||||
if (Array.isArray(body)) {
|
if (Array.isArray(body)) {
|
||||||
const updates = body.map(async (item: { id: string; order: number }) => {
|
const updates = body.map(async (item: { id: string; order: number }) => {
|
||||||
// Verify ownership for each or just assume if one matches?
|
|
||||||
// Better to be safe, but for performance in batch, we might trust ID if valid.
|
|
||||||
// Let's verify ownership implicitly by where clause.
|
|
||||||
return prisma.somedayList.updateMany({
|
return prisma.somedayList.updateMany({
|
||||||
where: {
|
where: {
|
||||||
id: item.id,
|
id: item.id,
|
||||||
userId: (session.user as any).id
|
userId
|
||||||
},
|
},
|
||||||
data: { order: item.order }
|
data: { order: item.order }
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
await Promise.all(updates);
|
await Promise.all(updates);
|
||||||
notifyUser((session.user as any).id, "list-changed", { action: "reordered" });
|
notifyUser(userId, "list-changed", { action: "reordered" });
|
||||||
return NextResponse.json({ success: true });
|
return NextResponse.json({ success: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -204,7 +212,7 @@ export async function PATCH(request: NextRequest) {
|
|||||||
where: { id }
|
where: { id }
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!existingList || existingList.userId !== (session.user as any).id) {
|
if (!existingList || existingList.userId !== userId) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: 'List not found or unauthorized' },
|
{ error: 'List not found or unauthorized' },
|
||||||
{ status: 404 }
|
{ status: 404 }
|
||||||
@ -220,7 +228,7 @@ export async function PATCH(request: NextRequest) {
|
|||||||
data
|
data
|
||||||
});
|
});
|
||||||
|
|
||||||
notifyUser((session.user as any).id, "list-changed", { action: "updated" });
|
notifyUser(userId, "list-changed", { action: "updated" });
|
||||||
return NextResponse.json({ list });
|
return NextResponse.json({ list });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error updating someday list:', error);
|
console.error('Error updating someday list:', error);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user