353 lines
11 KiB
TypeScript
353 lines
11 KiB
TypeScript
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<string | null> {
|
|
return this.http.get<MerchantPartnerIdResponse>(`${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<User> {
|
|
// 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<User>(`${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<User[]> {
|
|
return this.http.get<User[]>(`${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<User[]> {
|
|
return this.getMyMerchantUsers().pipe(
|
|
map(users => users.filter(user => user.merchantPartnerId === partnerId))
|
|
);
|
|
}
|
|
|
|
getMerchantUserById(id: string | undefined): Observable<User> {
|
|
return this.http.get<User>(`${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<PaginatedUserResponse> {
|
|
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<User> {
|
|
const payload: any = {
|
|
firstName: updateUserDto.firstName,
|
|
lastName: updateUserDto.lastName,
|
|
email: updateUserDto.email,
|
|
enabled: updateUserDto.enabled
|
|
};
|
|
|
|
return this.http.put<User>(`${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<MessageResponse> {
|
|
return this.http.delete<MessageResponse>(`${this.baseApiUrl}/${id}`).pipe(
|
|
catchError(error => {
|
|
console.error(`Error deleting merchant user ${id}:`, error);
|
|
return throwError(() => error);
|
|
})
|
|
);
|
|
}
|
|
|
|
resetMerchantUserPassword(id: string, resetPasswordDto: ResetPasswordDto): Observable<MessageResponse> {
|
|
const payload = {
|
|
newPassword: resetPasswordDto.newPassword,
|
|
temporary: resetPasswordDto.temporary !== undefined ? resetPasswordDto.temporary : true
|
|
};
|
|
|
|
return this.http.post<MessageResponse>(
|
|
`${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<User> {
|
|
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<User>(`${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<User> {
|
|
return this.updateMerchantUser(id, { enabled: true });
|
|
}
|
|
|
|
disableMerchantUser(id: string): Observable<User> {
|
|
return this.updateMerchantUser(id, { enabled: false });
|
|
}
|
|
|
|
getAvailableMerchantRoles(): Observable<AvailableRolesResponse> {
|
|
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<User[]> {
|
|
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<User[]> {
|
|
return this.searchMerchantUsers({ role });
|
|
}
|
|
|
|
getActiveMerchantUsers(): Observable<User[]> {
|
|
return this.searchMerchantUsers({ enabled: true });
|
|
}
|
|
|
|
getInactiveMerchantUsers(): Observable<User[]> {
|
|
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)
|
|
};
|
|
}
|
|
}
|