import { Body, Controller, Delete, Get, HttpCode, Param, ParseIntPipe, Patch, Post, Put, Query, Request, UseGuards, } from '@nestjs/common'; import { Throttle } from '@nestjs/throttler'; import { Public } from '../auth/decorators/public.decorator'; import { CreateProductDto } from './dto/create-product.dto'; import { UpdateProductDto } from './dto/update-product.dto'; import { ProductsService } from './products.service'; import { MergeProductsDto } from './dto/merge-products.dto'; import { UpdateCanonicalNameDto } from './dto/update-canonical-name.dto'; import { SetTagsDto } from './dto/set-tags.dto'; import { UpsertNutritionDto } from './dto/upsert-nutrition.dto'; import { BulkUpdateProductsDto } from './dto/bulk-update-products.dto'; import { AiCategorizeBulkDto } from './dto/ai-categorize-bulk.dto'; import { SetProductStatusDto } from './dto/set-product-status.dto'; import { UpdateCategoryMineDto } from './dto/update-category-mine.dto'; import { Roles } from '../auth/decorators/roles.decorator'; import { AiService } from '../ai/ai.service'; import { CategoriesService } from '../categories/categories.service'; import { PremiumOrAdminGuard } from '../auth/premium-or-admin.guard'; @Controller('products') export class ProductsController { constructor( private readonly productsService: ProductsService, private readonly aiService: AiService, private readonly categoriesService: CategoriesService, ) {} @Public() @Get() findAll( @Query('tag') tag?: string, ) { return this.productsService.findAll({ tag }); } @Public() @Get('tags') findAllTags() { return this.productsService.findAllTags(); } @Roles('admin') @Get('duplicates') findDuplicates() { return this.productsService.findDuplicateCandidates(); } @Roles('admin') @Get('merge-preview') previewMerge( @Query('sourceProductId', ParseIntPipe) sourceProductId: number, @Query('targetProductId', ParseIntPipe) targetProductId: number, ) { return this.productsService.previewMerge(sourceProductId, targetProductId); } @Roles('admin') @Post('backfill-canonical') backfillCanonical() { return this.productsService.backfillCanonicalNames(); } @Roles('admin') @Get('pending') findPending() { return this.productsService.findPending(); } @Roles('admin') @Get('private') findPrivate() { return this.productsService.findPrivate(); } @Roles('admin') @Post('ai-categorize-bulk') @Throttle({ default: { ttl: 60_000, limit: 5 } }) @HttpCode(200) aiCategorizeBulk(@Body() body: AiCategorizeBulkDto) { return this.productsService.aiCategorizeBulk(body.productIds); } @Roles('admin') @Get('deleted') findDeleted() { return this.productsService.findDeleted(); } // Inloggad användares egna privata produkter (måste vara före :id) @Get('mine') findMine(@Request() req: { user: { id: number } }) { return this.productsService.findByOwner(req.user.id); } // Tillgänglig för alla inloggade användare @Get(':id') findOne(@Param('id', ParseIntPipe) id: number) { return this.productsService.findOne(id); } // Skapa en privat produkt för den inloggade användaren @Post('private') createPrivate( @Body() body: CreateProductDto, @Request() req: { user: { id: number } }, ) { return this.productsService.createPrivate(body, req.user.id); } @UseGuards(PremiumOrAdminGuard) @Get(':id/suggest-category') @Throttle({ default: { ttl: 60_000, limit: 20 } }) async suggestCategory( @Param('id', ParseIntPipe) id: number, ) { const product = await this.productsService.findOne(id); const categories = await this.categoriesService.findFlattened(); return this.aiService.suggestCategory(product.canonicalName ?? product.name, categories); } @Roles('admin') @Post() create(@Body() body: CreateProductDto, @Request() req: { user: { id: number } }) { return this.productsService.create(body, req.user.id); } // Tillgänglig för alla inloggade användare — req.user.id injiceras av JWT-guard @Post('pending') createPending( @Body() body: CreateProductDto, @Request() req: { user: { id: number } }, ) { return this.productsService.createPending(body, req.user.id); } // ── Privata produkter: rename & merge ────────────────────────────────────── // Inloggade användare kan hantera sina egna privata produkter @Patch('private/:id/canonical-name') updateCanonicalNamePrivate( @Param('id', ParseIntPipe) id: number, @Body() body: UpdateCanonicalNameDto, @Request() req: { user: { id: number } }, ) { return this.productsService.updateCanonicalNamePrivate(req.user.id, id, body.canonicalName); } @Post('private/merge') mergePrivate( @Body() body: MergeProductsDto, @Request() req: { user: { id: number } }, ) { return this.productsService.mergePrivate(req.user.id, body.sourceProductId, body.targetProductId); } @Patch('mine/:id/category') updateCategoryMine( @Param('id', ParseIntPipe) id: number, @Body() body: UpdateCategoryMineDto, @Request() req: { user: { id: number } }, ) { return this.productsService.updateCategoryMine(req.user.id, id, body.categoryId); } @Roles('admin') @Post('merge') merge(@Body() body: MergeProductsDto) { return this.productsService.merge(body.sourceProductId, body.targetProductId); } @Roles('admin') @Patch(':id/canonical-name') updateCanonicalName( @Param('id', ParseIntPipe) id: number, @Body() body: UpdateCanonicalNameDto, ) { return this.productsService.updateCanonicalName(id, body.canonicalName); } @Roles('admin') @Put(':id/tags') setTags( @Param('id', ParseIntPipe) id: number, @Body() body: SetTagsDto, ) { return this.productsService.setTags(id, body.tags); } @Roles('admin') @Put(':id/nutrition') upsertNutrition( @Param('id', ParseIntPipe) id: number, @Body() body: UpsertNutritionDto, ) { return this.productsService.upsertNutrition(id, body); } @Roles('admin') @Patch(':id') update( @Param('id', ParseIntPipe) id: number, @Body() body: UpdateProductDto, ) { return this.productsService.update(id, body); } @Roles('admin') @Post('private/:id/promote') promotePrivateToGlobal( @Param('id', ParseIntPipe) id: number, @Request() req: { user: { id: number } }, ) { return this.productsService.promotePrivateToGlobal(id, req.user.id); } @Roles('admin') @Delete(':id/permanent') permanentDelete(@Param('id', ParseIntPipe) id: number) { return this.productsService.permanentDelete(id); } @Roles('admin') @Delete(':id') remove(@Param('id', ParseIntPipe) id: number) { return this.productsService.remove(id); } @Roles('admin') @Patch(':id/status') setStatus( @Param('id', ParseIntPipe) id: number, @Body() body: SetProductStatusDto, ) { return this.productsService.setStatus(id, body.status); } @Roles('admin') @Post(':id/restore') restore(@Param('id', ParseIntPipe) id: number) { return this.productsService.restore(id); } @Roles('admin') @Post('reset-all') @HttpCode(200) resetAll() { return this.productsService.resetAll(); } @Roles('admin') @Post('bulk-update') @HttpCode(200) bulkUpdate(@Body() body: BulkUpdateProductsDto) { return this.productsService.bulkUpdate(body.ids, { categoryId: body.categoryId }); } }