feat(transfer): add DELETE endpoint, domain events, and event-driven Typesense sync

- DeleteTransferListingCommand/Handler with seller authorization and soft delete (→ CANCELLED)
- Domain events: TransferListingCreated/Updated/DeletedEvent with EventEmitter2
- Event handler: TransferListingTypesenseHandler syncs Typesense on all CUD operations
- Create/Update handlers now emit domain events after persistence
- DELETE /transfer/listings/:id controller endpoint with JWT auth

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Ho Ngoc Hai
2026-04-16 15:27:57 +07:00
parent ca41f7e604
commit a7bcc807ad
13 changed files with 175 additions and 3 deletions

View File

@@ -1,9 +1,10 @@
import { Body, Controller, Get, Param, Patch, Post, Query, UseGuards } from '@nestjs/common';
import { Body, Controller, Delete, Get, Param, Patch, Post, Query, UseGuards } from '@nestjs/common';
import { type CommandBus, type QueryBus } from '@nestjs/cqrs';
import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
import { JwtAuthGuard, CurrentUser } from '@modules/auth';
import { EndpointRateLimit, EndpointRateLimitGuard, NotFoundException } from '@modules/shared';
import { CreateTransferListingCommand } from '../../application/commands/create-transfer-listing/create-transfer-listing.command';
import { DeleteTransferListingCommand } from '../../application/commands/delete-transfer-listing/delete-transfer-listing.command';
import { EstimateFromPhotosCommand } from '../../application/commands/estimate-from-photos/estimate-from-photos.command';
import { EstimateTransferPricesCommand } from '../../application/commands/estimate-transfer-prices/estimate-transfer-prices.command';
import { UpdateTransferListingCommand } from '../../application/commands/update-transfer-listing/update-transfer-listing.command';
@@ -169,4 +170,20 @@ export class TransferController {
),
);
}
@ApiOperation({ summary: 'Xoá tin sang nhượng', description: 'Xoá (soft-delete) tin sang nhượng của bạn' })
@ApiResponse({ status: 200, description: 'Tin sang nhượng đã xoá' })
@ApiResponse({ status: 403, description: 'Không có quyền xoá tin này' })
@ApiResponse({ status: 404, description: 'Không tìm thấy tin sang nhượng' })
@ApiBearerAuth('JWT')
@UseGuards(JwtAuthGuard)
@Delete('listings/:id')
async deleteListing(
@Param('id') id: string,
@CurrentUser() user: { sub: string },
) {
return this.commandBus.execute(
new DeleteTransferListingCommand(id, user.sub),
);
}
}