import { Injectable, inject } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { environment } from '@environments/environment'; import { Observable, map, catchError, throwError, of } from 'rxjs'; import { User, CreateUserDto, UpdateUserDto, ResetPasswordDto, PaginatedUserResponse, AvailableRolesResponse, SearchUsersParams, UserRole, UserType, UserUtils } from '@core/models/dcb-bo-hub-user.model'; // Interfaces pour les nouvelles réponses export interface TokenResponse { access_token: string; expires_in: number; refresh_token: string; refresh_expires_in: number; token_type: string; 'not-before-policy': number; session_state: string; scope: string; } export interface UserProfileResponse { id: string; username: string; email: string; firstName: string; lastName: string; emailVerified: boolean; enabled: boolean; role: string[]; merchantPartnerId?: string; createdBy?: string; createdByUsername?: string; } export interface MessageResponse { message: string; } export interface MerchantPartnerIdResponse { merchantPartnerId: string | null; } // ===== SERVICE UTILISATEURS MERCHANT ===== @Injectable({ providedIn: 'root' }) export class MerchantUsersService { private http = inject(HttpClient); private baseApiUrl = `${environment.iamApiUrl}/merchant-users`; getUserMerchantPartnerId(userId: string): Observable { return this.http.get(`${this.baseApiUrl}/merchant-partner/${userId}`).pipe( map(response => response.merchantPartnerId), catchError(error => { console.error(`Error loading merchant partner ID for user ${userId}:`, error); return throwError(() => error); }) ); } // === MÉTHODES SPÉCIFIQUES MERCHANT === createMerchantUser(createUserDto: CreateUserDto): Observable { // Utiliser la validation centralisée const errors = UserUtils.validateUserCreation(createUserDto); if (errors.length > 0) { return throwError(() => errors.join(', ')); } if (!createUserDto.username?.trim()) { return throwError(() => 'Username is required and cannot be empty'); } if (!createUserDto.email?.trim()) { return throwError(() => 'Email is required and cannot be empty'); } if (!createUserDto.password || createUserDto.password.length < 8) { return throwError(() => 'Password must be at least 8 characters'); } const payload = { username: createUserDto.username.trim(), email: createUserDto.email.trim(), firstName: (createUserDto.firstName || '').trim(), lastName: (createUserDto.lastName || '').trim(), password: createUserDto.password, role: createUserDto.role, enabled: createUserDto.enabled !== undefined ? createUserDto.enabled : true, emailVerified: createUserDto.emailVerified !== undefined ? createUserDto.emailVerified : true, merchantPartnerId: createUserDto.merchantPartnerId?.trim() || null, //merchantConfigId: createUserDto.merchantConfigId?.trim() || null, userType: createUserDto.userType.trim() }; return this.http.post(`${this.baseApiUrl}`, payload).pipe( map(user => this.mapToUserModel(user, UserType.MERCHANT_PARTNER)), catchError(error => { console.error('Error creating merchant user:', error); return throwError(() => error); }) ); } getMyMerchantUsers(): Observable { return this.http.get(`${this.baseApiUrl}`).pipe( map(users => users.map(user => this.mapToUserModel(user, UserType.MERCHANT_PARTNER))), catchError(error => { console.error('Error loading my merchant users:', error); return throwError(() => error); }) ); } getMerchantUsersByPartner(partnerId: string): Observable { return this.getMyMerchantUsers().pipe( map(users => users.filter(user => user.merchantPartnerId === partnerId)) ); } getMerchantUserById(id: string | undefined): Observable { return this.http.get(`${this.baseApiUrl}/${id}`).pipe( map(user => this.mapToUserModel(user, UserType.MERCHANT_PARTNER)), catchError(error => { console.error(`Error loading merchant user ${id}:`, error); return throwError(() => error); }) ); } getMerchantUsers(page: number = 1, limit: number = 10, filters?: SearchUsersParams): Observable { return this.getMyMerchantUsers().pipe( map(users => this.filterAndPaginateUsers(users, page, limit, filters)), catchError(error => { console.error('Error loading merchant users:', error); return throwError(() => error); }) ); } updateMerchantUser(id: string, updateUserDto: UpdateUserDto): Observable { const payload: any = { firstName: updateUserDto.firstName, lastName: updateUserDto.lastName, email: updateUserDto.email, enabled: updateUserDto.enabled }; return this.http.put(`${this.baseApiUrl}/${id}`, payload).pipe( map(user => this.mapToUserModel(user, UserType.MERCHANT_PARTNER)), catchError(error => { console.error(`Error updating merchant user ${id}:`, error); return throwError(() => error); }) ); } deleteMerchantUser(id: string): Observable { return this.http.delete(`${this.baseApiUrl}/${id}`).pipe( catchError(error => { console.error(`Error deleting merchant user ${id}:`, error); return throwError(() => error); }) ); } resetMerchantUserPassword(id: string, resetPasswordDto: ResetPasswordDto): Observable { const payload = { newPassword: resetPasswordDto.newPassword, temporary: resetPasswordDto.temporary !== undefined ? resetPasswordDto.temporary : true }; return this.http.post( `${this.baseApiUrl}/${id}/reset-password`, payload ).pipe( catchError(error => { console.error(`Error resetting password for merchant user ${id}:`, error); return throwError(() => error); }) ); } updateMerchantUserRole(id: string, role: UserRole): Observable { const merchantRoles = [UserRole.DCB_PARTNER_ADMIN, UserRole.DCB_PARTNER_SUPPORT, UserRole.DCB_PARTNER_MANAGER]; if (!merchantRoles.includes(role)) { return throwError(() => 'Invalid role for Merchant user'); } return this.http.put(`${this.baseApiUrl}/${id}/role`, { role }).pipe( map(user => this.mapToUserModel(user, UserType.MERCHANT_PARTNER)), catchError(error => { console.error(`Error updating role for merchant user ${id}:`, error); return throwError(() => error); }) ); } enableMerchantUser(id: string): Observable { return this.updateMerchantUser(id, { enabled: true }); } disableMerchantUser(id: string): Observable { return this.updateMerchantUser(id, { enabled: false }); } getAvailableMerchantRoles(): Observable { return of({ roles: [ { value: UserRole.DCB_PARTNER_ADMIN, label: 'Partner Admin', description: 'Full administrative access within the merchant partner', allowedForCreation: true, userType: UserType.MERCHANT_PARTNER }, { value: UserRole.DCB_PARTNER_MANAGER, label: 'Partner Manager', description: 'Manager access with limited administrative capabilities', allowedForCreation: true, userType: UserType.MERCHANT_PARTNER }, { value: UserRole.DCB_PARTNER_SUPPORT, label: 'Partner Support', description: 'Support role with read-only and basic operational access', allowedForCreation: true, userType: UserType.MERCHANT_PARTNER } ] } as AvailableRolesResponse); } searchMerchantUsers(params: SearchUsersParams): Observable { return this.getMerchantUsers(1, 1000, params).pipe( map(response => response.users) ); } merchantUserExists(username: string): Observable<{ exists: boolean }> { return this.searchMerchantUsers({ query: username }).pipe( map(users => ({ exists: users.some(user => user.username === username) })), catchError(error => { console.error('Error checking if merchant user exists:', error); return of({ exists: false }); }) ); } getMerchantUsersByRole(role: UserRole): Observable { return this.searchMerchantUsers({ role }); } getActiveMerchantUsers(): Observable { return this.searchMerchantUsers({ enabled: true }); } getInactiveMerchantUsers(): Observable { return this.searchMerchantUsers({ enabled: false }); } // === MÉTHODES UTILITAIRES === isValidRoleForMerchant(role: UserRole): boolean { const merchantRoles = [UserRole.DCB_PARTNER_ADMIN, UserRole.DCB_PARTNER_MANAGER, UserRole.DCB_PARTNER_SUPPORT]; return merchantRoles.includes(role); } // === MAPPING ET FILTRAGE === private mapToUserModel(apiUser: any, userType: UserType): User { return { id: apiUser.id, username: apiUser.username, email: apiUser.email, firstName: apiUser.firstName, lastName: apiUser.lastName, enabled: apiUser.enabled, emailVerified: apiUser.emailVerified, userType: userType, merchantPartnerId: apiUser.merchantPartnerId, merchantConfigId: apiUser.merchantConfigId, role: apiUser.role, createdBy: apiUser.createdBy, createdByUsername: apiUser.createdByUsername, createdTimestamp: apiUser.createdTimestamp, lastLogin: apiUser.lastLogin }; } private filterAndPaginateUsers( users: User[], page: number, limit: number, filters?: SearchUsersParams ): PaginatedUserResponse { let filteredUsers = users; if (filters) { if (filters.query) { const query = filters.query.toLowerCase(); filteredUsers = filteredUsers.filter(user => user.username.toLowerCase().includes(query) || user.email.toLowerCase().includes(query) || user.firstName?.toLowerCase().includes(query) || user.lastName?.toLowerCase().includes(query) ); } if (filters.role) { filteredUsers = filteredUsers.filter(user => user.role.includes(filters.role!)); } if (filters.enabled !== undefined) { filteredUsers = filteredUsers.filter(user => user.enabled === filters.enabled); } if (filters.userType) { filteredUsers = filteredUsers.filter(user => user.userType === filters.userType); } if (filters.merchantPartnerId) { filteredUsers = filteredUsers.filter(user => user.merchantPartnerId === filters.merchantPartnerId); } } // Pagination côté client const startIndex = (page - 1) * limit; const endIndex = startIndex + limit; const paginatedUsers = filteredUsers.slice(startIndex, endIndex); return { users: paginatedUsers, total: filteredUsers.length, page, limit, totalPages: Math.ceil(filteredUsers.length / limit) }; } }