diff --git a/app/api/tasks/[id]/route.ts b/app/api/tasks/[id]/route.ts index e7bc215..3533cf4 100644 --- a/app/api/tasks/[id]/route.ts +++ b/app/api/tasks/[id]/route.ts @@ -1,6 +1,6 @@ /** * @file app/api/tasks/[id]/route.ts - * @description API route handler for updating a task's status patch endpoint, ensuring user authorization and valid status transitions. + * @description API route handlers for updating a task's status or deleting a task, enforcing user authentication, schema validations, and authorization checks. */ import { NextResponse } from "next/server"; @@ -71,3 +71,46 @@ export async function PATCH(request: Request, context: RouteContext) { ); } } + +/** + * Handles DELETE requests to remove a specific task by its ID. + * Verifies user authentication and checks authorization permissions before deletion. + * + * @async + * @param {Request} request - The incoming HTTP request. + * @param {RouteContext} context - The route context containing dynamic route parameters. + * @returns {Promise} A JSON response confirming deletion or an error message. + */ +export async function DELETE(request: Request, context: RouteContext) { + try { + const session = await auth(); + if (!session?.user?.id) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + const { id: taskId } = await context.params; + + const deletedTask = await TaskService.deleteIfAuthorized( + taskId, + session.user.id, + ); + + if (!deletedTask) { + return NextResponse.json( + { error: "Task not found or access denied" }, + { status: 403 }, + ); + } + + return NextResponse.json( + { success: true, task: deletedTask }, + { status: 200 }, + ); + } catch (error) { + console.error("Error deleting task:", error); + return NextResponse.json( + { error: "Internal Server Error" }, + { status: 500 }, + ); + } +} diff --git a/services/task.service.ts b/services/task.service.ts index adf4ec4..6c85ac0 100644 --- a/services/task.service.ts +++ b/services/task.service.ts @@ -97,4 +97,39 @@ export class TaskService { return (updatedTask as DbTask | undefined) || null; } + + /** + * Deletes a task if the user is authorized as either the owner or an assignee. + * + * @async + * @param {string} taskId - The unique identifier of the task to delete. + * @param {string} userId - The unique identifier of the user performing the deletion. + * @returns {Promise} The deleted task object, or null if the deletion failed or user is unauthorized. + */ + static async deleteIfAuthorized(taskId: string, userId: string) { + const [deletedTask] = await db + .delete(tasksTable) + .where( + and( + eq(tasksTable.id, taskId), + or( + eq(tasksTable.userId, userId), + exists( + db + .select({ taskId: taskAssigneesTable.taskId }) + .from(taskAssigneesTable) + .where( + and( + eq(taskAssigneesTable.taskId, taskId), + eq(taskAssigneesTable.userId, userId), + ), + ), + ), + ), + ), + ) + .returning(); + + return (deletedTask as DbTask | undefined) || null; + } }