feat: Manage Images using Minio Service

This commit is contained in:
diallolatoile 2026-01-13 03:49:10 +00:00
parent a45f4b151c
commit d26feb396f
10 changed files with 97 additions and 1426 deletions

View File

@ -150,7 +150,7 @@ export class DashboardAccessService {
if (access.isHubUser) {
return this.merchantService.getAllMerchants().pipe(
map(merchants => {
const available: AllowedMerchant[] = merchants.map(m => ({
const available: AllowedMerchant[] = merchants.items.map(m => ({
id: m.id,
name: m.name
}));

View File

@ -8,7 +8,6 @@ import { Subject, takeUntil } from 'rxjs';
import { HubUsersService } from './hub-users.service';
import { RoleManagementService } from '@core/services/hub-users-roles-management.service';
import { AuthService } from '@core/services/auth.service';
import { MerchantSyncService } from './merchant-sync-orchestrator.service';
import { PageTitle } from '@app/components/page-title/page-title';
import { HubUsersList } from './hub-users-list/hub-users-list';
import { HubUserProfile } from './hub-users-profile/hub-users-profile';
@ -39,7 +38,6 @@ export class HubUsersManagement implements OnInit, OnDestroy {
private modalService = inject(NgbModal);
private authService = inject(AuthService);
private hubUsersService = inject(HubUsersService);
private merchantSyncService = inject(MerchantSyncService);
protected roleService = inject(RoleManagementService);
private cdRef = inject(ChangeDetectorRef);
private destroy$ = new Subject<void>();
@ -476,45 +474,6 @@ export class HubUsersManagement implements OnInit, OnDestroy {
});
}
/**
* Crée un marchand dans MerchantConfig (séparément de l'utilisateur)
*/
createMerchant() {
// Validation du formulaire marchand
const merchantValidation = this.validateMerchantForm();
if (!merchantValidation.isValid) {
this.createUserError = merchantValidation.error!;
console.error('❌ Merchant form validation failed:', merchantValidation.error);
return;
}
this.creatingMerchant = true;
console.log('📤 Creating merchant in MerchantConfig...');
this.merchantSyncService.createMerchantInConfigOnly(this.newMerchant)
.pipe(takeUntil(this.destroy$))
.subscribe({
next: (merchantConfig) => {
console.log('✅ Merchant created in MerchantConfig:', merchantConfig);
this.creatingMerchant = false;
// Optionnel: proposer d'associer un utilisateur au marchand créé
console.log(`✅ Merchant ID: ${merchantConfig.id} - Name: ${merchantConfig.name}`);
this.modalService.dismissAll();
this.resetMerchantForm();
this.cdRef.detectChanges();
},
error: (error) => {
console.error('❌ Error creating merchant in MerchantConfig:', error);
this.creatingMerchant = false;
this.createUserError = this.getMerchantErrorMessage(error);
this.cdRef.detectChanges();
}
});
}
/**
* Vérifie si le rôle est un rôle partenaire
*/

View File

@ -1,507 +0,0 @@
import { Injectable, inject } from '@angular/core';
import { Observable, forkJoin, map, switchMap, catchError, of, throwError } from 'rxjs';
import {
CreateUserDto,
User,
UserType,
UserRole,
} from '@core/models/dcb-bo-hub-user.model';
import { MerchantUsersService} from '@modules/hub-users-management/merchant-users.service';
import { MerchantConfigService } from '@modules/merchant-config/merchant-config.service';
import {
CreateMerchantDto,
UpdateMerchantDto,
Merchant,
MerchantUser,
AddUserToMerchantDto,
UpdateUserRoleDto
} from '@core/models/merchant-config.model';
export interface MerchantSyncResult {
merchantConfig: Merchant;
keycloakUser?: User; // Utilisateur associé (optionnel)
}
export interface MerchantUserSyncResult {
keycloakUser: User;
merchantConfigUser?: MerchantUser;
}
export interface MerchantSyncStatus {
existsInKeycloak: boolean;
existsInMerchantConfig: boolean;
usersSynced: boolean;
syncedUserCount: number;
totalUserCount: number;
}
export interface UserMerchantAssociation {
userId: string;
merchantConfigId: string;
role: UserRole; // Rôle dans MerchantConfig
}
@Injectable({ providedIn: 'root' })
export class MerchantSyncService {
private merchantUsersService = inject(MerchantUsersService);
private merchantConfigService = inject(MerchantConfigService);
// ==================== CONSTANTES ====================
private readonly KEYCLOAK_MERCHANT_ROLES = [
UserRole.DCB_PARTNER_ADMIN,
UserRole.DCB_PARTNER_MANAGER,
UserRole.DCB_PARTNER_SUPPORT
];
private readonly MERCHANT_CONFIG_ROLES = [
UserRole.MERCHANT_CONFIG_ADMIN,
UserRole.MERCHANT_CONFIG_MANAGER,
UserRole.MERCHANT_CONFIG_TECHNICAL,
UserRole.MERCHANT_CONFIG_VIEWER
];
private readonly ROLE_MAPPING: Map<UserRole, UserRole> = new Map([
// Keycloak -> Merchant Config
[UserRole.DCB_PARTNER_ADMIN, UserRole.MERCHANT_CONFIG_ADMIN],
[UserRole.DCB_PARTNER_MANAGER, UserRole.MERCHANT_CONFIG_MANAGER],
[UserRole.DCB_PARTNER_SUPPORT, UserRole.MERCHANT_CONFIG_VIEWER],
// Merchant Config -> Keycloak
[UserRole.MERCHANT_CONFIG_ADMIN, UserRole.DCB_PARTNER_ADMIN],
[UserRole.MERCHANT_CONFIG_MANAGER, UserRole.DCB_PARTNER_MANAGER],
[UserRole.MERCHANT_CONFIG_TECHNICAL, UserRole.DCB_PARTNER_SUPPORT],
[UserRole.MERCHANT_CONFIG_VIEWER, UserRole.DCB_PARTNER_SUPPORT]
]);
// ==================== MÉTHODES DE BASE ====================
/**
* CREATE - Créer un merchant uniquement dans MerchantConfig
*/
createMerchantInConfigOnly(merchantData: CreateMerchantDto): Observable<Merchant> {
console.log('📝 CREATE Merchant dans MerchantConfig seulement...');
return this.merchantConfigService.createMerchant(merchantData).pipe(
map(merchant => {
console.log('✅ Merchant créé dans MerchantConfig:', merchant);
return merchant;
}),
catchError(error => {
console.error('❌ Échec création merchant dans MerchantConfig:', error);
return throwError(() => error);
})
);
}
/**
* CREATE - Créer un utilisateur dans Keycloak (sans association)
*/
createKeycloakUser(
userData: {
username: string;
email: string;
password: string;
firstName: string;
lastName: string;
role: UserRole;
enabled?: boolean;
emailVerified?: boolean;
}
): Observable<User> {
console.log('📝 CREATE User dans Keycloak...');
// Déterminer le type d'utilisateur selon le rôle
const userType = this.isMerchantRole(userData.role)
? UserType.MERCHANT_PARTNER
: UserType.HUB;
const keycloakUserDto: CreateUserDto = {
username: userData.username,
email: userData.email,
firstName: userData.firstName,
lastName: userData.lastName,
password: userData.password,
userType: userType,
role: userData.role,
enabled: userData.enabled ?? true,
emailVerified: userData.emailVerified ?? false
};
// Sélectionner le service approprié selon le type d'utilisateur
const createService = this.merchantUsersService.createMerchantUser(keycloakUserDto);
return createService.pipe(
map(user => {
console.log('✅ Utilisateur créé dans Keycloak:', user);
return user;
}),
catchError(error => {
console.error('❌ Échec création utilisateur dans Keycloak:', error);
return throwError(() => error);
})
);
}
/**
* READ - Récupérer un merchant depuis MerchantConfig seulement
*/
getMerchantFromConfigOnly(merchantConfigId: string): Observable<Merchant> {
console.log('🔍 READ Merchant depuis MerchantConfig seulement...');
return this.merchantConfigService.getMerchantById(Number(merchantConfigId)).pipe(
map(merchant => {
console.log('✅ Merchant récupéré depuis MerchantConfig:', merchant);
return merchant;
}),
catchError(error => {
console.error('❌ Erreur récupération merchant depuis MerchantConfig:', error);
return throwError(() => error);
})
);
}
/**
* READ - Récupérer tous les merchants depuis MerchantConfig
*/
getAllMerchantsFromConfig(): Observable<Merchant[]> {
console.log('🔍 READ All Merchants depuis MerchantConfig...');
return this.merchantConfigService.getAllMerchants().pipe(
map(merchants => {
console.log(`${merchants.length} merchants récupérés depuis MerchantConfig`);
return merchants;
}),
catchError(error => {
console.error('❌ Erreur récupération merchants depuis MerchantConfig:', error);
return throwError(() => error);
})
);
}
// ==================== ASSOCIATION UTILISATEUR-MERCHANT ====================
/**
* Associer un utilisateur existant à un marchand
*/
associateUserToMerchant(
association: UserMerchantAssociation
): Observable<MerchantUserSyncResult> {
console.log('🔗 ASSOCIATE User to Merchant...');
return forkJoin({
user: this.getUserById(association.userId),
merchant: this.merchantConfigService.getMerchantById(Number(association.merchantConfigId))
}).pipe(
switchMap(({ user, merchant }) => {
console.log(`🔗 Associating user ${user.username} to merchant ${merchant.name}`);
// 2. Ajouter l'utilisateur à MerchantConfig
const merchantConfigUserDto: AddUserToMerchantDto = {
userId: user.id,
role: association.role,
merchantPartnerId: Number(association.merchantConfigId),
};
return this.merchantConfigService.addUserToMerchant(merchantConfigUserDto).pipe(
map(merchantConfigUser => {
console.log('✅ User added to Merchant Config:', merchantConfigUser);
return {
keycloakUser: user,
merchantConfigUser
};
}),
catchError(error => {
console.error('❌ Error adding user to MerchantConfig:', error);
return throwError(() => error);
})
);
}),
catchError(error => {
console.error('❌ Error in association process:', error);
return throwError(() => error);
})
);
}
/**
* Dissocier un utilisateur d'un marchand
*/
dissociateUserFromMerchant(
userId: string,
merchantConfigId: string
): Observable<{ success: boolean; message: string }> {
console.log('🔗 DISSOCIATE User from Merchant...');
return forkJoin({
// Retirer l'utilisateur de MerchantConfig
merchantConfigRemoval: this.merchantConfigService.removeUserFromMerchant(
Number(merchantConfigId),
userId
).pipe(catchError(() => of({ ignored: true })))
}).pipe(
map(() => ({
success: true,
message: 'Utilisateur dissocié du marchand avec succès'
})),
catchError(async (error) => ({
success: false,
message: `Erreur lors de la dissociation: ${error.message}`
}))
);
}
/**
* Récupérer les utilisateurs associés à un marchand
*/
getUsersByMerchant(merchantConfigId: string): Observable<User[]> {
console.log('🔍 READ Users by Merchant...');
// Récupérer les utilisateurs de MerchantConfig
return this.merchantConfigService.getMerchantUsers(Number(merchantConfigId)).pipe(
switchMap(merchantConfigUsers => {
if (merchantConfigUsers.total === 0) {
return of([]);
}
// Récupérer les détails de chaque utilisateur depuis Keycloak
const userObservables = merchantConfigUsers.items.map((mcUser: { userId: string; }) =>
this.getUserById(mcUser.userId).pipe(
catchError(() => of(null)) // Ignorer les utilisateurs non trouvés
)
);
return forkJoin(userObservables).pipe(
map(users => users.filter((user): user is User => user !== null))
);
}),
map(users => {
console.log(`${users.length} utilisateurs trouvés pour merchant ${merchantConfigId}`);
return users;
}),
catchError(error => {
console.error('❌ Erreur récupération utilisateurs:', error);
return throwError(() => error);
})
);
}
// ==================== MÉTHODES DE GESTION ====================
/**
* UPDATE - Mettre à jour un merchant dans MerchantConfig seulement
*/
updateMerchantInConfigOnly(
merchantConfigId: string,
updates: UpdateMerchantDto
): Observable<Merchant> {
console.log('✏️ UPDATE Merchant dans MerchantConfig seulement...');
return this.merchantConfigService.updateMerchant(
Number(merchantConfigId),
updates
).pipe(
map(merchant => {
console.log('✅ Merchant mis à jour dans MerchantConfig:', merchant);
return merchant;
}),
catchError(error => {
console.error('❌ Erreur mise à jour merchant dans MerchantConfig:', error);
return throwError(() => error);
})
);
}
/**
* UPDATE - Mettre à jour un utilisateur dans Keycloak
*/
updateKeycloakUserRole(
keycloakUserId: string,
newRole: UserRole
): Observable<UpdateUserRoleDto> {
console.log('✏️ UPDATE User dans Keycloak...');
return this.getUserById(keycloakUserId).pipe(
switchMap(user => {
const updateService = this.merchantUsersService.updateMerchantUserRole(keycloakUserId, newRole);
return updateService.pipe(
map(updatedUser => {
console.log('✅ Utilisateur mis à jour dans Keycloak:', updatedUser);
return updatedUser;
})
);
}),
catchError(error => {
console.error('❌ Erreur mise à jour utilisateur dans Keycloak:', error);
return throwError(() => error);
})
);
}
/**
* UPDATE - Changer le rôle d'un utilisateur dans MerchantConfig
*/
updateUserRoleInMerchantConfig(
merchantConfigId: string,
userId: string,
newRole: UserRole
): Observable<MerchantUser> {
console.log('✏️ UPDATE User Role dans MerchantConfig...');
const updateRoleDto: UpdateUserRoleDto = {
role: newRole
};
this.updateKeycloakUserRole(userId, newRole)
return this.merchantConfigService.updateUserRole(
Number(merchantConfigId),
userId,
updateRoleDto
).pipe(
map(merchantConfigUser => {
console.log('✅ Rôle utilisateur mis à jour dans MerchantConfig:', merchantConfigUser);
return merchantConfigUser;
}),
catchError(error => {
console.error('❌ Erreur changement rôle utilisateur dans MerchantConfig:', error);
return throwError(() => error);
})
);
}
/**
* DELETE - Supprimer un merchant de MerchantConfig seulement
*/
deleteMerchantFromConfigOnly(
merchantConfigId: string
): Observable<{ success: boolean; message: string }> {
console.log('🗑️ DELETE Merchant de MerchantConfig seulement...');
return this.merchantConfigService.deleteMerchant(Number(merchantConfigId)).pipe(
map(() => ({
success: true,
message: 'Merchant supprimé de MerchantConfig avec succès'
})),
catchError(async (error) => ({
success: false,
message: `Erreur suppression merchant: ${error.message}`
}))
);
}
/**
* DELETE - Supprimer un utilisateur de Keycloak
*/
deleteKeycloakUser(
userId: string
): Observable<{ success: boolean; message: string }> {
console.log('🗑️ DELETE User de Keycloak...');
return this.getUserById(userId).pipe(
switchMap(user => {
const deleteService = this.merchantUsersService.deleteMerchantUser(userId);
return deleteService.pipe(
map(() => ({
success: true,
message: 'Utilisateur supprimé de Keycloak avec succès'
}))
);
}),
catchError(async (error) => ({
success: false,
message: `Erreur suppression utilisateur: ${error.message}`
}))
);
}
// ==================== MÉTHODES DE RECHERCHE ====================
/**
* Rechercher des merchants dans MerchantConfig
*/
searchMerchantsInConfig(query: string): Observable<Merchant[]> {
console.log('🔍 SEARCH Merchants dans MerchantConfig...');
return this.merchantConfigService.getAllMerchants({ query }).pipe(
map(merchants => {
console.log(`${merchants.length} merchants trouvés avec "${query}"`);
return merchants;
}),
catchError(error => {
console.error('❌ Erreur recherche merchants dans MerchantConfig:', error);
return throwError(() => error);
})
);
}
/**
* Rechercher des utilisateurs dans Keycloak
*/
searchKeycloakUsers(query: string): Observable<User[]> {
console.log('🔍 SEARCH Users dans Keycloak...');
// Rechercher dans les deux types d'utilisateurs
return forkJoin({
merchantUsers: this.merchantUsersService.searchMerchantUsers({ query }).pipe(
catchError(() => of([]))
)
}).pipe(
map(({ merchantUsers }) => {
const allUsers = [
...merchantUsers
];
console.log(`${allUsers.length} utilisateurs trouvés avec "${query}"`);
return allUsers;
}),
catchError(error => {
console.error('❌ Erreur recherche utilisateurs:', error);
return throwError(() => error);
})
);
}
// ==================== MÉTHODES PRIVÉES ====================
/**
* Récupérer un utilisateur par ID (gère les deux types)
*/
private getUserById(userId: string): Observable<User> {
return forkJoin({
merchantUser: this.merchantUsersService.getMerchantUserById(userId).pipe(
catchError(() => of(null))
)
}).pipe(
map(({ merchantUser }) => {
if (merchantUser) return merchantUser;
throw new Error(`Utilisateur ${userId} non trouvé`);
}),
catchError(error => {
console.error(`❌ Erreur récupération utilisateur ${userId}:`, error);
return throwError(() => error);
})
);
}
/**
* Vérifie si le rôle est un rôle partenaire
*/
private isMerchantRole(role: UserRole): boolean {
return [
UserRole.DCB_PARTNER_ADMIN,
UserRole.DCB_PARTNER_MANAGER,
UserRole.DCB_PARTNER_SUPPORT
].includes(role);
}
private mapToMerchantConfigRole(keycloakRole: UserRole): UserRole {
const mappedRole = this.ROLE_MAPPING.get(keycloakRole);
return mappedRole || UserRole.MERCHANT_CONFIG_VIEWER;
}
}

View File

@ -3,7 +3,7 @@ import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { NgIcon } from '@ng-icons/core';
import { NgbNavModule, NgbModal, NgbModalModule } from '@ng-bootstrap/ng-bootstrap';
import { catchError, map, of, Subject, switchMap, takeUntil } from 'rxjs';
import { catchError, map, of, Subject, switchMap, takeUntil, throwError } from 'rxjs';
import { MerchantUsersService } from './merchant-users.service';
import { RoleManagementService } from '@core/services/hub-users-roles-management.service';
@ -346,23 +346,49 @@ export class MerchantUsersManagement implements OnInit, OnDestroy {
}
private loadAllMerchants(): void {
if (this.isMerchantUser) {
console.log('⚠️ User is not a Hub user, merchant list not displayed');
return;
}
this.loadingMerchantPartners = true;
this.merchantPartnersError = '';
this.merchantConfigService.fetchAllMerchants()
this.merchantPartners = [];
const pageSize = 10; // batch size identique au backend
let currentPage = 1;
const loadNextPage = () => {
this.merchantConfigService.getAllMerchants(currentPage, pageSize)
.pipe(takeUntil(this.destroy$))
.subscribe({
next: (merchants) => {
this.merchantPartners = merchants;
this.loadingMerchantPartners = false;
console.log('✅ All merchants loaded for Hub Admin:', merchants.length);
next: response => {
const items = Array.isArray(response.items) ? response.items : [];
this.merchantPartners.push(...items);
const loadedCount = this.merchantPartners.length;
const totalItems = response.total;
console.log(`📥 Page ${currentPage} chargée (${loadedCount}/${totalItems} merchants)`);
if (loadedCount < totalItems) {
currentPage++;
loadNextPage();
} else {
this.loadingMerchantPartners = false;
console.log(`✅ Tous les merchants chargés: ${loadedCount}`);
}
},
error: (error) => {
console.error('❌ Error loading all merchants:', error);
error: err => {
console.error('❌ Error loading merchants:', err);
this.merchantPartnersError = 'Erreur lors du chargement des merchants';
this.loadingMerchantPartners = false;
}
});
};
loadNextPage();
}
getCurrentUserMerchantName(): string {
@ -593,7 +619,6 @@ export class MerchantUsersManagement implements OnInit, OnDestroy {
merchantPartnerId: merchantPartnerId
};
console.log('📤 Adding user to merchant config:', addUserDto);
return this.merchantConfigService.addUserToMerchant(addUserDto).pipe(
map((merchantConfigUser) => {
return {
@ -608,15 +633,16 @@ export class MerchantUsersManagement implements OnInit, OnDestroy {
// ROLLBACK: Supprimer l'utilisateur Keycloak créé
if (createdUserId) {
console.log(`🔄 Rollback: Deleting Keycloak user ${createdUserId} because merchant association failed`);
return this.merchantUsersService.deleteMerchantUser(createdUserId).pipe(
map(() => {
switchMap(() => {
console.log(`✅ Keycloak user ${createdUserId} deleted as part of rollback`);
throw new Error(`Failed to associate user with merchant: ${merchantError.message}. User creation rolled back.`);
// On propage l'erreur originale en créant un Observable qui échoue
return throwError(() => new Error(`Failed to associate user with merchant: ${merchantError.message}. User creation rolled back.`));
}),
catchError((deleteError) => {
console.error(`❌ Failed to delete Keycloak user during rollback:`, deleteError);
// Même si le rollback échoue, on propage l'erreur originale avec info supplémentaire
throw new Error(`Failed to associate user with merchant: ${merchantError.message}. AND failed to rollback user creation: ${deleteError.message}`);
return throwError(() => new Error(`Failed to associate user with merchant: ${merchantError.message}. AND failed to rollback user creation: ${deleteError.message}`));
})
);
}

View File

@ -334,7 +334,7 @@
</table>
<!-- Pagination -->
@if (totalPages > 1) {
@if (totalPages >= 1) {
<div class="d-flex justify-content-between align-items-center mt-3">
<div class="text-muted">
Affichage de {{ getStartIndex() }}

View File

@ -154,7 +154,7 @@ export class MerchantConfigsList implements OnInit, OnDestroy {
];
}
loadMerchants() {
loadMerchants(): void {
if (!this.isHubUser) {
console.log('⚠️ User is not a Hub user, merchant list not displayed');
return;
@ -163,15 +163,14 @@ export class MerchantConfigsList implements OnInit, OnDestroy {
this.loading = true;
this.error = '';
this.merchantConfigService.getMerchants(
this.currentPage,
this.itemsPerPage,
this.buildSearchParams()
)
const params = this.buildSearchParams();
const skip = (this.currentPage - 1) * this.itemsPerPage;
this.merchantConfigService.getAllMerchants(this.currentPage, this.itemsPerPage, params)
.pipe(
takeUntil(this.destroy$),
catchError(error => {
console.error('Error loading merchants:', error);
console.error('Error loading merchants:', error);
this.error = 'Erreur lors du chargement des marchands';
return of({
items: [],
@ -182,37 +181,26 @@ export class MerchantConfigsList implements OnInit, OnDestroy {
} as PaginatedResponse<Merchant>);
})
)
.subscribe({
next: (response) => {
console.log('📊 Pagination response:', {
page: response.page,
total: response.total,
totalPages: response.totalPages,
itemsCount: response.items?.length,
limit: response.limit
});
this.allMerchants = response.items || [];
this.displayedMerchants = response.items || [];
this.totalItems = response.total || 0;
this.totalPages = response.totalPages || 0;
this.loading = false;
this.cdRef.detectChanges();
},
error: () => {
this.error = 'Erreur lors du chargement des marchands';
this.loading = false;
this.allMerchants = [];
this.displayedMerchants = [];
this.totalItems = 0;
this.totalPages = 0;
this.cdRef.detectChanges();
}
.subscribe(response => {
this.allMerchants = response.items || [];
this.displayedMerchants = response.items || [];
this.totalItems = response.total || 0;
this.totalPages = response.totalPages || Math.ceil((response.total || 0) / this.itemsPerPage);
this.loading = false;
this.cdRef.detectChanges();
console.log('📊 Pagination response:', {
page: response.page,
total: response.total,
totalPages: this.totalPages,
itemsCount: response.items?.length,
limit: response.limit
});
});
}
// ==================== AFFICHAGE DU LOGO ====================
// ==================== AFFICHAGE DU LOGO ====================
/**

View File

@ -61,200 +61,44 @@ export class MerchantConfigService {
);
}
getMerchants(page: number = 1, limit: number = 10, params?: SearchMerchantsParams): Observable<PaginatedResponse<Merchant>> {
// Vérifier si le cache est valide
const paramsChanged = !this.areParamsEqual(params, this.cacheParams);
if (paramsChanged || this.merchantsCache.length === 0) {
// Nouvelle requête nécessaire
return this.fetchAllMerchants(params).pipe(
map(allMerchants => {
// Mettre en cache
this.merchantsCache = allMerchants;
this.cacheParams = params;
// Appliquer pagination
return this.applyPagination(allMerchants, page, limit);
})
);
} else {
// Vérifier si le cache contient assez d'éléments pour la page demandée
const neededItems = page * limit;
if (neededItems <= this.merchantsCache.length) {
// Cache suffisant
return of(this.applyPagination(this.merchantsCache, page, limit));
} else {
// Besoin de plus d'éléments
const additionalNeeded = neededItems - this.merchantsCache.length;
const newTake = this.calculateOptimalTake(additionalNeeded, page, limit);
console.log(`🔄 Cache insufficient (${this.merchantsCache.length}), fetching ${newTake} more items`);
return this.fetchAdditionalMerchants(newTake, params).pipe(
map(additionalMerchants => {
// Fusionner avec le cache (éviter les doublons)
const merged = this.mergeMerchants(this.merchantsCache, additionalMerchants);
this.merchantsCache = merged;
return this.applyPagination(merged, page, limit);
})
);
}
}
}
/**
* Récupère tous les merchants (optionnel: avec recherche)
*/
getAllMerchants(
page: number = 1,
limit: number = 10,
params?: SearchMerchantsParams
): Observable<PaginatedResponse<Merchant>> {
const skip = (page - 1) * limit;
// Calculer le take optimal en fonction du total
private calculateOptimalTake(totalCount: number, page: number, limit: number): number {
// Calculer combien d'éléments nous avons besoin pour la pagination
const neededItems = page * limit;
// Ajouter un buffer pour éviter les appels fréquents
const buffer = Math.max(limit * 2, 100);
// Calculer le take nécessaire
let optimalTake = neededItems + buffer;
// Si le total est connu, adapter le take
if (totalCount > 0) {
// Prendre soit ce dont on a besoin, soit le total (le plus petit)
optimalTake = Math.min(optimalTake, totalCount);
}
// Arrondir aux paliers optimaux
return this.roundToOptimalValue(optimalTake);
}
// Arrondir à des valeurs optimales (100, 500, 1000, etc.)
private roundToOptimalValue(value: number): number {
if (value <= 100) return 100;
if (value <= 500) return 500;
if (value <= 1000) return 1000;
if (value <= 2000) return 2000;
if (value <= 5000) return 5000;
if (value <= 10000) return 10000;
// Pour les très grands nombres, arrondir au multiple de 10000 supérieur
return Math.ceil(value / 10000) * 10000;
}
fetchAllMerchants(params?: SearchMerchantsParams): Observable<Merchant[]> {
// Commencer avec un take raisonnable
const initialTake = 500;
return this.fetchMerchantsWithParams(initialTake, params).pipe(
switchMap(initialBatch => {
// Si nous avons récupéré moins que demandé, c'est probablement tout
if (initialBatch.length < initialTake) {
console.log(`✅ Retrieved all ${initialBatch.length} merchants`);
return of(initialBatch);
}
// Sinon, peut-être qu'il y a plus, essayer un take plus grand
console.log(`⚠️ Initial batch size (${initialBatch.length}) equals take, might be more`);
const largerTake = 2000;
return this.fetchMerchantsWithParams(largerTake, params).pipe(
map(largerBatch => {
console.log(`✅ Retrieved ${largerBatch.length} merchants with larger take`);
return largerBatch;
})
);
})
);
}
private fetchMerchantsWithParams(take: number, params?: SearchMerchantsParams): Observable<Merchant[]> {
let httpParams = new HttpParams().set('take', take.toString());
if (params?.query) {
httpParams = httpParams.set('query', params.query.trim());
}
console.log(`📥 Fetching ${take} merchants`);
return this.http.get<ApiMerchant[]>(this.baseApiUrl, {
params: httpParams
}).pipe(
timeout(this.REQUEST_TIMEOUT),
map(apiMerchants =>
apiMerchants.map(merchant =>
this.dataAdapter.convertApiMerchantToFrontend(merchant)
)
)
);
}
private fetchAdditionalMerchants(take: number, params?: SearchMerchantsParams): Observable<Merchant[]> {
// Prendre à partir de la fin du cache
const skip = this.merchantsCache.length;
let httpParams = new HttpParams()
.set('take', take.toString())
.set('skip', skip.toString());
.set('skip', skip.toString())
.set('take', limit.toString());
if (params?.query) {
httpParams = httpParams.set('query', params.query.trim());
}
console.log(`📥 Fetching additional ${take} merchants (skip: ${skip})`);
return this.http.get<ApiMerchant[]>(this.baseApiUrl, {
params: httpParams
}).pipe(
timeout(this.REQUEST_TIMEOUT),
map(apiMerchants =>
apiMerchants.map(merchant =>
this.dataAdapter.convertApiMerchantToFrontend(merchant)
)
)
);
}
private mergeMerchants(existing: Merchant[], newOnes: Merchant[]): Merchant[] {
const existingIds = new Set(existing.map(m => m.id));
const uniqueNewOnes = newOnes.filter(m => !existingIds.has(m.id));
return [...existing, ...uniqueNewOnes];
}
return this.http.get<{ items: ApiMerchant[], total: number }>(this.baseApiUrl, { params: httpParams })
.pipe(
timeout(this.REQUEST_TIMEOUT),
map(response => {
// Sécuriser le mapping, même si items est undefined
const itemsArray: ApiMerchant[] = Array.isArray(response?.items) ? response.items : [];
const merchants: Merchant[] = itemsArray.map(m => this.dataAdapter.convertApiMerchantToFrontend(m));
private applyPagination(merchants: Merchant[], page: number, limit: number): PaginatedResponse<Merchant> {
const total = merchants.length;
const totalPages = Math.ceil(total / limit);
const startIndex = (page - 1) * limit;
const endIndex = Math.min(startIndex + limit, total);
const paginatedItems = merchants.slice(startIndex, endIndex);
return {
items: paginatedItems,
total: total,
page: page,
limit: limit,
totalPages: totalPages
};
}
const total: number = typeof response?.total === 'number' ? response.total : merchants.length;
private areParamsEqual(a?: SearchMerchantsParams, b?: SearchMerchantsParams): boolean {
if (!a && !b) return true;
if (!a || !b) return false;
return a.query === b.query;
}
getAllMerchants(params?: SearchMerchantsParams): Observable<Merchant[]> {
let httpParams = new HttpParams();
if (params?.query) {
httpParams = httpParams.set('query', params.query.trim());
}
return this.http.get<ApiMerchant[]>(this.baseApiUrl, { params: httpParams }).pipe(
timeout(this.REQUEST_TIMEOUT),
map(apiMerchants =>
apiMerchants.map(merchant =>
this.dataAdapter.convertApiMerchantToFrontend(merchant)
)
),
catchError(error => this.handleError('getAllMerchants', error))
);
return {
items: merchants,
total,
page,
limit,
totalPages: Math.ceil(total / limit)
};
}),
catchError(error => this.handleError('getAllMerchants', error))
);
}
getMerchantById(userId: number): Observable<Merchant> {

View File

@ -9,7 +9,6 @@ import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
import { MerchantConfigService } from './merchant-config.service';
import { RoleManagementService } from '@core/services/hub-users-roles-management.service';
import { AuthService } from '@core/services/auth.service';
import { MerchantSyncService } from '../hub-users-management/merchant-sync-orchestrator.service';
import { PageTitle } from '@app/components/page-title/page-title';
import { MerchantConfigsList } from './merchant-config-list/merchant-config-list';
import { MerchantConfigView } from './merchant-config-view/merchant-config-view';
@ -26,7 +25,7 @@ import {
MerchantConfig,
TechnicalContact
} from '@core/models/merchant-config.model';
import { UserRole, UserType, PaginatedUserResponse, User } from '@core/models/dcb-bo-hub-user.model';
import { UserRole, UserType, User } from '@core/models/dcb-bo-hub-user.model';
import { MerchantDataAdapter } from './merchant-data-adapter.service';

View File

@ -16,7 +16,6 @@ import { WebhooksStatus } from '@modules/webhooks/status/status';
import { WebhooksRetry } from '@modules/webhooks/retry/retry';
import { Settings } from '@modules/settings/settings';
import { Integrations } from '@modules/integrations/integrations';
import { Support } from '@modules/support/support';
import { MyProfile } from '@modules/profile/profile';
import { Documentation } from '@modules/documentation/documentation';
import { Help } from '@modules/help/help';
@ -233,17 +232,8 @@ const routes: Routes = [
},
// ---------------------------
// Support & Profile (Tous les utilisateurs authentifiés)
// Profile (Tous les utilisateurs authentifiés)
// ---------------------------
{
path: 'support',
component: Support,
canActivate: [authGuard, roleGuard],
data: {
title: 'Support',
module: 'support'
}
},
{
path: 'profile',
component: MyProfile,

View File

@ -1,628 +0,0 @@
import { Component, OnInit } from '@angular/core';
import { MerchantSyncService } from '../hub-users-management/merchant-sync-orchestrator.service';
import { UserRole } from '@core/models/dcb-bo-hub-user.model';
import { firstValueFrom } from 'rxjs';
import { MerchantUsersService } from '@modules/hub-users-management/merchant-users.service';
@Component({
selector: 'app-support',
templateUrl: './support.html'
})
export class Support implements OnInit {
private testData = {
merchantConfigId: '',
keycloakUserId: '',
testUserId: '',
testMerchantConfigUserId: '',
associatedUserId: '' // ID d'un utilisateur associé au marchand
};
constructor(
private merchantCrudService: MerchantSyncService,
private merchantUsersService: MerchantUsersService
) {}
ngOnInit() {
console.log('🚀 Support Component - Tests CRUD Merchants');
console.log('='.repeat(60));
console.log('📌 NOUVELLE LOGIQUE: Merchant et User indépendants, association séparée');
console.log('='.repeat(60));
// Démarrer les tests automatiquement
this.runAllTests();
}
/**
* Exécuter tous les tests
*/
async runAllTests(): Promise<void> {
try {
console.log('\n🧪 DÉMARRAGE DES TESTS COMPLETS');
console.log('📌 NOUVELLE LOGIQUE: Association séparée');
console.log('='.repeat(50));
// Test 1: Création indépendante
await this.testCreateOperations();
// Test 2: Association
await this.testAssociationOperations();
// Test 3: Lecture
await this.testReadOperations();
// Test 4: Mise à jour
await this.testUpdateOperations();
// Test 5: Gestion utilisateurs
await this.testUserOperations();
// Test 6: Suppression
await this.testDeleteOperations();
console.log('\n✅ TOUS LES TESTS TERMINÉS AVEC SUCCÈS!');
console.log('='.repeat(50));
} catch (error) {
console.error('❌ ERREUR CRITIQUE DANS LES TESTS:', error);
}
}
/**
* TEST 1: Opérations de création indépendante
*/
private async testCreateOperations(): Promise<void> {
console.log('\n📝 TEST 1: Opérations CREATE INDÉPENDANTES');
console.log('-'.repeat(40));
try {
// 1.1 Créer un merchant uniquement dans MerchantConfig
console.log('1.1 Création d\'un merchant dans MerchantConfig seulement...');
const merchantData = {
name: 'Test Merchant ' + Date.now(),
adresse: '123 Test Street',
phone: '+336' + Math.floor(10000000 + Math.random() * 90000000),
configs: [
{
name: 'API_KEY',
value: 'test-api-key-' + Date.now(),
operatorId: 1
}
],
technicalContacts: [
{
firstName: 'Test',
lastName: 'User',
phone: '+33612345678',
email: `test.${Date.now()}@example.com`
}
]
};
console.log('📤 Données merchant pour MerchantConfig:', merchantData);
const merchantConfigResult = await firstValueFrom(
this.merchantCrudService.createMerchantInConfigOnly(merchantData)
);
this.testData.merchantConfigId = String(merchantConfigResult.id!);
console.log('✅ Merchant créé dans MerchantConfig uniquement!');
console.log(' Merchant Config ID:', this.testData.merchantConfigId);
console.log(' Merchant name:', merchantConfigResult.name);
// 1.2 Créer un utilisateur Hub dans Keycloak
console.log('\n1.2 Création d\'un utilisateur Hub dans Keycloak...');
const hubUserData = {
username: `testhubuser.${Date.now()}`,
email: `hubuser.${Date.now()}@example.com`,
password: 'HubPassword123!',
firstName: 'Test',
lastName: 'HubUser',
role: UserRole.DCB_SUPPORT
};
console.log('👤 Données Hub User pour Keycloak:', { ...hubUserData, password: '***' });
const hubUserResult = await firstValueFrom(
this.merchantCrudService.createKeycloakUser(hubUserData)
);
console.log('✅ Utilisateur Hub créé dans Keycloak:');
console.log(' Keycloak User ID:', hubUserResult.id);
console.log(' Email:', hubUserResult.email);
console.log(' Rôle:', hubUserResult.role);
console.log(' User Type:', hubUserResult.userType);
// 1.3 Créer un utilisateur Partenaire dans Keycloak
console.log('\n1.3 Création d\'un utilisateur Partenaire dans Keycloak...');
const partnerUserData = {
username: `testpartner.${Date.now()}`,
email: `partner.${Date.now()}@example.com`,
password: 'PartnerPassword123!',
firstName: 'Test',
lastName: 'Partner',
role: UserRole.DCB_PARTNER_ADMIN
};
console.log('👤 Données Partner User pour Keycloak:', { ...partnerUserData, password: '***' });
const partnerUserResult = await firstValueFrom(
this.merchantCrudService.createKeycloakUser(partnerUserData)
);
this.testData.testUserId = partnerUserResult.id;
console.log('✅ Utilisateur Partenaire créé dans Keycloak:');
console.log(' Keycloak User ID:', this.testData.testUserId);
console.log(' Email:', partnerUserResult.email);
console.log(' Rôle:', partnerUserResult.role);
console.log(' User Type:', partnerUserResult.userType);
} catch (error) {
console.error('❌ ERREUR lors de la création:', error);
throw error;
}
}
/**
* TEST 2: Opérations d'association
*/
private async testAssociationOperations(): Promise<void> {
console.log('\n🔗 TEST 2: Opérations d\'ASSOCIATION');
console.log('-'.repeat(40));
if (!this.testData.merchantConfigId || !this.testData.testUserId) {
console.log('⚠️ Merchant ou utilisateur non créé, passage au test suivant');
return;
}
try {
// 2.1 Associer l'utilisateur au marchand
console.log('2.1 Association de l\'utilisateur au marchand...');
const associationData = {
userId: this.testData.testUserId,
merchantConfigId: this.testData.merchantConfigId,
role: UserRole.MERCHANT_CONFIG_ADMIN
};
console.log('🔗 Données d\'association:', associationData);
const associationResult = await firstValueFrom(
this.merchantCrudService.associateUserToMerchant(associationData)
);
this.testData.associatedUserId = associationResult.keycloakUser.id;
this.testData.testMerchantConfigUserId = String(associationResult.merchantConfigUser?.userId || '');
console.log('✅ Utilisateur associé au marchand:');
console.log(' Keycloak User ID:', associationResult.keycloakUser.id);
console.log(' Merchant Config User ID:', this.testData.testMerchantConfigUserId);
console.log(' Rôle dans MerchantConfig:', associationResult.merchantConfigUser?.role);
console.log(' Associé au merchant:', this.testData.merchantConfigId);
// 2.2 Récupérer les utilisateurs associés au marchand
console.log('\n2.2 Lecture des utilisateurs associés au marchand...');
const merchantUsers = await firstValueFrom(
this.merchantCrudService.getUsersByMerchant(this.testData.merchantConfigId)
);
console.log(`${merchantUsers.length} utilisateurs associés à ce merchant`);
merchantUsers.forEach((user: any, index: number) => {
console.log(` ${index + 1}. ${user.email || 'Inconnu'}`);
console.log(` ID: ${user.id}`);
console.log(` Rôle: ${user.role}`);
});
} catch (error) {
console.error('❌ ERREUR lors de l\'association:', error);
}
}
/**
* TEST 3: Opérations de lecture
*/
private async testReadOperations(): Promise<void> {
console.log('\n🔍 TEST 3: Opérations READ');
console.log('-'.repeat(40));
if (!this.testData.merchantConfigId) {
console.log('⚠️ Aucun merchant créé, passage au test suivant');
return;
}
try {
// 3.1 Lire le merchant depuis MerchantConfig
console.log('3.1 Lecture du merchant depuis MerchantConfig...');
const merchant = await firstValueFrom(
this.merchantCrudService.getMerchantFromConfigOnly(
this.testData.merchantConfigId
)
);
console.log('✅ Merchant récupéré depuis MerchantConfig:');
console.log(' ID:', merchant.id);
console.log(' Nom:', merchant.name);
console.log(' Adresse:', merchant.adresse);
console.log(' Configurations:', merchant.configs?.length || 0);
// 3.2 Lire tous les merchants depuis MerchantConfig
console.log('\n3.2 Lecture de tous les merchants depuis MerchantConfig...');
const allMerchants = await firstValueFrom(
this.merchantCrudService.getAllMerchantsFromConfig()
);
console.log(`${allMerchants.length} merchants trouvés au total`);
if (allMerchants.length > 0) {
const lastMerchant = allMerchants[allMerchants.length - 1];
console.log(' Dernier merchant:', lastMerchant.name, '(ID:', lastMerchant.id, ')');
}
// 3.3 Rechercher des merchants
console.log('\n3.3 Recherche de merchants dans MerchantConfig...');
const searchResults = await firstValueFrom(
this.merchantCrudService.searchMerchantsInConfig('Test')
);
console.log(`${searchResults.length} merchants trouvés avec "Test"`);
if (searchResults.length > 0) {
console.log(' Premier résultat:', searchResults[0].name);
}
// 3.4 Rechercher des utilisateurs
console.log('\n3.4 Recherche d\'utilisateurs dans Keycloak...');
const userSearchResults = await firstValueFrom(
this.merchantCrudService.searchKeycloakUsers('test')
);
console.log(`${userSearchResults.length} utilisateurs trouvés avec "test"`);
if (userSearchResults.length > 0) {
console.log(' Premier résultat:', userSearchResults[0].email);
}
} catch (error) {
console.error('❌ ERREUR lors de la lecture:', error);
}
}
/**
* TEST 4: Opérations de mise à jour
*/
private async testUpdateOperations(): Promise<void> {
console.log('\n✏ TEST 4: Opérations UPDATE');
console.log('-'.repeat(40));
if (!this.testData.merchantConfigId) {
console.log('⚠️ Aucun merchant créé, passage au test suivant');
return;
}
try {
// 4.1 Mettre à jour le merchant dans MerchantConfig
console.log('4.1 Mise à jour du merchant dans MerchantConfig...');
const newName = `Updated Merchant ${Date.now()}`;
const updateResult = await firstValueFrom(
this.merchantCrudService.updateMerchantInConfigOnly(
this.testData.merchantConfigId,
{
name: newName,
description: 'Mis à jour par les tests',
adresse: '456 Updated Street'
}
)
);
console.log('✅ Merchant mis à jour dans MerchantConfig:');
console.log(' Nouveau nom:', newName);
console.log(' Description:', updateResult.description);
// 4.3 Changer le rôle de l'utilisateur dans MerchantConfig
console.log('\n4.3 Changement de rôle utilisateur dans MerchantConfig...');
if (this.testData.testUserId && this.testData.merchantConfigId && this.testData.testMerchantConfigUserId) {
const roleUpdateResult = await firstValueFrom(
this.merchantCrudService.updateUserRoleInMerchantConfig(
this.testData.merchantConfigId,
this.testData.testUserId,
UserRole.MERCHANT_CONFIG_MANAGER
)
);
console.log('✅ Rôle utilisateur mis à jour dans MerchantConfig:');
console.log(' Nouveau rôle:', UserRole.MERCHANT_CONFIG_MANAGER);
}
} catch (error) {
console.error('❌ ERREUR lors de la mise à jour:', error);
}
}
/**
* TEST 5: Opérations utilisateurs avancées
*/
private async testUserOperations(): Promise<void> {
console.log('\n👥 TEST 5: Opérations utilisateurs avancées');
console.log('-'.repeat(40));
if (!this.testData.testUserId) {
console.log('⚠️ Aucun utilisateur créé, passage au test suivant');
return;
}
try {
// 5.1 Réinitialiser le mot de passe
console.log('5.1 Réinitialisation mot de passe...');
const resetPasswordDto = {
newPassword: 'NewPassword123!',
temporary: false
};
const resetResult = await firstValueFrom(
this.merchantUsersService.resetMerchantUserPassword(
this.testData.testUserId,
resetPasswordDto
)
);
console.log('✅ Mot de passe réinitialisé:');
console.log(' Message:', resetResult.message);
// 5.2 Dissocier l'utilisateur du marchand
console.log('\n5.2 Dissociation de l\'utilisateur du marchand...');
if (this.testData.testUserId && this.testData.merchantConfigId) {
const dissociateResult = await firstValueFrom(
this.merchantCrudService.dissociateUserFromMerchant(
this.testData.testUserId,
this.testData.merchantConfigId
)
);
console.log('✅ Utilisateur dissocié du marchand:');
console.log(' Succès:', dissociateResult.success);
console.log(' Message:', dissociateResult.message);
// Vérifier que l'utilisateur n'est plus associé
const userMerchants = await firstValueFrom(
this.merchantCrudService.getUsersByMerchant(this.testData.merchantConfigId)
);
const userStillLinked = userMerchants.some(
user => user.id === this.testData.testUserId
);
console.log(` Marchands associés après dissociation: ${userMerchants.length}`);
if (userStillLinked) {
console.error('❌ Lutilisateur est encore associé au marchand ! ❌');
} else {
console.log('✅ Lutilisateur a été correctement dissocié du marchand.');
}
}
// 5.3 Réassocier l'utilisateur (pour les tests suivants)
console.log('\n5.3 Réassociation de l\'utilisateur (pour les tests)...');
if (this.testData.testUserId && this.testData.merchantConfigId) {
const reassociationData = {
userId: this.testData.testUserId,
merchantConfigId: this.testData.merchantConfigId,
role: UserRole.MERCHANT_CONFIG_ADMIN
};
await firstValueFrom(
this.merchantCrudService.associateUserToMerchant(reassociationData)
);
console.log('✅ Utilisateur réassocié pour les tests suivants');
}
} catch (error) {
console.error('❌ ERREUR opérations utilisateurs:', error);
}
}
/**
* TEST 6: Opérations de suppression
*/
private async testDeleteOperations(): Promise<void> {
console.log('\n🗑 TEST 6: Opérations DELETE');
console.log('-'.repeat(40));
if (!this.testData.merchantConfigId) {
console.log('⚠️ Aucun merchant créé, passage au test suivant');
return;
}
try {
// 6.1 Dissocier avant suppression
console.log('6.1 Dissociation de l\'utilisateur avant suppression...');
if (this.testData.testUserId && this.testData.merchantConfigId) {
await firstValueFrom(
this.merchantCrudService.dissociateUserFromMerchant(
this.testData.testUserId,
this.testData.merchantConfigId
)
);
console.log('✅ Utilisateur dissocié');
}
// 6.2 Supprimer l'utilisateur de Keycloak
console.log('\n6.2 Suppression de l\'utilisateur de Keycloak...');
if (this.testData.testUserId) {
const deleteUserResult = await firstValueFrom(
this.merchantCrudService.deleteKeycloakUser(
this.testData.testUserId
)
);
console.log('✅ Utilisateur supprimé de Keycloak:');
console.log(' Succès:', deleteUserResult.success);
console.log(' Message:', deleteUserResult.message);
this.testData.testUserId = '';
this.testData.testMerchantConfigUserId = '';
}
// 6.3 Supprimer le merchant de MerchantConfig
console.log('\n6.3 Suppression du merchant de MerchantConfig...');
const deleteMerchantResult = await firstValueFrom(
this.merchantCrudService.deleteMerchantFromConfigOnly(
this.testData.merchantConfigId
)
);
console.log('✅ Merchant supprimé de MerchantConfig:');
console.log(' Succès:', deleteMerchantResult.success);
console.log(' Message:', deleteMerchantResult.message);
// 6.4 Vérifier la suppression
console.log('\n6.4 Vérification de la suppression...');
try {
await firstValueFrom(
this.merchantCrudService.getMerchantFromConfigOnly(
this.testData.merchantConfigId
)
);
console.log('❌ Le merchant existe toujours dans MerchantConfig - PROBLÈME!');
} catch (error) {
console.log('✅ Le merchant a bien été supprimé de MerchantConfig');
}
// Réinitialiser toutes les données
this.testData = {
merchantConfigId: '',
keycloakUserId: '',
testUserId: '',
testMerchantConfigUserId: '',
associatedUserId: ''
};
console.log('🧹 Données de test réinitialisées');
} catch (error) {
console.error('❌ ERREUR lors de la suppression:', error);
}
}
// ==================== MÉTHODES POUR TESTS INDIVIDUELS ====================
async testCreateOnly(): Promise<void> {
console.log('🧪 Test CREATE uniquement');
console.log('📌 Création indépendante');
await this.testCreateOperations();
}
async testAssociationOnly(): Promise<void> {
console.log('🧪 Test ASSOCIATION uniquement');
// Créer d'abord un merchant et un utilisateur si nécessaire
if (!this.testData.merchantConfigId) {
await this.createTestMerchant();
}
if (!this.testData.testUserId) {
await this.createTestUser();
}
await this.testAssociationOperations();
}
async testReadOnly(): Promise<void> {
console.log('🧪 Test READ uniquement');
await this.testReadOperations();
}
async testUpdateOnly(): Promise<void> {
console.log('🧪 Test UPDATE uniquement');
await this.testUpdateOperations();
}
async testDeleteOnly(): Promise<void> {
console.log('🧪 Test DELETE uniquement');
await this.testDeleteOperations();
}
// ==================== MÉTHODES UTILITAIRES ====================
private async createTestMerchant(): Promise<void> {
const merchantData = {
name: 'Test Merchant ' + Date.now(),
adresse: '123 Test Street',
phone: '+336' + Math.floor(10000000 + Math.random() * 90000000),
configs: [],
technicalContacts: []
};
const merchant = await firstValueFrom(
this.merchantCrudService.createMerchantInConfigOnly(merchantData)
);
this.testData.merchantConfigId = String(merchant.id!);
console.log('✅ Merchant de test créé:', this.testData.merchantConfigId);
}
private async createTestUser(): Promise<void> {
const userData = {
username: `testuser.${Date.now()}`,
email: `user.${Date.now()}@example.com`,
password: 'TestPassword123!',
firstName: 'Test',
lastName: 'User',
role: UserRole.DCB_PARTNER_ADMIN
};
const user = await firstValueFrom(
this.merchantCrudService.createKeycloakUser(userData)
);
this.testData.testUserId = user.id;
console.log('✅ Utilisateur de test créé:', this.testData.testUserId);
}
/**
* Afficher l'état actuel des tests
*/
showTestStatus(): void {
console.log('\n📊 ÉTAT ACTUEL DES TESTS');
console.log('📌 NOUVELLE LOGIQUE: Association séparée');
console.log('-'.repeat(30));
console.log('Merchant Config ID:', this.testData.merchantConfigId || 'Non créé');
console.log('Keycloak User ID:', this.testData.testUserId || 'Non créé');
console.log('Associated User ID:', this.testData.associatedUserId || 'Non associé');
console.log('Merchant Config User ID:', this.testData.testMerchantConfigUserId || 'Non créé');
}
/**
* Réinitialiser les données de test
*/
resetTestData(): void {
this.testData = {
merchantConfigId: '',
keycloakUserId: '',
testUserId: '',
testMerchantConfigUserId: '',
associatedUserId: ''
};
console.log('🧹 Données de test réinitialisées');
}
}