From 601f9dd1de5602e8ef436cd2ec49f986a9b4fbab Mon Sep 17 00:00:00 2001 From: Lucas Hahne Date: Tue, 18 Aug 2026 19:27:45 +0200 Subject: [PATCH 1/5] Added Category update option to more options inside dashboard --- app/(authenticated)/dashboard/page.tsx | 181 +++++++++++++++++++++++ app/api/tools/update-categories/route.ts | 140 ++++++++++++++++++ lib/mock-tools.ts | 2 +- next-env.d.ts | 2 +- 4 files changed, 323 insertions(+), 2 deletions(-) create mode 100644 app/api/tools/update-categories/route.ts diff --git a/app/(authenticated)/dashboard/page.tsx b/app/(authenticated)/dashboard/page.tsx index 0d7d025..65dd5a6 100644 --- a/app/(authenticated)/dashboard/page.tsx +++ b/app/(authenticated)/dashboard/page.tsx @@ -76,6 +76,11 @@ export default function DashboardPage() { errors: string[]; warnings: string[]; } | null>(null); + const [categoryOptions, setCategoryOptions] = useState>([]); + const [categoryModal, setCategoryModal] = useState<{ toolId: string; toolName: string } | null>(null); + const [selectedCategoryIds, setSelectedCategoryIds] = useState([]); + const [savingCategories, setSavingCategories] = useState(false); + const [categoryError, setCategoryError] = useState(null); useEffect(() => { // Get auth token from sessionStorage (set by layout) @@ -113,6 +118,20 @@ export default function DashboardPage() { })(); }, []); + // Fetch category options once for the "Assign categories" modal + useEffect(() => { + (async () => { + try { + const response = await fetch("/api/categories"); + if (!response.ok) throw new Error("Failed to fetch categories"); + const data = await response.json(); + setCategoryOptions(Array.isArray(data) ? data : []); + } catch (error) { + console.error("Error fetching categories:", error); + } + })(); + }, []); + // Close the "More" dropdown on scroll or resize to avoid stale fixed positioning useEffect(() => { if (openMoreMenuForToolId === null) return; @@ -204,6 +223,61 @@ export default function DashboardPage() { } }; + const openCategoryModal = (toolId: string, toolName: string) => { + setCategoryModal({ toolId, toolName }); + setSelectedCategoryIds([]); + setCategoryError(null); + }; + + const handleCategoryToggle = (categoryId: number) => { + setSelectedCategoryIds((prev) => { + if (prev.includes(categoryId)) { + return prev.filter((id) => id !== categoryId); + } else if (prev.length < 3) { + return [...prev, categoryId]; + } + return prev; + }); + }; + + const handleAssignCategories = async () => { + if (!categoryModal || !authToken) return; + + if (selectedCategoryIds.length === 0) { + setCategoryError("Please select at least one category"); + return; + } + + setSavingCategories(true); + setCategoryError(null); + try { + const response = await fetch("/api/tools/update-categories", { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${authToken}`, + }, + body: JSON.stringify({ toolId: categoryModal.toolId, categoryIds: selectedCategoryIds }), + }); + + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.error || "Failed to assign categories"); + } + + const assigned: Array<{ id: number; name: string }> = data.categories || []; + setTools((prevTools) => prevTools.map((tool) => (tool.id === categoryModal.toolId ? { ...tool, categories: assigned } : tool))); + setCategoryModal(null); + setSelectedCategoryIds([]); + } catch (error) { + console.error("Error assigning categories:", error); + setCategoryError(error instanceof Error ? error.message : "Failed to assign categories. Please try again."); + } finally { + setSavingCategories(false); + } + }; + // Filter tools based on view mode. Intakes only appear in "My Tools". const filteredTools = viewMode === "my" @@ -673,6 +747,27 @@ export default function DashboardPage() { } }} > + {(!tool.categories || tool.categories.length === 0) && ( + + )} + +
+ {categoryError && ( +
{categoryError}
+ )} + {categoryOptions.length === 0 ? ( +

No categories available. Please contact an administrator.

+ ) : ( + <> +
+ {categoryOptions.map((category) => { + const isSelected = selectedCategoryIds.includes(category.id); + const isDisabled = savingCategories || (selectedCategoryIds.length >= 3 && !isSelected); + return ( + + ); + })} +
+

Select up to 3 categories that best describe your tool ({selectedCategoryIds.length}/3 selected)

+ + )} +
+
+ + +
+ + + )} ); } diff --git a/app/api/tools/update-categories/route.ts b/app/api/tools/update-categories/route.ts new file mode 100644 index 0000000..a8d0e35 --- /dev/null +++ b/app/api/tools/update-categories/route.ts @@ -0,0 +1,140 @@ +import { createClient } from "@supabase/supabase-js"; +import { NextRequest, NextResponse } from "next/server"; + +// Create Supabase client with service role for server-side operations +function getSupabaseClient() { + const supabaseUrl = process.env.SUPABASE_URL; + const supabaseServiceKey = process.env.SUPABASE_SERVICE_ROLE_KEY; + + if (!supabaseUrl || !supabaseServiceKey) { + return null; + } + + return createClient(supabaseUrl, supabaseServiceKey); +} + +interface UpdateCategoriesRequest { + toolId: string; + categoryIds: number[]; +} + +export async function POST(request: NextRequest) { + try { + const supabase = getSupabaseClient(); + + if (!supabase) { + return NextResponse.json({ error: "Database connection not configured" }, { status: 500 }); + } + + // Verify user is authenticated + const authHeader = request.headers.get("authorization"); + let userId: string | null = null; + + if (authHeader?.startsWith("Bearer ")) { + const token = authHeader.slice(7); + const { + data: { user }, + error: authError, + } = await supabase.auth.getUser(token); + + if (!authError && user) { + userId = user.id; + } else { + return NextResponse.json({ error: "Unauthorized. Valid user token required." }, { status: 401 }); + } + } + + if (!userId) { + return NextResponse.json({ error: "Unauthorized. Please sign in." }, { status: 401 }); + } + + // Parse request body + const body = (await request.json()) as UpdateCategoriesRequest; + const { toolId, categoryIds } = body; + + if (!toolId) { + return NextResponse.json({ error: "toolId is required" }, { status: 400 }); + } + + if (!categoryIds || !Array.isArray(categoryIds) || categoryIds.length === 0) { + return NextResponse.json({ error: "At least one category is required" }, { status: 400 }); + } + + const uniqueCategoryIds = Array.from(new Set(categoryIds)); + + if (uniqueCategoryIds.length > 3) { + return NextResponse.json({ error: "Please select no more than 3 categories" }, { status: 400 }); + } + + // Verify the tool exists and belongs to the user + const { data: tool, error: fetchError } = await supabase.from("tools").select("id, user_id").eq("id", toolId).single(); + + if (fetchError || !tool) { + return NextResponse.json({ error: "Tool not found" }, { status: 404 }); + } + + if (tool.user_id !== userId) { + return NextResponse.json({ error: "You do not have permission to update this tool" }, { status: 403 }); + } + + // Empty-only: refuse to change categories on a tool that already has some + const { data: existingRelations, error: existingError } = await supabase + .from("tool_categories") + .select("category_id") + .eq("tool_id", toolId); + + if (existingError) { + console.error("Error checking existing tool categories:", existingError); + return NextResponse.json({ error: "Failed to load current categories. Please try again." }, { status: 500 }); + } + + if (existingRelations && existingRelations.length > 0) { + return NextResponse.json({ error: "This tool already has categories assigned." }, { status: 409 }); + } + + // Validate that all provided category IDs exist + const { data: existingCategories, error: categoriesLookupError } = await supabase + .from("categories") + .select("id, name") + .in("id", uniqueCategoryIds); + + if (categoriesLookupError || !existingCategories) { + console.error("Error validating categories:", categoriesLookupError); + return NextResponse.json({ error: "Failed to validate categories. Please try again." }, { status: 500 }); + } + + const validCategoryIds = new Set(existingCategories.map((c) => c.id)); + const invalidCount = uniqueCategoryIds.filter((id) => !validCategoryIds.has(id)).length; + + if (invalidCount > 0) { + return NextResponse.json( + { + error: `${invalidCount} selected ${invalidCount === 1 ? "category is" : "categories are"} invalid. Please try again with valid categories.`, + }, + { status: 400 }, + ); + } + + // Insert category relationships + const categoryRelations = uniqueCategoryIds.map((categoryId) => ({ + tool_id: toolId, + category_id: categoryId, + })); + + const { error: insertError } = await supabase.from("tool_categories").insert(categoryRelations); + + if (insertError) { + console.error("Error inserting tool categories:", insertError); + return NextResponse.json({ error: "Failed to save tool categories. Please try again." }, { status: 500 }); + } + + return NextResponse.json({ + success: true, + message: "Categories assigned successfully", + categories: existingCategories.map((c) => ({ id: c.id, name: c.name })), + }); + } catch (error) { + console.error("Error updating tool categories:", error); + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); + } +} diff --git a/lib/mock-tools.ts b/lib/mock-tools.ts index 76b7d08..a4615f3 100644 --- a/lib/mock-tools.ts +++ b/lib/mock-tools.ts @@ -21,7 +21,7 @@ export const mockTools: MockTool[] = [ description: "Manage your Power Platform solutions with ease. Export, import, and version control your solutions.", icon: "📦", contributors: ["Power Platform ToolBox"], - categories: ["Solutions"], + categories: [], downloads: 1250, rating: 4.8, mau: 320, diff --git a/next-env.d.ts b/next-env.d.ts index c4b7818..9edff1c 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/dev/types/routes.d.ts"; +import "./.next/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. From ec98c2d7155fdd12ac49e3cc3fdeec955a8d0aeb Mon Sep 17 00:00:00 2001 From: Lucas Hahne Date: Wed, 26 Aug 2026 20:46:35 +0200 Subject: [PATCH 2/5] Adjusted category edit mode, changed assign to edit as it is now always available. Fixed tw z- issue --- app/(authenticated)/dashboard/page.tsx | 63 ++++++++++++------------ app/api/tools/update-categories/route.ts | 34 ++++++------- next-env.d.ts | 2 +- 3 files changed, 48 insertions(+), 51 deletions(-) diff --git a/app/(authenticated)/dashboard/page.tsx b/app/(authenticated)/dashboard/page.tsx index 65dd5a6..8786814 100644 --- a/app/(authenticated)/dashboard/page.tsx +++ b/app/(authenticated)/dashboard/page.tsx @@ -118,7 +118,7 @@ export default function DashboardPage() { })(); }, []); - // Fetch category options once for the "Assign categories" modal + // Fetch category options once for the "Edit categories" modal useEffect(() => { (async () => { try { @@ -223,9 +223,9 @@ export default function DashboardPage() { } }; - const openCategoryModal = (toolId: string, toolName: string) => { - setCategoryModal({ toolId, toolName }); - setSelectedCategoryIds([]); + const openCategoryModal = (tool: Tool) => { + setCategoryModal({ toolId: tool.id, toolName: tool.name }); + setSelectedCategoryIds(tool.categories?.map((cat) => cat.id) || []); setCategoryError(null); }; @@ -263,7 +263,7 @@ export default function DashboardPage() { const data = await response.json(); if (!response.ok) { - throw new Error(data.error || "Failed to assign categories"); + throw new Error(data.error || "Failed to update categories"); } const assigned: Array<{ id: number; name: string }> = data.categories || []; @@ -271,8 +271,8 @@ export default function DashboardPage() { setCategoryModal(null); setSelectedCategoryIds([]); } catch (error) { - console.error("Error assigning categories:", error); - setCategoryError(error instanceof Error ? error.message : "Failed to assign categories. Please try again."); + console.error("Error updating categories:", error); + setCategoryError(error instanceof Error ? error.message : "Failed to update categories. Please try again."); } finally { setSavingCategories(false); } @@ -715,7 +715,7 @@ export default function DashboardPage() { {openMoreMenuForToolId === tool.id && ( <>
{ setOpenMoreMenuForToolId(null); moreMenuAnchorRef.current = null; @@ -731,7 +731,7 @@ export default function DashboardPage() { role="menu" tabIndex={-1} autoFocus - className="fixed z-[9999] w-48 rounded-lg border border-slate-200 bg-white py-1 shadow-lg" + className="fixed z-9999 w-48 rounded-lg border border-slate-200 bg-white py-1 shadow-lg" style={ moreMenuAnchorRef.current ? { @@ -747,27 +747,26 @@ export default function DashboardPage() { } }} > - {(!tool.categories || tool.categories.length === 0) && ( - - )} +
)} - {/* Assign Categories Modal */} + {/* Edit Categories Modal */} {categoryModal && (

- Assign categories + Edit categories

{categoryModal.toolName} diff --git a/app/api/tools/update-categories/route.ts b/app/api/tools/update-categories/route.ts index a8d0e35..13da0d4 100644 --- a/app/api/tools/update-categories/route.ts +++ b/app/api/tools/update-categories/route.ts @@ -77,21 +77,6 @@ export async function POST(request: NextRequest) { return NextResponse.json({ error: "You do not have permission to update this tool" }, { status: 403 }); } - // Empty-only: refuse to change categories on a tool that already has some - const { data: existingRelations, error: existingError } = await supabase - .from("tool_categories") - .select("category_id") - .eq("tool_id", toolId); - - if (existingError) { - console.error("Error checking existing tool categories:", existingError); - return NextResponse.json({ error: "Failed to load current categories. Please try again." }, { status: 500 }); - } - - if (existingRelations && existingRelations.length > 0) { - return NextResponse.json({ error: "This tool already has categories assigned." }, { status: 409 }); - } - // Validate that all provided category IDs exist const { data: existingCategories, error: categoriesLookupError } = await supabase .from("categories") @@ -115,7 +100,14 @@ export async function POST(request: NextRequest) { ); } - // Insert category relationships + // Replace existing category relationships so owners can add/remove freely + const { error: deleteError } = await supabase.from("tool_categories").delete().eq("tool_id", toolId); + + if (deleteError) { + console.error("Error clearing tool categories:", deleteError); + return NextResponse.json({ error: "Failed to update tool categories. Please try again." }, { status: 500 }); + } + const categoryRelations = uniqueCategoryIds.map((categoryId) => ({ tool_id: toolId, category_id: categoryId, @@ -128,10 +120,16 @@ export async function POST(request: NextRequest) { return NextResponse.json({ error: "Failed to save tool categories. Please try again." }, { status: 500 }); } + const categoryById = new Map(existingCategories.map((c) => [c.id, c])); + const categories = uniqueCategoryIds + .map((id) => categoryById.get(id)) + .filter((c): c is { id: number; name: string } => Boolean(c)) + .map((c) => ({ id: c.id, name: c.name })); + return NextResponse.json({ success: true, - message: "Categories assigned successfully", - categories: existingCategories.map((c) => ({ id: c.id, name: c.name })), + message: "Categories updated successfully", + categories, }); } catch (error) { console.error("Error updating tool categories:", error); diff --git a/next-env.d.ts b/next-env.d.ts index 9edff1c..c4b7818 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/types/routes.d.ts"; +import "./.next/dev/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. From 348fd5b92d77df68a02e20f82a3bc1dc40b3fe40 Mon Sep 17 00:00:00 2001 From: Lucas Hahne Date: Sun, 30 Aug 2026 20:35:49 +0200 Subject: [PATCH 3/5] Revoked delete and updated category reassignment/update --- app/(authenticated)/dashboard/page.tsx | 178 ++++++++++++----------- app/api/tools/update-categories/route.ts | 51 +++++-- 2 files changed, 130 insertions(+), 99 deletions(-) diff --git a/app/(authenticated)/dashboard/page.tsx b/app/(authenticated)/dashboard/page.tsx index cc2fa29..bd204e4 100644 --- a/app/(authenticated)/dashboard/page.tsx +++ b/app/(authenticated)/dashboard/page.tsx @@ -81,7 +81,7 @@ export default function DashboardPage() { warnings: string[]; } | null>(null); const [categoryOptions, setCategoryOptions] = useState>([]); - const [categoryModal, setCategoryModal] = useState<{ toolId: string; toolName: string } | null>(null); + const [categoryModal, setCategoryModal] = useState<{ toolId: string; toolName: string; existingCategoryIds: number[] } | null>(null); const [selectedCategoryIds, setSelectedCategoryIds] = useState([]); const [savingCategories, setSavingCategories] = useState(false); const [categoryError, setCategoryError] = useState(null); @@ -241,14 +241,18 @@ export default function DashboardPage() { }; const openCategoryModal = (tool: Tool) => { - setCategoryModal({ toolId: tool.id, toolName: tool.name }); - setSelectedCategoryIds(tool.categories?.map((cat) => cat.id) || []); + const existingCategoryIds = tool.categories?.map((cat) => cat.id) || []; + setCategoryModal({ toolId: tool.id, toolName: tool.name, existingCategoryIds }); + setSelectedCategoryIds(existingCategoryIds); setCategoryError(null); }; - const handleCategoryToggle = (categoryId: number) => { + const handleCategoryToggle = (categoryId: number, lockedCategoryIds: number[]) => { setSelectedCategoryIds((prev) => { if (prev.includes(categoryId)) { + if (lockedCategoryIds.includes(categoryId)) { + return prev; + } return prev.filter((id) => id !== categoryId); } else if (prev.length < 3) { return [...prev, categoryId]; @@ -810,7 +814,7 @@ export default function DashboardPage() { d="M7 7h.01M7 3h5a1.99 1.99 0 011.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.99 1.99 0 013 12V7a4 4 0 014-4z" /> - Edit categories + {(tool.categories?.length ?? 0) > 0 ? "Update categories" : "Assign categories"} -

-
- {categoryError && ( -
{categoryError}
- )} - {categoryOptions.length === 0 ? ( -

No categories available. Please contact an administrator.

- ) : ( - <> -
- {categoryOptions.map((category) => { - const isSelected = selectedCategoryIds.includes(category.id); - const isDisabled = savingCategories || (selectedCategoryIds.length >= 3 && !isSelected); - return ( - - ); - })} + {categoryModal && + (() => { + const lockedCategoryIds = categoryModal.existingCategoryIds; + const isInitialAssign = lockedCategoryIds.length === 0; + const hasNewCategories = selectedCategoryIds.some((id) => !lockedCategoryIds.includes(id)); + const canSaveCategories = !savingCategories && categoryOptions.length > 0 && (isInitialAssign ? selectedCategoryIds.length > 0 : hasNewCategories); + + return ( +
e.key === "Escape" && !savingCategories && setCategoryModal(null)} + > +
!savingCategories && setCategoryModal(null)} /> +
+
+
+

+ {isInitialAssign ? "Assign categories" : "Update categories"} +

+

+ {categoryModal.toolName} +

-

Select up to 3 categories that best describe your tool ({selectedCategoryIds.length}/3 selected)

- - )} -
-
- - + +
+
+ {categoryError &&
{categoryError}
} + {categoryOptions.length === 0 ? ( +

No categories available. Please contact an administrator.

+ ) : ( + <> +
+ {categoryOptions.map((category) => { + const isSelected = selectedCategoryIds.includes(category.id); + const isLocked = lockedCategoryIds.includes(category.id); + const isDisabled = savingCategories || isLocked || (selectedCategoryIds.length >= 3 && !isSelected); + return ( + + ); + })} +
+

+ {isInitialAssign + ? `Select up to 3 categories that best describe your tool (${selectedCategoryIds.length}/3 selected)` + : `You can add up to 3 categories total (${selectedCategoryIds.length}/3 selected). To remove a category, contact an administrator.`} +

+ + )} +
+
+ + +
+
-
-
- )} + ); + })()} ); } diff --git a/app/api/tools/update-categories/route.ts b/app/api/tools/update-categories/route.ts index 13da0d4..df8aeb9 100644 --- a/app/api/tools/update-categories/route.ts +++ b/app/api/tools/update-categories/route.ts @@ -100,15 +100,35 @@ export async function POST(request: NextRequest) { ); } - // Replace existing category relationships so owners can add/remove freely - const { error: deleteError } = await supabase.from("tool_categories").delete().eq("tool_id", toolId); + const { data: currentRelations, error: currentRelationsError } = await supabase + .from("tool_categories") + .select("category_id") + .eq("tool_id", toolId); + + if (currentRelationsError) { + console.error("Error fetching current tool categories:", currentRelationsError); + return NextResponse.json({ error: "Failed to load current categories. Please try again." }, { status: 500 }); + } + + const currentCategoryIds = (currentRelations ?? []).map((relation) => relation.category_id); + const removedCategoryIds = currentCategoryIds.filter((id) => !uniqueCategoryIds.includes(id)); - if (deleteError) { - console.error("Error clearing tool categories:", deleteError); - return NextResponse.json({ error: "Failed to update tool categories. Please try again." }, { status: 500 }); + if (removedCategoryIds.length > 0) { + return NextResponse.json( + { + error: "Removing categories is not allowed. Please contact an administrator if you need to remove a category.", + }, + { status: 400 }, + ); } - const categoryRelations = uniqueCategoryIds.map((categoryId) => ({ + const categoriesToAdd = uniqueCategoryIds.filter((id) => !currentCategoryIds.includes(id)); + + if (categoriesToAdd.length === 0) { + return NextResponse.json({ error: "No new categories to add." }, { status: 400 }); + } + + const categoryRelations = categoriesToAdd.map((categoryId) => ({ tool_id: toolId, category_id: categoryId, })); @@ -120,15 +140,22 @@ export async function POST(request: NextRequest) { return NextResponse.json({ error: "Failed to save tool categories. Please try again." }, { status: 500 }); } - const categoryById = new Map(existingCategories.map((c) => [c.id, c])); - const categories = uniqueCategoryIds - .map((id) => categoryById.get(id)) - .filter((c): c is { id: number; name: string } => Boolean(c)) - .map((c) => ({ id: c.id, name: c.name })); + const finalCategoryIds = [...currentCategoryIds, ...categoriesToAdd]; + const { data: finalCategories, error: finalCategoriesError } = await supabase + .from("categories") + .select("id, name") + .in("id", finalCategoryIds); + + if (finalCategoriesError || !finalCategories) { + console.error("Error fetching updated tool categories:", finalCategoriesError); + return NextResponse.json({ error: "Categories were saved but could not be loaded. Please refresh the page." }, { status: 500 }); + } + + const categories = finalCategories.map((category) => ({ id: category.id, name: category.name })); return NextResponse.json({ success: true, - message: "Categories updated successfully", + message: categoriesToAdd.length === uniqueCategoryIds.length ? "Categories assigned successfully" : "Categories added successfully", categories, }); } catch (error) { From 21ce34f27e1ab06d91ddd677da106b1870507421 Mon Sep 17 00:00:00 2001 From: Lucas Hahne <63300977+LucasHahne@users.noreply.github.com> Date: Sun, 30 Aug 2026 22:16:02 +0200 Subject: [PATCH 4/5] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- app/api/tools/update-categories/route.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/api/tools/update-categories/route.ts b/app/api/tools/update-categories/route.ts index df8aeb9..64c0322 100644 --- a/app/api/tools/update-categories/route.ts +++ b/app/api/tools/update-categories/route.ts @@ -60,6 +60,10 @@ export async function POST(request: NextRequest) { return NextResponse.json({ error: "At least one category is required" }, { status: 400 }); } + if (!categoryIds.every((id) => typeof id === "number" && Number.isInteger(id))) { + return NextResponse.json({ error: "categoryIds must be an array of integer IDs" }, { status: 400 }); + } + const uniqueCategoryIds = Array.from(new Set(categoryIds)); if (uniqueCategoryIds.length > 3) { From 3ac7e069d5066dc1af3459b4185ed7b17f7dc4be Mon Sep 17 00:00:00 2001 From: Lucas Hahne Date: Sun, 30 Aug 2026 22:32:28 +0200 Subject: [PATCH 5/5] Adjusted 500 error to 400 when Json is malformed --- app/api/tools/update-categories/route.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/api/tools/update-categories/route.ts b/app/api/tools/update-categories/route.ts index 64c0322..8e12e9c 100644 --- a/app/api/tools/update-categories/route.ts +++ b/app/api/tools/update-categories/route.ts @@ -49,7 +49,13 @@ export async function POST(request: NextRequest) { } // Parse request body - const body = (await request.json()) as UpdateCategoriesRequest; + let body: UpdateCategoriesRequest; + try { + body = (await request.json()) as UpdateCategoriesRequest; + } catch { + return NextResponse.json({ error: "Invalid request body. Expected JSON." }, { status: 400 }); + } + const { toolId, categoryIds } = body; if (!toolId) {