feat: Manage Images using Minio Service
This commit is contained in:
parent
33a9dbde36
commit
5e5ecb6cd1
@ -10,6 +10,8 @@ import {
|
|||||||
ParseIntPipe,
|
ParseIntPipe,
|
||||||
HttpCode,
|
HttpCode,
|
||||||
HttpStatus,
|
HttpStatus,
|
||||||
|
DefaultValuePipe,
|
||||||
|
BadRequestException,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { ApiTags, ApiOperation, ApiResponse, ApiParam, ApiQuery } from '@nestjs/swagger';
|
import { ApiTags, ApiOperation, ApiResponse, ApiParam, ApiQuery } from '@nestjs/swagger';
|
||||||
import { MerchantService } from './services/merchant.service';
|
import { MerchantService } from './services/merchant.service';
|
||||||
@ -32,19 +34,23 @@ export class MerchantController {
|
|||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@ApiOperation({ summary: 'Get all merchants' })
|
@ApiOperation({ summary: 'Get all merchants' })
|
||||||
@ApiQuery({ name: 'skip', required: false, type: Number })
|
@ApiQuery({ name: 'skip', required: false, type: Number, example: 0 })
|
||||||
@ApiQuery({ name: 'take', required: false, type: Number })
|
@ApiQuery({ name: 'take', required: false, type: Number, example: 10 })
|
||||||
@ApiResponse({ status: 200, description: 'List of merchants' })
|
@ApiResponse({ status: 200, description: 'Paginated merchants' })
|
||||||
findAll(
|
async findAll(
|
||||||
@Query('skip') skip?: string,
|
@Query('skip', new DefaultValuePipe(0), ParseIntPipe) skip: number,
|
||||||
@Query('take') take?: string,
|
@Query('take', new DefaultValuePipe(10), ParseIntPipe) take: number,
|
||||||
) {
|
) {
|
||||||
return this.merchantService.findAll(
|
|
||||||
skip ? parseInt(skip) : 0,
|
if (skip < 0) throw new BadRequestException('skip must be >= 0');
|
||||||
take ? parseInt(take) : 10,
|
if (take < 1 || take > 100) {
|
||||||
);
|
throw new BadRequestException('take must be between 1 and 100');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return this.merchantService.findAll(skip, take);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
@ApiOperation({ summary: 'Get merchant by ID' })
|
@ApiOperation({ summary: 'Get merchant by ID' })
|
||||||
@ApiParam({ name: 'id', type: Number })
|
@ApiParam({ name: 'id', type: Number })
|
||||||
|
|||||||
@ -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 { MerchantPartnerWithRelations, MerchantUserWithInfo } from '../entities/merchant.entity';
|
||||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
import type { UserServiceClient } from '../interfaces/user.service.interface';
|
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<MerchantPartnerWithRelations[]> {
|
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');
|
||||||
|
|
||||||
|
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({
|
const merchants = await this.prisma.merchantPartner.findMany({
|
||||||
skip,
|
skip,
|
||||||
take,
|
take: adjustedTake,
|
||||||
include: {
|
include: {
|
||||||
configs: true,
|
configs: true,
|
||||||
merchantUsers: true,
|
merchantUsers: true,
|
||||||
technicalContacts: true,
|
technicalContacts: true,
|
||||||
},
|
},
|
||||||
orderBy: {
|
orderBy: { createdAt: 'desc' }
|
||||||
createdAt: 'desc',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return Promise.all(merchants.map(m => this.enrichMerchantWithUserInfo(m)));
|
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,17 +212,35 @@ export class MerchantService {
|
|||||||
* Add user to merchant
|
* Add user to merchant
|
||||||
*/
|
*/
|
||||||
async addUserToMerchant(dto: AddUserToMerchantDto): Promise<MerchantUserWithInfo> {
|
async addUserToMerchant(dto: AddUserToMerchantDto): Promise<MerchantUserWithInfo> {
|
||||||
// Check if merchant exists
|
console.log('🔹 Starting addUserToMerchant process', dto);
|
||||||
await this.findOne(dto.merchantPartnerId);
|
|
||||||
|
|
||||||
// Validate user exists
|
// 1️⃣ Vérifier que le merchant existe
|
||||||
const userExists = await this.userServiceClient.verifyUserExists(dto.userId);
|
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`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
if (!userExists) {
|
||||||
throw new BadRequestException(`User with ID ${dto.userId} not found in user service`);
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
// Check if user already attached
|
// 3️⃣ Vérifier si l'utilisateur est déjà attaché au merchant
|
||||||
const existing = await this.prisma.merchantUser.findUnique({
|
let existing;
|
||||||
|
try {
|
||||||
|
existing = await this.prisma.merchantUser.findUnique({
|
||||||
where: {
|
where: {
|
||||||
userId_merchantPartnerId: {
|
userId_merchantPartnerId: {
|
||||||
userId: dto.userId,
|
userId: dto.userId,
|
||||||
@ -204,30 +248,55 @@ export class MerchantService {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
console.log(`📌 Existing association check:`, existing ? 'Exists' : 'Not exists');
|
||||||
if (existing) {
|
if (existing) {
|
||||||
throw new ConflictException(`User ${dto.userId} is already attached to merchant ${dto.merchantPartnerId}`);
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
// Create merchant user
|
// 4️⃣ Créer l'association
|
||||||
const merchantUser = await this.prisma.merchantUser.create({
|
let merchantUser: MerchantUserWithInfo;
|
||||||
|
try {
|
||||||
|
merchantUser = await this.prisma.merchantUser.create({
|
||||||
data: {
|
data: {
|
||||||
userId: dto.userId,
|
userId: dto.userId,
|
||||||
role: dto.role,
|
role: dto.role,
|
||||||
merchantPartnerId: dto.merchantPartnerId,
|
merchantPartnerId: 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');
|
||||||
|
}
|
||||||
|
|
||||||
// Emit event
|
// 5️⃣ Émettre l'événement
|
||||||
|
try {
|
||||||
this.eventEmitter.emit('merchant.user.attached', {
|
this.eventEmitter.emit('merchant.user.attached', {
|
||||||
merchantId: dto.merchantPartnerId,
|
merchantId: dto.merchantPartnerId,
|
||||||
userId: dto.userId,
|
userId: dto.userId,
|
||||||
role: dto.role,
|
role: dto.role,
|
||||||
timestamp: new Date(),
|
timestamp: new Date(),
|
||||||
});
|
});
|
||||||
|
console.log(`📣 Event emitted: merchant.user.attached`);
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(`⚠️ Failed to emit event for merchant.user.attached`, err);
|
||||||
|
}
|
||||||
|
|
||||||
// Enrich with user info
|
// 6️⃣ Récupérer les infos utilisateur enrichies
|
||||||
const userInfo = await this.userServiceClient.getUserInfo(dto.userId);
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7️⃣ Retourner l'objet final
|
||||||
return {
|
return {
|
||||||
...merchantUser,
|
...merchantUser,
|
||||||
userInfo,
|
userInfo,
|
||||||
|
|||||||
@ -148,7 +148,7 @@ export class HttpUserServiceClient implements UserServiceClient {
|
|||||||
try {
|
try {
|
||||||
const headers = await this.getAuthHeaders();
|
const headers = await this.getAuthHeaders();
|
||||||
const response = await firstValueFrom(
|
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);
|
return this.mapToUserInfo(response.data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -174,7 +174,7 @@ export class HttpUserServiceClient implements UserServiceClient {
|
|||||||
try {
|
try {
|
||||||
const headers = await this.getAuthHeaders();
|
const headers = await this.getAuthHeaders();
|
||||||
const response = await firstValueFrom(
|
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));
|
return response.data.map(user => this.mapToUserInfo(user));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user