feat: add DCB User Service API - Authentication system with KEYCLOAK - Modular architecture with services for each feature

This commit is contained in:
diallolatoile 2025-12-15 13:09:56 +00:00
parent 296fe413a9
commit 93c5aa33c7
4 changed files with 390 additions and 146 deletions

View File

@ -3,9 +3,6 @@ import { VerticalLayout } from '@layouts/vertical-layout/vertical-layout';
import { authGuard } from './core/guards/auth.guard';
export const routes: Routes = [
// Redirection racine
{ path: '', redirectTo: '/dcb-dashboard', pathMatch: 'full' },
// ===== ROUTES D'ERREUR (publiques) =====
{
path: 'error',

View File

@ -66,7 +66,6 @@ export class AuthService {
private userProfile$ = new BehaviorSubject<User | null>(null);
private initialized$ = new BehaviorSubject<boolean>(false);
private readonly dashboardAccessService = inject(DashboardAccessService);
private readonly transactionAccessService = inject(TransactionAccessService);
// === INITIALISATION DE L'APPLICATION ===
@ -198,14 +197,14 @@ export class AuthService {
).pipe(
tap(() => {
this.clearAuthData();
this.dashboardAccessService.clearCache();
this.transactionAccessService.clearCache();
this.clearAllStorage(); // Nettoyer tout le storage
}),
catchError(error => {
// Même en cas d'erreur, nettoyer tout
this.clearAuthData();
this.dashboardAccessService.clearCache();
this.transactionAccessService.clearCache();
this.clearAllStorage();
console.warn('Logout API error, but local data cleared:', error);
@ -215,7 +214,7 @@ export class AuthService {
finalize(() => {
// Garantir le nettoyage dans tous les cas
this.clearAuthData();
this.dashboardAccessService.clearCache();
this.transactionAccessService.clearCache();
})
);
@ -226,7 +225,7 @@ export class AuthService {
*/
forceLogout(): void {
this.clearAuthData();
this.dashboardAccessService.clearCache();
this.transactionAccessService.clearCache();
this.clearAllStorage();
}
@ -236,7 +235,7 @@ export class AuthService {
*/
private clearAuthData(): void {
this.dashboardAccessService.clearCache();
this.transactionAccessService.clearCache();
// Supprimer tous les tokens et données utilisateur
@ -375,6 +374,10 @@ export class AuthService {
return this.userProfile$.asObservable();
}
getCurrentUserProfile(): User | null {
return this.userProfile$.value;
}
// === GESTION DES RÔLES ET TYPES ===
getCurrentUserRoles(): UserRole[] {
@ -545,10 +548,42 @@ export class AuthService {
/**
* Récupère le merchantPartnerId de l'utilisateur courant (si marchand)
* Retourne string pour compatibilité, mais devrait être converti en number ailleurs
*/
getCurrentMerchantPartnerId(): string | null {
const profile = this.userProfile$.value;
return profile?.merchantPartnerId || null;
if (!profile) {
return null;
}
const merchantId = profile.merchantPartnerId
if (merchantId === null || merchantId === undefined) {
return null;
}
return merchantId.toString();
}
/**
* Version qui retourne un number (pour usage interne)
*/
getCurrentMerchantPartnerIdAsNumber(): number | null {
const merchantIdStr = this.getCurrentMerchantPartnerId();
if (!merchantIdStr) {
return null;
}
const merchantId = Number(merchantIdStr);
if (isNaN(merchantId) || merchantId <= 0 || !Number.isInteger(merchantId)) {
console.error(`Merchant ID invalide: ${merchantIdStr}`);
return null;
}
return merchantId;
}
/**

View File

@ -310,9 +310,18 @@ export class DcbReportingDashboard implements OnInit, OnDestroy, AfterViewInit {
}
ngOnInit(): void {
// 1. Initialiser l'accès
this.initializeAccess();
this.loadAllowedMerchants();
// 2. Initialiser le dashboard (avec délai pour laisser le temps à l'accès)
setTimeout(() => {
this.initializeDashboard();
}, 100);
// 3. Charger les merchants (avec délai)
setTimeout(() => {
this.loadAllowedMerchants();
}, 150);
if (this.accessService.shouldShowSystemHealth()) {
setInterval(() => {
@ -321,49 +330,74 @@ export class DcbReportingDashboard implements OnInit, OnDestroy, AfterViewInit {
}
}
// ============ INITIALISATION ============
private initializeAccess(): void {
// Attendre que l'accès soit prêt
this.subscriptions.push(
this.accessService.waitForAccess().subscribe(() => {
this.access = this.accessService.getDashboardAccess();
this.currentRoleLabel = this.access.roleLabel;
this.currentRoleIcon = this.access.roleIcon;
// Récupérer le merchant ID du service d'accès
const merchantPartnerId = this.getCurrentMerchantPartnerId();
console.log('✅ Dashboard initialisé avec:', {
access: this.access,
merchantId: this.access.merchantId,
isHubUser: this.access.isHubUser,
});
// Pour les merchant users
if (this.access.isMerchantUser) {
// Pour les merchant users, vérifier que l'ID est valide
if (merchantPartnerId) {
this.merchantId = Number(merchantPartnerId);
this.accessService.setSelectedMerchantId(this.merchantId);
this.isViewingGlobalData = false;
const merchantId = this.access.merchantId;
console.log(`Merchant User: ID = ${this.merchantId}`);
if (merchantId && merchantId > 0) {
this.merchantId = merchantId;
this.accessService.setSelectedMerchantId(merchantId);
this.isViewingGlobalData = false;
this.dataSelection.merchantPartnerId = merchantId;
console.log(`✅ Merchant User: ID = ${this.merchantId}`);
} else {
console.error('❌ Merchant ID invalide pour Merchant User:', merchantId);
// Essayer de récupérer directement depuis le profil
const merchantPartnerId = this.authService.getCurrentMerchantPartnerId();
if (merchantPartnerId) {
const id = Number(merchantPartnerId);
if (!isNaN(id) && id > 0) {
this.merchantId = id;
this.accessService.setSelectedMerchantId(id);
this.isViewingGlobalData = false;
this.dataSelection.merchantPartnerId = id;
console.log(`✅ Merchant ID récupéré depuis profil: ${id}`);
}
} else {
console.error('Merchant ID invalide pour Merchant User:', merchantPartnerId);
this.addAlert('danger', 'Erreur de configuration',
'Impossible de déterminer le merchant ID', 'Maintenant');
this.isViewingGlobalData = false;
}
} else if (this.access.isHubUser) {
// Pour les hub users, vérifier si un merchant est sélectionné
}
}
// Pour les hub users
else if (this.access.isHubUser) {
const selectedMerchantId = this.accessService.getSelectedMerchantId();
if (selectedMerchantId && selectedMerchantId > 0) {
this.merchantId = selectedMerchantId;
this.isViewingGlobalData = false;
console.log(`Hub User: Merchant sélectionné = ${this.merchantId}`);
this.dataSelection.merchantPartnerId = selectedMerchantId;
console.log(`✅ Hub User: Merchant sélectionné = ${this.merchantId}`);
} else {
this.isViewingGlobalData = true;
this.merchantId = undefined;
console.log('Hub User: Mode global (aucun merchant sélectionné)');
this.dataSelection.merchantPartnerId = undefined;
console.log('✅ Hub User: Mode global (aucun merchant sélectionné)');
}
}
})
);
}
// Mettre à jour la sélection de données
this.dataSelection.merchantPartnerId = this.isViewingGlobalData ?
undefined : this.merchantId;
}
isValidMerchantId(id: any): boolean {
if (id === null || id === undefined) {
@ -390,6 +424,13 @@ export class DcbReportingDashboard implements OnInit, OnDestroy, AfterViewInit {
}
private initializeDashboard(): void {
// Vérifier d'abord si le profil est chargé
const profile = this.authService.getProfile();
if (!profile) {
console.log('⏳ Profil non chargé, attente...');
return;
}
if (this.access.isHubUser) {
if (this.isViewingGlobalData) {

View File

@ -1,22 +1,16 @@
import { inject, Injectable, Injector } from '@angular/core';
import { Observable, of } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
import { Injectable } from '@angular/core';
import { Observable, of, BehaviorSubject } from 'rxjs';
import { map, catchError, switchMap, take, filter, first } from 'rxjs/operators';
import { UserRole, RoleManagementService } from '@core/services/hub-users-roles-management.service';
import { MerchantConfigService } from '@modules/merchant-config/merchant-config.service';
import { AuthService } from '@core/services/auth.service';
// Interface minimaliste
export interface DashboardAccess {
// Type d'utilisateur - CORE
isHubUser: boolean;
isMerchantUser: boolean;
// Info du rôle
roleLabel: string;
roleIcon: string;
userRole: UserRole;
// Merchant info (seulement pour merchant users)
merchantId?: number;
}
@ -30,16 +24,48 @@ export class DashboardAccessService {
private accessCache: DashboardAccess | null = null;
private merchantsCache: AllowedMerchant[] | null = null;
private currentMerchantId: number | null = null;
private readonly injector = inject(Injector);
private accessReady$ = new BehaviorSubject<boolean>(false);
constructor(
private roleService: RoleManagementService,
private merchantService: MerchantConfigService
) {}
private merchantService: MerchantConfigService,
private authService: AuthService
) {
// S'abonner aux changements du profil utilisateur
this.initializeProfileSubscription();
}
/**
* Obtient l'accès simplifié
* Initialise la surveillance du profil utilisateur
*/
private initializeProfileSubscription(): void {
this.authService.getUserProfile().pipe(
filter(profile => profile !== null),
first()
).subscribe(profile => {
console.log('📊 DashboardAccessService: Profil utilisateur chargé', {
username: profile.username,
merchantPartnerId: profile.merchantPartnerId,
userType: profile.userType
});
this.clearCache();
this.accessReady$.next(true);
});
}
/**
* Attend que l'accès soit prêt
*/
waitForAccess(): Observable<boolean> {
return this.accessReady$.pipe(
filter(ready => ready),
take(1)
);
}
/**
* Obtient l'accès dashboard (version synchrone)
*/
getDashboardAccess(): DashboardAccess {
if (this.accessCache) {
@ -49,92 +75,174 @@ export class DashboardAccessService {
const userRole = this.roleService.getCurrentRole();
const isHubUser = this.roleService.isHubUser();
let merchantId: number | undefined = undefined;
if (!isHubUser) {
merchantId = this.getMerchantIdForCurrentUser();
}
const access: DashboardAccess = {
isHubUser,
isMerchantUser: !isHubUser,
roleLabel: this.roleService.getRoleLabel(),
roleIcon: this.roleService.getRoleIcon(),
userRole: userRole || UserRole.DCB_SUPPORT,
merchantId
};
// Pour les merchant users, définir leur merchant ID
if (!isHubUser) {
access.merchantId = this.getMerchantIdForUser();
}
console.log('📊 DashboardAccess créé:', {
...access,
merchantId,
userRoleLabel: userRole
});
this.accessCache = access;
return access;
}
/**
* Obtient le merchant ID pour un merchant user
* Obtient l'accès dashboard (version asynchrone)
*/
private getMerchantIdForUser(): number | undefined {
// Récupérer le merchant ID de l'utilisateur courant
const authService = this.injector.get(AuthService);
const merchantPartnerId = authService.getCurrentMerchantPartnerId();
// Vérifier si la valeur existe et est numérique
if (!merchantPartnerId) {
console.warn('Aucun merchant ID trouvé pour l\'utilisateur');
return undefined;
}
// Convertir en nombre en gérant les erreurs
const merchantId = Number(merchantPartnerId);
if (isNaN(merchantId) || !Number.isInteger(merchantId)) {
console.error(`Merchant ID invalide: ${merchantPartnerId}`);
return undefined;
}
return merchantId;
getDashboardAccessAsync(): Observable<DashboardAccess> {
return this.waitForAccess().pipe(
map(() => this.getDashboardAccess())
);
}
/**
* Obtient les merchants disponibles
* - Hub users: tous les merchants
* - Merchant users: seulement leur merchant
* Récupère le merchant ID pour l'utilisateur courant
*/
private getMerchantIdForCurrentUser(): number | undefined {
// Utiliser la méthode optimisée d'AuthService
const merchantId = this.authService.getCurrentMerchantPartnerIdAsNumber();
if (this.isValidMerchantId(merchantId)) {
console.log(`✅ Merchant ID récupéré: ${merchantId}`);
return merchantId;
}
console.warn('⚠️ Aucun merchant ID valide trouvé pour l\'utilisateur');
console.log('Debug:', {
merchantId,
profile: this.authService.getCurrentUserProfile(),
isAuthenticated: this.authService.isAuthenticated()
});
return undefined;
}
/**
* Valide si un ID marchand est valide
*/
private isValidMerchantId(id: any): id is number {
if (id === null || id === undefined) return false;
const numId = Number(id);
return !isNaN(numId) &&
Number.isInteger(numId) &&
numId > 0;
}
/**
* Obtient la liste des merchants disponibles
*/
getAvailableMerchants(): Observable<AllowedMerchant[]> {
return this.waitForAccess().pipe(
switchMap(() => {
if (this.merchantsCache) {
return of(this.merchantsCache);
}
const access = this.getDashboardAccess();
console.log('📊 getAvailableMerchants pour:', {
isHubUser: access.isHubUser,
merchantId: access.merchantId,
role: access.userRole
});
if (access.isHubUser) {
// Hub users voient tous les merchants
// Hub users: tous les merchants + option globale
return this.loadAllMerchantsForHubUser();
} else {
// Merchant users: seulement leur merchant
return this.loadSingleMerchantForUser(access.merchantId);
}
})
);
}
/**
* Charge tous les merchants pour les hub users
*/
private loadAllMerchantsForHubUser(): Observable<AllowedMerchant[]> {
return this.merchantService.getAllMerchants().pipe(
map(merchants => {
const availableMerchants: any[] = merchants.map(m => ({
const availableMerchants: AllowedMerchant[] = merchants.map(m => ({
id: m.id,
name: m.name
}));
// Ajouter l'option "Global"
availableMerchants.unshift({
id: 0,
name: '🌐 Données globales'
});
this.merchantsCache = availableMerchants;
return availableMerchants;
}),
catchError(error => {
console.error('Erreur chargement merchants:', error);
return of([]);
console.error('❌ Erreur lors du chargement des merchants:', error);
// Retourner au moins l'option globale
return of([{
id: 0,
name: '🌐 Données globales'
}]);
})
);
} else {
// Merchant users: seulement leur merchant
const merchantId = access.merchantId || this.getMerchantIdForUser();
return of([{
id: merchantId,
name: `Merchant ${merchantId}`
}]);
}
}
/**
* Définit le merchant sélectionné
* Charge le merchant unique pour un merchant user
*/
private loadSingleMerchantForUser(merchantId?: number): Observable<AllowedMerchant[]> {
if (!merchantId || merchantId <= 0) {
console.warn('⚠️ Aucun merchant ID valide pour merchant user');
// Dernière tentative de récupération
const fallbackId = this.getMerchantIdForCurrentUser();
if (this.isValidMerchantId(fallbackId)) {
const merchants = [{
id: fallbackId,
name: `🏪 Merchant ${fallbackId}`
}];
this.merchantsCache = merchants;
return of(merchants);
}
return of([]);
}
const merchants: AllowedMerchant[] = [{
id: merchantId,
name: `🏪 Merchant ${merchantId}`
}];
this.merchantsCache = merchants;
return of(merchants);
}
/**
* Définit le merchant sélectionné (pour hub users)
*/
setSelectedMerchantId(merchantId: number): void {
if (this.getDashboardAccess().isHubUser) {
this.currentMerchantId = merchantId;
console.log(`📌 Merchant sélectionné: ${merchantId}`);
} else {
console.warn('⚠️ Seuls les hub users peuvent sélectionner un merchant');
}
}
/**
@ -148,7 +256,7 @@ export class DashboardAccessService {
return access.merchantId || null;
}
// Hub users: le merchant sélectionné ou le premier
// Hub users: le merchant sélectionné
return this.currentMerchantId;
}
@ -158,9 +266,8 @@ export class DashboardAccessService {
canAccessMerchant(merchantId: number): Observable<boolean> {
const access = this.getDashboardAccess();
// Hub users: tous les merchants sont accessibles
if (access.isHubUser) {
return of(true);
return of(true); // Hub users: accès à tous
}
// Merchant users: seulement leur merchant
@ -174,13 +281,30 @@ export class DashboardAccessService {
this.accessCache = null;
this.merchantsCache = null;
this.currentMerchantId = null;
console.log('🗑️ DashboardAccessService: Cache nettoyé');
}
/**
* Méthodes utilitaires pour le template
* Méthode de débogage
*/
debug(): void {
console.log('=== 🔍 DEBUG DashboardAccessService ===');
console.log('1. Access cache:', this.accessCache);
console.log('2. Merchants cache:', this.merchantsCache);
console.log('3. Current merchant ID:', this.currentMerchantId);
console.log('4. Access ready:', this.accessReady$.value);
console.log('5. AuthService info:');
console.log(' - Merchant ID (number):', this.authService.getCurrentMerchantPartnerIdAsNumber());
console.log(' - Merchant ID (string):', this.authService.getCurrentMerchantPartnerId());
console.log(' - Profil courant:', this.authService.getCurrentUserProfile());
console.log(' - Token présent:', !!this.authService.getAccessToken());
console.log(' - Is authenticated:', this.authService.isAuthenticated());
console.log('==============================');
}
// ============ MÉTHODES UTILITAIRES POUR LE TEMPLATE ============
// Pour les Hub Users
shouldShowSystemHealth(): boolean {
return this.getDashboardAccess().isHubUser;
}
@ -199,40 +323,36 @@ export class DashboardAccessService {
return access.isHubUser && access.userRole === UserRole.DCB_ADMIN;
}
// Pour tous les utilisateurs
shouldShowTransactions(): boolean {
return true; // Tous peuvent voir les transactions (mais scope différent)
return true;
}
shouldShowCharts(): boolean {
return true; // Tous peuvent voir les transactions (mais scope différent)
return true;
}
shouldShowKPIs(): boolean {
return true; // Tous peuvent voir les transactions (mais scope différent)
return true;
}
shouldShowAlerts(): boolean {
return true; // Tous peuvent voir les transactions (mais scope différent)
return true;
}
canRefreshData(): boolean {
return true; // Tous peuvent rafraîchir
return true;
}
// Pour la sélection de merchant
canSelectMerchant(): boolean {
return this.getDashboardAccess().isHubUser;
}
// Pour l'affichage du merchant ID
shouldShowMerchantId(): boolean {
const access = this.getDashboardAccess();
return access.isMerchantUser ||
(access.isHubUser && this.getSelectedMerchantId() !== null);
}
// Pour l'édition du merchant filter
canEditMerchantFilter(): boolean {
const access = this.getDashboardAccess();
if (access.isHubUser) {
@ -240,4 +360,55 @@ export class DashboardAccessService {
}
return access.userRole === UserRole.DCB_PARTNER_ADMIN;
}
// ============ MÉTHODES UTILITAIRES SUPPLÉMENTAIRES ============
/**
* Vérifie si l'utilisateur peut voir les données globales
*/
canViewGlobalData(): boolean {
return this.getDashboardAccess().isHubUser;
}
/**
* Vérifie si l'utilisateur est en mode "données globales"
*/
isViewingGlobalData(): boolean {
const access = this.getDashboardAccess();
if (access.isHubUser) {
return this.currentMerchantId === 0 || this.currentMerchantId === null;
}
return false;
}
/**
* Retourne le nom du merchant courant
*/
getCurrentMerchantName(): string {
const access = this.getDashboardAccess();
if (access.isMerchantUser && access.merchantId) {
return `Merchant ${access.merchantId}`;
}
if (access.isHubUser) {
if (this.currentMerchantId === 0 || this.currentMerchantId === null) {
return 'Données globales';
} else if (this.currentMerchantId) {
return `Merchant ${this.currentMerchantId}`;
}
}
return 'Inconnu';
}
/**
* Réinitialise complètement le service
*/
reset(): void {
this.clearCache();
this.accessReady$.next(false);
// Réinitialiser la surveillance du profil
this.initializeProfileSubscription();
}
}