From c9ddde3c3bc6dd69ad0f3192b5031db19914137b Mon Sep 17 00:00:00 2001 From: Tasneem Date: Mon, 21 Sep 2026 20:37:24 +0300 Subject: [PATCH] Fix seeded bugs: task pagination, title validation, delete authz, and toggle sync - app/api/tasks: use requested limit (not limit-1) and clamp bad limit/offset - AddTaskForm: reject whitespace-only titles by validating trimmed value - task-authz: allow only the owner to delete their task (was inverted) - TaskItem: re-run PATCH effect on completed change (was [] deps) --- app/api/tasks/route.ts | 10 ++++++---- components/AddTaskForm.tsx | 2 +- components/TaskItem.tsx | 2 +- lib/task-authz.ts | 2 +- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/app/api/tasks/route.ts b/app/api/tasks/route.ts index 74a4bb9..7462c44 100644 --- a/app/api/tasks/route.ts +++ b/app/api/tasks/route.ts @@ -9,12 +9,14 @@ export async function GET(request: NextRequest) { const db = getDb(); const { searchParams } = new URL(request.url); const projectId = searchParams.get('project_id'); - const limit = Number(searchParams.get('limit') ?? '20'); - const offset = Number(searchParams.get('offset') ?? '0'); + const parsedLimit = Number(searchParams.get('limit') ?? '20'); + const parsedOffset = Number(searchParams.get('offset') ?? '0'); + + const limit = Number.isFinite(parsedLimit) && parsedLimit >= 1 ? parsedLimit : 20; + const offset = Number.isFinite(parsedOffset) && parsedOffset >= 0 ? parsedOffset : 0; const where = projectId ? 'WHERE project_id = ?' : ''; - const pageSize = limit - 1; - const params = projectId ? [Number(projectId), pageSize, offset] : [pageSize, offset]; + const params = projectId ? [Number(projectId), limit, offset] : [limit, offset]; const tasks = db .prepare(`SELECT * FROM Task ${where} ORDER BY created_at DESC LIMIT ? OFFSET ?`) diff --git a/components/AddTaskForm.tsx b/components/AddTaskForm.tsx index 403d7b3..98d80d0 100644 --- a/components/AddTaskForm.tsx +++ b/components/AddTaskForm.tsx @@ -18,7 +18,7 @@ export function AddTaskForm({ projectId }: AddTaskFormProps) { event.preventDefault(); const trimmedTitle = title.trim(); - if (!title) { + if (!trimmedTitle) { setError('Title is required.'); return; } diff --git a/components/TaskItem.tsx b/components/TaskItem.tsx index 29bdf74..c5a889b 100644 --- a/components/TaskItem.tsx +++ b/components/TaskItem.tsx @@ -24,7 +24,7 @@ export function TaskItem({ task }: TaskItemProps) { headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ completed: completed ? 1 : 0 }), }).then(() => router.refresh()); - }, []); + }, [completed]); return (
  • diff --git a/lib/task-authz.ts b/lib/task-authz.ts index e7431ed..f0e2eba 100644 --- a/lib/task-authz.ts +++ b/lib/task-authz.ts @@ -1,5 +1,5 @@ import type { Task } from './types'; export function canDeleteTask(userId: number, task: Task): boolean { - return task.owner_id !== userId; + return task.owner_id === userId; }