import { BadRequestException, Body, Controller, Delete, HttpCode, Param, ParseUUIDPipe, Patch, Post, UploadedFile, UseInterceptors, } from '@nestjs/common'; import { FileInterceptor } from '@nestjs/platform-express'; import { ApiBearerAuth, ApiBody, ApiConsumes, ApiCreatedResponse, ApiForbiddenResponse, ApiNoContentResponse, ApiNotFoundResponse, ApiOkResponse, ApiOperation, ApiTags, ApiUnauthorizedResponse, } from '@nestjs/swagger'; import { CurrentUser } from '../../../common/decorators/current-user.decorator'; import { RequirePrivilege } from '../../../common/decorators/require-privilege.decorator'; import { BEARER_AUTH_NAME } from '../../../common/swagger/setup-swagger'; import { isAllowedCsvUpload } from './customer-fields'; import { CUSTOMER_PRIVILEGE_KEY } from './customers-read.controller'; import { CustomersService } from './customers.service'; import { BulkIdsDto, BulkStatusDto, CreateCustomerContactDto, CreateCustomerDto, CustomerDto, UpdateCustomerContactDto, UpdateCustomerDto, UpdateCustomerStatusDto, } from './dto/customer.dto'; @ApiTags('customers') @ApiBearerAuth(BEARER_AUTH_NAME) @Controller('customers') export class CustomersWriteController { constructor(private readonly customersService: CustomersService) {} @Post('import') @RequirePrivilege(CUSTOMER_PRIVILEGE_KEY, 'import') @UseInterceptors( FileInterceptor('file', { limits: { fileSize: 1_048_576 }, fileFilter: (_req, file, cb) => { if (!isAllowedCsvUpload(file)) { cb(new BadRequestException('Only CSV files are allowed'), false); return; } cb(null, true); }, }), ) @ApiConsumes('multipart/form-data') @ApiBody({ schema: { type: 'object', properties: { file: { type: 'string', format: 'binary' }, }, required: ['file'], }, }) @ApiOperation({ summary: 'Import customers from CSV' }) @ApiOkResponse({ schema: { properties: { imported: { type: 'number' } } }, }) @ApiUnauthorizedResponse() @ApiForbiddenResponse() importCsv( @UploadedFile() file: { buffer?: Buffer } | undefined, @CurrentUser('id') userId: string, ): Promise<{ imported: number }> { const csv = file?.buffer?.toString('utf8') ?? ''; return this.customersService.importCsv(csv, userId); } @Post('bulk-delete') @HttpCode(200) @RequirePrivilege(CUSTOMER_PRIVILEGE_KEY, 'delete') @ApiOperation({ summary: 'Bulk delete customers' }) @ApiOkResponse({ schema: { properties: { deleted: { type: 'number' } } }, }) @ApiUnauthorizedResponse() @ApiForbiddenResponse() bulkDelete(@Body() dto: BulkIdsDto): Promise<{ deleted: number }> { return this.customersService.bulkDelete(dto.ids); } @Post('bulk-status') @HttpCode(200) @RequirePrivilege(CUSTOMER_PRIVILEGE_KEY, 'update') @ApiOperation({ summary: 'Bulk update customer status' }) @ApiOkResponse({ schema: { properties: { updated: { type: 'number' } } }, }) @ApiUnauthorizedResponse() @ApiForbiddenResponse() bulkStatus( @Body() dto: BulkStatusDto, @CurrentUser('id') userId: string, ): Promise<{ updated: number }> { return this.customersService.bulkUpdateStatus(dto.ids, dto.status, userId); } @Post() @RequirePrivilege(CUSTOMER_PRIVILEGE_KEY, 'create') @ApiOperation({ summary: 'Create customer' }) @ApiCreatedResponse({ type: CustomerDto }) @ApiUnauthorizedResponse() @ApiForbiddenResponse() create( @Body() dto: CreateCustomerDto, @CurrentUser('id') userId: string, ): Promise { return this.customersService.create({ ...dto, userId, }); } @Post(':id/contacts') @RequirePrivilege(CUSTOMER_PRIVILEGE_KEY, 'update') @ApiOperation({ summary: 'Add a customer contact' }) @ApiOkResponse({ type: CustomerDto }) @ApiNotFoundResponse() @ApiUnauthorizedResponse() @ApiForbiddenResponse() addContact( @Param('id', ParseUUIDPipe) id: string, @Body() dto: CreateCustomerContactDto, @CurrentUser('id') userId: string, ): Promise { return this.customersService.addContact(id, dto, userId); } @Patch(':id/contacts/:contactId') @RequirePrivilege(CUSTOMER_PRIVILEGE_KEY, 'update') @ApiOperation({ summary: 'Update a customer contact' }) @ApiOkResponse({ type: CustomerDto }) @ApiNotFoundResponse() @ApiUnauthorizedResponse() @ApiForbiddenResponse() updateContact( @Param('id', ParseUUIDPipe) id: string, @Param('contactId', ParseUUIDPipe) contactId: string, @Body() dto: UpdateCustomerContactDto, @CurrentUser('id') userId: string, ): Promise { return this.customersService.updateContact(id, contactId, { ...dto, userId, }); } @Delete(':id/contacts/:contactId') @HttpCode(204) @RequirePrivilege(CUSTOMER_PRIVILEGE_KEY, 'update') @ApiOperation({ summary: 'Delete a customer contact' }) @ApiNoContentResponse() @ApiNotFoundResponse() @ApiUnauthorizedResponse() @ApiForbiddenResponse() async deleteContact( @Param('id', ParseUUIDPipe) id: string, @Param('contactId', ParseUUIDPipe) contactId: string, ): Promise { await this.customersService.deleteContact(id, contactId); } @Patch(':id/status') @RequirePrivilege(CUSTOMER_PRIVILEGE_KEY, 'update') @ApiOperation({ summary: 'Update customer status' }) @ApiOkResponse({ type: CustomerDto }) @ApiNotFoundResponse() @ApiUnauthorizedResponse() @ApiForbiddenResponse() updateStatus( @Param('id', ParseUUIDPipe) id: string, @Body() dto: UpdateCustomerStatusDto, @CurrentUser('id') userId: string, ): Promise { return this.customersService.updateStatus(id, dto.status, userId); } @Patch(':id') @RequirePrivilege(CUSTOMER_PRIVILEGE_KEY, 'update') @ApiOperation({ summary: 'Update customer (not status)' }) @ApiOkResponse({ type: CustomerDto }) @ApiNotFoundResponse() @ApiUnauthorizedResponse() @ApiForbiddenResponse() update( @Param('id', ParseUUIDPipe) id: string, @Body() dto: UpdateCustomerDto, @CurrentUser('id') userId: string, ): Promise { return this.customersService.update(id, { ...dto, userId, }); } @Delete(':id') @HttpCode(204) @RequirePrivilege(CUSTOMER_PRIVILEGE_KEY, 'delete') @ApiOperation({ summary: 'Delete customer' }) @ApiNoContentResponse() @ApiNotFoundResponse() @ApiUnauthorizedResponse() @ApiForbiddenResponse() async delete(@Param('id', ParseUUIDPipe) id: string): Promise { await this.customersService.delete(id); } }