import { BadRequestException, Body, Controller, Delete, Get, Headers, Param, Patch, Post, Put, Query, StreamableFile, UploadedFile, UseGuards, UseInterceptors, } from '@nestjs/common'; import { FileInterceptor } from '@nestjs/platform-express'; import { memoryStorage } from 'multer'; import { AuthUser } from '../auth/auth.types'; import { CurrentUser } from '../auth/decorators/current-user.decorator'; import { RequireBusinessPermission } from '../auth/decorators/require-business-permission.decorator'; import { BusinessPermissionGuard } from '../auth/guards/business-permission.guard'; import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; import { WebsiteAnalyticsService } from '../website-analytics/website-analytics.service'; import { BatchCreateStoreItemsDto, BatchUpdateStoreItemDiscountsDto, BatchUpdateFestivalRewardsDto, ListPublicStoreItemsDto, ListStoreItemsDto, SyncProductStoreItemVariantsDto, UpdateStoreItemVariantDto, } from './dto/store-items.dto'; import { StoreItemsService } from './store-items.service'; import { storeItemsExcelFilename } from './store-items-excel'; @Controller('businesses/:businessId/store-items') @UseGuards(JwtAuthGuard, BusinessPermissionGuard) export class StoreItemsController { constructor(private readonly service: StoreItemsService) {} @Get() @RequireBusinessPermission('products.read') list( @Param('businessId') businessId: string, @Query() query: ListStoreItemsDto, @CurrentUser() user: AuthUser, ) { return this.service.list(businessId, query, user); } @Get('export') @RequireBusinessPermission('products.read') async exportExcel( @Param('businessId') businessId: string, @CurrentUser() user: AuthUser, ) { const buffer = await this.service.exportExcel(businessId, user); const filename = storeItemsExcelFilename(); return new StreamableFile(buffer, { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', disposition: `attachment; filename="${filename}"; filename*=UTF-8''${encodeURIComponent(filename)}`, length: buffer.length, }); } @Post('import') @RequireBusinessPermission('products.update') @UseInterceptors( FileInterceptor('file', { storage: memoryStorage(), limits: { fileSize: 8 * 1024 * 1024 }, }), ) importExcel( @Param('businessId') businessId: string, @UploadedFile() file: Express.Multer.File, @CurrentUser() user: AuthUser, ) { if (!file) { throw new BadRequestException('Excel file is required.'); } return this.service.importExcel(businessId, file, user); } @Get('by-product/:productId') @RequireBusinessPermission('products.read') getByProduct( @Param('businessId') businessId: string, @Param('productId') productId: string, @CurrentUser() user: AuthUser, ) { return this.service.getByProduct(businessId, productId, user); } @Post() @RequireBusinessPermission('products.update') batchCreateVariants( @Param('businessId') businessId: string, @Body() dto: BatchCreateStoreItemsDto, @CurrentUser() user: AuthUser, ) { return this.service.batchCreateVariants(businessId, dto, user); } @Put('discounts') @RequireBusinessPermission('products.update') updateDiscounts( @Param('businessId') businessId: string, @Body() dto: BatchUpdateStoreItemDiscountsDto, @CurrentUser() user: AuthUser, ) { return this.service.updateDiscounts(businessId, dto, user); } @Put('sync') @RequireBusinessPermission('products.update') syncProductVariants( @Param('businessId') businessId: string, @Body() dto: SyncProductStoreItemVariantsDto, @CurrentUser() user: AuthUser, ) { return this.service.syncProductVariants(businessId, dto, user); } @Put('festival-rewards') @RequireBusinessPermission('products.update') updateFestivalRewards( @Param('businessId') businessId: string, @Body() dto: BatchUpdateFestivalRewardsDto, @CurrentUser() user: AuthUser, ) { return this.service.updateFestivalRewards(businessId, dto, user); } @Patch('variants/:variantId') @RequireBusinessPermission('products.update') updateVariant( @Param('businessId') businessId: string, @Param('variantId') variantId: string, @Body() dto: UpdateStoreItemVariantDto, @CurrentUser() user: AuthUser, ) { return this.service.updateVariant(businessId, variantId, dto, user); } @Delete('variants/:variantId') @RequireBusinessPermission('products.update') removeVariant( @Param('businessId') businessId: string, @Param('variantId') variantId: string, @CurrentUser() user: AuthUser, ) { return this.service.removeVariant(businessId, variantId, user); } @Delete('by-product/:productId') @RequireBusinessPermission('products.update') removeByProduct( @Param('businessId') businessId: string, @Param('productId') productId: string, @CurrentUser() user: AuthUser, ) { return this.service.removeByProduct(businessId, productId, user); } } @Controller('tenants/:host/store-items') export class PublicStoreItemsController { constructor( private readonly service: StoreItemsService, private readonly analytics: WebsiteAnalyticsService, ) {} @Get() list(@Param('host') host: string, @Query() query: ListPublicStoreItemsDto) { return this.service.listPublic(host, query); } @Get('by-product/:productId') async getByProduct( @Param('host') host: string, @Param('productId') productId: string, @Headers('user-agent') userAgent?: string, ) { const result = await this.service.getPublicByProduct(host, productId); if (result.storeItem) { this.analytics.trackPublicPage( host, 'store_item_detail', result.storeItem.id, undefined, userAgent, ); } return result; } @Get(':variantId') async getVariant( @Param('host') host: string, @Param('variantId') variantId: string, @Headers('user-agent') userAgent?: string, ) { const result = await this.service.getPublicVariant(host, variantId); this.analytics.trackPublicPage( host, 'store_item_detail', result.variant.storeItemId, undefined, userAgent, ); return result; } }