From 09caf5ffa0f1bac45a65b496979b02ac8e68698a Mon Sep 17 00:00:00 2001 From: mARTin Date: Thu, 2 Apr 2026 22:34:26 +0200 Subject: [PATCH] fix: email-based user lookup, slot controls, sub-hour toggle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit API routes (goal, weather, calendar/sync, calendar/events): - All user lookups now use session email instead of session ID so stale JWTs after DB restore/migration no longer break requests (P2003 FK error) WeeklyView QuickSettings sidebar: - effectiveCellDuration now uses local cellDuration state as fallback instead of stale profile.cellDuration — slot buttons now actually update the time grid - Added 20m slot option to all three button groups (sidebar, header, mobile) - Added :15/:30/:45 sub-hour slots toggle to QuickSettings sidebar - Slot controls now highlight using effectiveCellDuration (per-view aware) - Someday and All-day toggles now use effective value, not global profile v1.81.15 --- package.json | 2 +- src/app/api/calendar/events/route.ts | 6 +++--- src/app/api/calendar/sync/route.ts | 16 ++++----------- src/app/api/goal/route.ts | 24 ++++++++++------------ src/app/api/weather/route.ts | 5 ++--- src/components/WeeklyView.tsx | 30 ++++++++++++++++++---------- 6 files changed, 40 insertions(+), 43 deletions(-) diff --git a/package.json b/package.json index 6bdff20..f833b1f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "my-weekly-todo-list", - "version": "1.81.14", + "version": "1.81.15", "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": { diff --git a/src/app/api/calendar/events/route.ts b/src/app/api/calendar/events/route.ts index 7ba68f4..2db57a9 100644 --- a/src/app/api/calendar/events/route.ts +++ b/src/app/api/calendar/events/route.ts @@ -5,10 +5,10 @@ import { prisma } from '@/lib/prisma'; import { createCalendarEvent, updateCalendarEvent, deleteCalendarEvent, CalendarConnection } from '@/lib/calendar-events'; import { upsertCachedEvent, deleteCachedEvent } from '@/lib/calendar-cache'; -// Helper to find connection by calendarId -async function findConnectionForCalendar(userId: string, calendarId: string) { +// Helper to find connection by calendarId (look up by email to avoid stale session IDs) +async function findConnectionForCalendar(email: string, calendarId: string) { const user = await prisma.user.findUnique({ - where: { id: userId }, + where: { email }, include: { calendarConnections: true } }); diff --git a/src/app/api/calendar/sync/route.ts b/src/app/api/calendar/sync/route.ts index 3137e4f..9661b7e 100644 --- a/src/app/api/calendar/sync/route.ts +++ b/src/app/api/calendar/sync/route.ts @@ -9,8 +9,7 @@ import { runSyncRules } from '@/lib/calendar-cross-sync'; export async function POST(request: NextRequest) { try { const session = await getServerSession(authOptions); - const userId = (session?.user as any)?.id; - if (!userId) { + if (!session?.user?.email) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } @@ -20,19 +19,12 @@ export async function POST(request: NextRequest) { const timeMinDate = new Date(timeMin ?? Date.now()); const timeMaxDate = new Date(timeMax ?? Date.now() + 7 * 24 * 60 * 60 * 1000); - let user = await prisma.user.findUnique({ - where: { id: userId }, + // Look up by email — session ID can be stale after DB restore/migration + const user = await prisma.user.findUnique({ + where: { email: session.user.email }, include: { calendarConnections: true }, }); - // Fallback: session ID may be stale (e.g. after DB restore) — try by email - if (!user && session?.user?.email) { - user = await prisma.user.findUnique({ - where: { email: session.user.email }, - include: { calendarConnections: true }, - }) ?? null; - } - if (!user) { return NextResponse.json({ error: 'User not found' }, { status: 404 }); } diff --git a/src/app/api/goal/route.ts b/src/app/api/goal/route.ts index 5bbfce9..440dfe9 100644 --- a/src/app/api/goal/route.ts +++ b/src/app/api/goal/route.ts @@ -10,15 +10,10 @@ export const dynamic = 'force-dynamic'; export async function GET(req: Request) { try { const session = await getServerSession(authOptions); - if (!session || !session.user) { + if (!session?.user?.email) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } - const userId = (session.user as any).id; - if (!userId) { - return NextResponse.json({ error: 'User ID missing from session' }, { status: 400 }); - } - const { searchParams } = new URL(req.url); const weekStartParam = searchParams.get('weekStart'); @@ -29,10 +24,11 @@ export async function GET(req: Request) { const date = new Date(weekStartParam); date.setUTCHours(0, 0, 0, 0); - // Fetch user preferences + // Look up by email — session ID can be stale after DB restore const user = await prisma.user.findUnique({ - where: { id: userId }, + where: { email: session.user.email }, select: { + id: true, goalFallbackType: true, goalDefaultSentence: true, language: true, @@ -40,6 +36,9 @@ export async function GET(req: Request) { } }); + if (!user) return NextResponse.json({ error: 'User not found' }, { status: 404 }); + const userId = user.id; + // 1. Check if user has a custom set goal for THIS week specifically const goal = await prisma.weeklyGoal.findUnique({ where: { @@ -134,14 +133,13 @@ export async function POST(req: Request) { export async function PUT(req: Request) { try { const session = await getServerSession(authOptions); - if (!session || !session.user) { + if (!session?.user?.email) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } - const userId = (session.user as any).id; - if (!userId) { - return NextResponse.json({ error: 'User ID missing from session' }, { status: 400 }); - } + const dbUser = await prisma.user.findUnique({ where: { email: session.user.email }, select: { id: true } }); + if (!dbUser) return NextResponse.json({ error: 'User not found' }, { status: 404 }); + const userId = dbUser.id; const { weekStart, text } = await req.json(); diff --git a/src/app/api/weather/route.ts b/src/app/api/weather/route.ts index 53191c6..6fcadbd 100644 --- a/src/app/api/weather/route.ts +++ b/src/app/api/weather/route.ts @@ -11,13 +11,12 @@ const CACHE_TTL_MS = 15 * 60 * 1000; // 15 minutes export async function GET(request: NextRequest) { const session = await getServerSession(authOptions); - const userId = (session?.user as any)?.id; - if (!userId) { + if (!session?.user?.email) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } const user = await prisma.user.findUnique({ - where: { id: userId }, + where: { email: session.user.email }, select: { weatherEnabled: true, weatherLat: true, weatherLon: true }, }); diff --git a/src/components/WeeklyView.tsx b/src/components/WeeklyView.tsx index e28e350..41c9a4f 100644 --- a/src/components/WeeklyView.tsx +++ b/src/components/WeeklyView.tsx @@ -1040,7 +1040,7 @@ export default function WeeklyView() { const effectiveShowAllDay = getEffective("showAllDayEvents", profile.showAllDayEvents ?? true); const effectiveAllDayPosition = getEffective("allDayPosition", profile.allDayPosition ?? "above") || "above"; const effectiveShowCompletedTasks = getEffective("showCompletedTasks", profile.showCompletedTasks !== false); - const effectiveCellDuration = getEffective("cellDuration", profile.cellDuration ?? 30) as CellDuration; + const effectiveCellDuration = getEffective("cellDuration", cellDuration) as CellDuration; const effectiveStartHour = getEffective("startHour", profile.startHour ?? 8) ?? 8; const effectiveEndHour = getEffective("endHour", profile.endHour ?? 18) ?? 18; @@ -5444,22 +5444,30 @@ export default function WeeklyView() { - {/* Slot Duration (only with time grid) */} + {/* Slot Duration + sub-hour slots (only with time grid) */} {profile.showTimeGrid && (
{profile.language === "de" ? "Zeitfenster" : "Slot"}
- {[15, 30, 60].map((d) => ( - ))}
)} + {profile.showTimeGrid && ( +
+ {profile.language === "de" ? ":15/:30/:45" : ":15/:30/:45"} + +
+ )} {/* Text size */}
@@ -5482,13 +5490,13 @@ export default function WeeklyView() { {/* Toggle switches */}
{profile.language === "de" ? "Irgendwann" : "Someday"} -
{profile.language === "de" ? "Ganztägig" : "All-day"} -
@@ -5738,11 +5746,11 @@ export default function WeeklyView() { title="Slot Duration" > - {[15, 30, 60].map((duration) => ( + {([15, 20, 30, 60] as CellDuration[]).map((duration) => (