From e41ee760b9b8c21f4355f833661f2baa14b6c72a Mon Sep 17 00:00:00 2001 From: Nils-Johan Gynther Date: Fri, 17 Apr 2026 21:22:54 +0200 Subject: [PATCH] feat(products): add reset functionality to delete all products and related data --- backend/src/products/products.controller.ts | 7 +++ backend/src/products/products.service.ts | 16 ++++++ .../admin/products/ResetProductsButton.tsx | 52 +++++++++++++++++++ frontend/app/admin/products/actions.ts | 15 ++++++ frontend/app/admin/products/page.tsx | 3 ++ 5 files changed, 93 insertions(+) create mode 100644 frontend/app/admin/products/ResetProductsButton.tsx diff --git a/backend/src/products/products.controller.ts b/backend/src/products/products.controller.ts index 0bb25f21..910856ff 100644 --- a/backend/src/products/products.controller.ts +++ b/backend/src/products/products.controller.ts @@ -3,6 +3,7 @@ import { Controller, Delete, Get, + HttpCode, Param, ParseIntPipe, Patch, @@ -109,4 +110,10 @@ export class ProductsController { restore(@Param('id', ParseIntPipe) id: number) { return this.productsService.restore(id); } + + @Delete('reset-all') + @HttpCode(200) + resetAll() { + return this.productsService.resetAll(); + } } \ No newline at end of file diff --git a/backend/src/products/products.service.ts b/backend/src/products/products.service.ts index dc463561..e6e26bae 100644 --- a/backend/src/products/products.service.ts +++ b/backend/src/products/products.service.ts @@ -381,4 +381,20 @@ export class ProductsService { async findAllTags() { return this.prisma.tag.findMany({ orderBy: { name: 'asc' } }); } + + async resetAll() { + await this.prisma.$transaction([ + this.prisma.receiptAlias.deleteMany(), + this.prisma.inventoryConsumption.deleteMany(), + this.prisma.inventoryItem.deleteMany(), + this.prisma.pantryItem.deleteMany(), + this.prisma.nutrition.deleteMany(), + this.prisma.productTag.deleteMany(), + this.prisma.tag.deleteMany(), + this.prisma.userProduct.deleteMany(), + this.prisma.recipeIngredient.deleteMany(), + this.prisma.product.deleteMany(), + ]); + return { ok: true }; + } } \ No newline at end of file diff --git a/frontend/app/admin/products/ResetProductsButton.tsx b/frontend/app/admin/products/ResetProductsButton.tsx new file mode 100644 index 00000000..245c39e9 --- /dev/null +++ b/frontend/app/admin/products/ResetProductsButton.tsx @@ -0,0 +1,52 @@ +'use client'; + +import { useState, useTransition } from 'react'; +import { resetAllProducts } from './actions'; + +export default function ResetProductsButton() { + const [isPending, startTransition] = useTransition(); + const [error, setError] = useState(null); + + function handleClick() { + if ( + !confirm( + '⚠️ Detta raderar ALLA produkter, inventory, taggar, kvitto-alias och pantry.\n\nKategorier och användare behålls.\n\nÄr du säker?', + ) + ) + return; + + setError(null); + startTransition(async () => { + try { + await resetAllProducts(); + } catch (err) { + setError(err instanceof Error ? err.message : 'Okänt fel'); + } + }); + } + + return ( +
+ + {error && ( +

{error}

+ )} +
+ ); +} diff --git a/frontend/app/admin/products/actions.ts b/frontend/app/admin/products/actions.ts index 78c28d78..13f14609 100644 --- a/frontend/app/admin/products/actions.ts +++ b/frontend/app/admin/products/actions.ts @@ -73,3 +73,18 @@ export async function deleteProduct(id: number) { revalidatePath('/admin/products'); } + +export async function resetAllProducts() { + const res = await fetch(`${API_BASE}/api/products/reset-all`, { + method: 'DELETE', + headers: { ...(await getAuthHeaders()) }, + cache: 'no-store', + }); + + if (!res.ok) { + const text = await res.text(); + throw new Error(`Kunde inte återställa produkter: ${text}`); + } + + revalidatePath('/admin/products'); +} diff --git a/frontend/app/admin/products/page.tsx b/frontend/app/admin/products/page.tsx index 2181dede..f0ea4ddc 100644 --- a/frontend/app/admin/products/page.tsx +++ b/frontend/app/admin/products/page.tsx @@ -4,6 +4,7 @@ import MergePreviewForm from './MergePreviewForm'; import AdminProductList from './AdminProductList'; import Navigation from '../../Navigation'; import ExpandableCreateProductSection from './ExpandableCreateProductSection'; +import ResetProductsButton from './ResetProductsButton'; export default async function AdminProductsPage() { const products = await fetchJson('/api/products'); @@ -16,6 +17,8 @@ export default async function AdminProductsPage() { + +