diff --git a/src/merchant/merchant.controller.ts b/src/merchant/merchant.controller.ts index 628f9c5..c9bf44a 100644 --- a/src/merchant/merchant.controller.ts +++ b/src/merchant/merchant.controller.ts @@ -10,6 +10,8 @@ import { ParseIntPipe, HttpCode, HttpStatus, + DefaultValuePipe, + BadRequestException, } from '@nestjs/common'; import { ApiTags, ApiOperation, ApiResponse, ApiParam, ApiQuery } from '@nestjs/swagger'; import { MerchantService } from './services/merchant.service'; @@ -32,19 +34,23 @@ export class MerchantController { @Get() @ApiOperation({ summary: 'Get all merchants' }) - @ApiQuery({ name: 'skip', required: false, type: Number }) - @ApiQuery({ name: 'take', required: false, type: Number }) - @ApiResponse({ status: 200, description: 'List of merchants' }) - findAll( - @Query('skip') skip?: string, - @Query('take') take?: string, + @ApiQuery({ name: 'skip', required: false, type: Number, example: 0 }) + @ApiQuery({ name: 'take', required: false, type: Number, example: 10 }) + @ApiResponse({ status: 200, description: 'Paginated merchants' }) + async findAll( + @Query('skip', new DefaultValuePipe(0), ParseIntPipe) skip: number, + @Query('take', new DefaultValuePipe(10), ParseIntPipe) take: number, ) { - return this.merchantService.findAll( - skip ? parseInt(skip) : 0, - take ? parseInt(take) : 10, - ); + + if (skip < 0) throw new BadRequestException('skip must be >= 0'); + if (take < 1 || take > 100) { + throw new BadRequestException('take must be between 1 and 100'); + } + + return this.merchantService.findAll(skip, take); } + @Get(':id') @ApiOperation({ summary: 'Get merchant by ID' }) @ApiParam({ name: 'id', type: Number }) diff --git a/src/merchant/services/merchant.service.ts b/src/merchant/services/merchant.service.ts index a3384a1..3ba2877 100644 --- a/src/merchant/services/merchant.service.ts +++ b/src/merchant/services/merchant.service.ts @@ -1,4 +1,4 @@ -import { Injectable, NotFoundException, BadRequestException, ConflictException, Inject } from '@nestjs/common'; +import { Injectable, NotFoundException, BadRequestException, ConflictException, Inject, InternalServerErrorException } from '@nestjs/common'; import { MerchantPartnerWithRelations, MerchantUserWithInfo } from '../entities/merchant.entity'; import { EventEmitter2 } from '@nestjs/event-emitter'; import type { UserServiceClient } from '../interfaces/user.service.interface'; @@ -97,23 +97,49 @@ export class MerchantService { } /** - * Find all merchants with optional pagination + * Find merchants with pagination and total count */ - async findAll(skip = 0, take = 10): Promise { - const merchants = await this.prisma.merchantPartner.findMany({ - skip, - take, - include: { - configs: true, - merchantUsers: true, - technicalContacts: true, - }, - orderBy: { - createdAt: 'desc', - }, - }); + async findAll(skip = 0, take = 10): Promise<{ items: MerchantPartnerWithRelations[], total: number }> { + if (skip < 0) throw new BadRequestException('skip must be >= 0'); + if (take < 1 || take > 100) throw new BadRequestException('take must be between 1 et 100'); - return Promise.all(merchants.map(m => this.enrichMerchantWithUserInfo(m))); + try { + const total = await this.prisma.merchantPartner.count(); + + // Ajuster le take si on dépasse le total + if (skip >= total) { + return { items: [], total }; + } + const remaining = total - skip; + const adjustedTake = Math.min(take, remaining); + + const merchants = await this.prisma.merchantPartner.findMany({ + skip, + take: adjustedTake, + include: { + configs: true, + merchantUsers: true, + technicalContacts: true, + }, + orderBy: { createdAt: 'desc' } + }); + + const enrichedMerchants = await Promise.all( + merchants.map(async merchant => { + try { + return await this.enrichMerchantWithUserInfo(merchant); + } catch (e) { + console.error(`⚠️ Error enriching merchant ${merchant.id}:`, e); + return merchant; + } + }) + ); + + return { items: enrichedMerchants, total }; + } catch (error) { + console.error('❌ Failed to fetch merchants:', error); + throw new InternalServerErrorException('Failed to fetch merchants'); + } } /** @@ -186,48 +212,91 @@ export class MerchantService { * Add user to merchant */ async addUserToMerchant(dto: AddUserToMerchantDto): Promise { - // Check if merchant exists - await this.findOne(dto.merchantPartnerId); + console.log('🔹 Starting addUserToMerchant process', dto); - // Validate user exists - const userExists = await this.userServiceClient.verifyUserExists(dto.userId); - if (!userExists) { - throw new BadRequestException(`User with ID ${dto.userId} not found in user service`); + // 1️⃣ Vérifier que le merchant existe + let merchant; + try { + merchant = await this.findOne(dto.merchantPartnerId); + console.log(`✅ Merchant exists: ID=${dto.merchantPartnerId}`); + } catch (err) { + console.error(`❌ Merchant not found: ID=${dto.merchantPartnerId}`, err); + throw new BadRequestException(`Merchant with ID ${dto.merchantPartnerId} not found`); } - // Check if user already attached - const existing = await this.prisma.merchantUser.findUnique({ - where: { - userId_merchantPartnerId: { + // 2️⃣ Vérifier que l'utilisateur existe dans le service utilisateur + let userExists: boolean; + try { + userExists = await this.userServiceClient.verifyUserExists(dto.userId); + console.log(`📌 User exists check for ID=${dto.userId}: ${userExists}`); + if (!userExists) { + throw new BadRequestException(`User with ID ${dto.userId} not found in user service`); + } + } catch (err) { + console.error(`❌ Error verifying user existence: ID=${dto.userId}`, err); + throw err; + } + + // 3️⃣ Vérifier si l'utilisateur est déjà attaché au merchant + let existing; + try { + existing = await this.prisma.merchantUser.findUnique({ + where: { + userId_merchantPartnerId: { + userId: dto.userId, + merchantPartnerId: dto.merchantPartnerId, + }, + }, + }); + console.log(`📌 Existing association check:`, existing ? 'Exists' : 'Not exists'); + if (existing) { + throw new ConflictException(`User ${dto.userId} is already attached to merchant ${dto.merchantPartnerId}`); + } + } catch (err) { + console.error(`❌ Error checking existing merchant user: ID=${dto.userId}`, err); + throw err; + } + + // 4️⃣ Créer l'association + let merchantUser: MerchantUserWithInfo; + try { + merchantUser = await this.prisma.merchantUser.create({ + data: { userId: dto.userId, + role: dto.role, merchantPartnerId: dto.merchantPartnerId, }, - }, - }); - - if (existing) { - throw new ConflictException(`User ${dto.userId} is already attached to merchant ${dto.merchantPartnerId}`); + }); + console.log(`✅ MerchantUser created: ID=${merchantUser.id}, UserID=${dto.userId}, MerchantID=${dto.merchantPartnerId}`); + } catch (err) { + console.error(`❌ Error creating merchantUser`, err); + throw new InternalServerErrorException('Failed to create merchant user'); } - // Create merchant user - const merchantUser = await this.prisma.merchantUser.create({ - data: { + // 5️⃣ Émettre l'événement + try { + this.eventEmitter.emit('merchant.user.attached', { + merchantId: dto.merchantPartnerId, userId: dto.userId, role: dto.role, - merchantPartnerId: dto.merchantPartnerId, - }, - }); + timestamp: new Date(), + }); + console.log(`📣 Event emitted: merchant.user.attached`); + } catch (err) { + console.warn(`⚠️ Failed to emit event for merchant.user.attached`, err); + } - // Emit event - this.eventEmitter.emit('merchant.user.attached', { - merchantId: dto.merchantPartnerId, - userId: dto.userId, - role: dto.role, - timestamp: new Date(), - }); + // 6️⃣ Récupérer les infos utilisateur enrichies + let userInfo; + try { + userInfo = await this.userServiceClient.getUserInfo(dto.userId); + console.log(`📌 User info fetched for ID=${dto.userId}`); + } catch (err) { + console.error(`❌ Failed to fetch user info, returning merchantUser without enrichment`, err); + userInfo = null; + } - // Enrich with user info - const userInfo = await this.userServiceClient.getUserInfo(dto.userId); + // 7️⃣ Retourner l'objet final return { ...merchantUser, userInfo, diff --git a/src/merchant/services/user.service.client.ts b/src/merchant/services/user.service.client.ts index d4a8d01..3b3f6a8 100644 --- a/src/merchant/services/user.service.client.ts +++ b/src/merchant/services/user.service.client.ts @@ -148,7 +148,7 @@ export class HttpUserServiceClient implements UserServiceClient { try { const headers = await this.getAuthHeaders(); const response = await firstValueFrom( - this.httpService.get(`${this.baseUrl}/users/${userId}`, { headers }), + this.httpService.get(`${this.baseUrl}/merchant-users/${userId}`, { headers }), ); return this.mapToUserInfo(response.data); } catch (error) { @@ -174,7 +174,7 @@ export class HttpUserServiceClient implements UserServiceClient { try { const headers = await this.getAuthHeaders(); const response = await firstValueFrom( - this.httpService.post(`${this.baseUrl}/users/batch`, { userIds }, { headers }), + this.httpService.post(`${this.baseUrl}/merchant-users/batch`, { userIds }, { headers }), ); return response.data.map(user => this.mapToUserInfo(user)); } catch (error) {