diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 2813f882..7c2a4cee 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,119 +1,115 @@ -/** - * Main Modules - * - */ -import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; -import { AppPreloadingStrategy } from './core/app_preloading_strategy'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; -import { BrowserModule, Title } from '@angular/platform-browser'; -import { NgIdleKeepaliveModule } from '@ng-idle/keepalive'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { NgIdleKeepaliveModule } from '@ng-idle/keepalive'; +import { StoreModule } from '@ngrx/store'; import { DataTablesModule } from 'angular-datatables'; -import { ReactiveFormsModule } from '@angular/forms'; -import { Injector, NgModule } from '@angular/core'; +import { MomentModule } from 'ngx-moment'; + import { CommonModule } from '@angular/common'; +import { HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; +import { Injector, NgModule, inject, provideAppInitializer } from '@angular/core'; import { FormsModule } from '@angular/forms'; -import { StoreModule } from '@ngrx/store'; -import { MomentModule } from 'ngx-moment'; -/** - * App Pages Components - * - */ -import { ScreenSizeDetectorComponent } from './layout/screen-size-detector/screen-size-detector.component'; -import { PageNotFoundComponent } from './layout/page-not-found/page-not-found.component'; -import { AuthInterceptorService } from './core/_interceptors/auth-interceptor.service'; -import { HttpResInterceptor } from './core/_interceptors/http-res.interceptor'; -import { BreadcrumbComponent } from './shared/breadcrumb/breadcrumb.component'; -import { ErrorPageComponent } from './layout/error-page/error-page.component'; -import { TimeoutComponent } from './shared/alert/timeout/timeout.component'; -import { ConfigService } from './core/_services/shared/config.service'; -import { HeaderComponent } from './layout/header/header.component'; -import { FooterComponent } from './layout/footer/footer.component'; -import { AppRoutingModule } from './app-routing.module'; -import { AppComponent } from './app.component'; -/** - * App Modules, Reducers - * - */ -import { ScrollYTopComponent } from './shared/scrollytop/scrollytop.component'; -import { ThemeService } from './core/_services/shared/theme.service'; -import { ComponentsModule } from './shared/components.module'; -import { DirectivesModule } from './shared/directives.module'; -import { configReducer } from './core/_store/config.reducer'; -import { PipesModule } from './shared/pipes.module'; -import { AuthModule } from './auth/auth.module'; +import { ReactiveFormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; +import { MatCardModule } from '@angular/material/card'; +import { MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field'; import { MatIconModule } from '@angular/material/icon'; import { MatMenuModule } from '@angular/material/menu'; -import { MatTooltipModule } from '@angular/material/tooltip'; +import { MAT_SNACK_BAR_DEFAULT_OPTIONS, MatSnackBarModule } from '@angular/material/snack-bar'; import { MatToolbarModule } from '@angular/material/toolbar'; -import { MatCardModule } from '@angular/material/card'; -import { CoreComponentsModule } from './core/_components/core-components.module'; -import { MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field'; -import { - MAT_SNACK_BAR_DEFAULT_OPTIONS, - MatSnackBarModule -} from '@angular/material/snack-bar'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { BrowserModule, Title } from '@angular/platform-browser'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; + +import { AppInitService } from '@services/app-init.service'; +import { ConfigService } from '@services/shared/config.service'; +import { ThemeService } from '@services/shared/theme.service'; + +import { CoreComponentsModule } from '@components/core-components.module'; + +import { AppRoutingModule } from '@src/app/app-routing.module'; +import { AppComponent } from '@src/app/app.component'; +import { AuthModule } from '@src/app/auth/auth.module'; +import { AuthInterceptorService } from '@src/app/core/_interceptors/auth-interceptor.service'; +import { HttpResInterceptor } from '@src/app/core/_interceptors/http-res.interceptor'; +import { configReducer } from '@src/app/core/_store/config.reducer'; +import { AppPreloadingStrategy } from '@src/app/core/app_preloading_strategy'; +import { ErrorPageComponent } from '@src/app/layout/error-page/error-page.component'; +import { FooterComponent } from '@src/app/layout/footer/footer.component'; +import { HeaderComponent } from '@src/app/layout/header/header.component'; +import { PageNotFoundComponent } from '@src/app/layout/page-not-found/page-not-found.component'; +import { ScreenSizeDetectorComponent } from '@src/app/layout/screen-size-detector/screen-size-detector.component'; +import { BreadcrumbComponent } from '@src/app/shared/breadcrumb/breadcrumb.component'; +import { ComponentsModule } from '@src/app/shared/components.module'; +import { DirectivesModule } from '@src/app/shared/directives.module'; +import { PipesModule } from '@src/app/shared/pipes.module'; +import { ScrollYTopComponent } from '@src/app/shared/scrollytop/scrollytop.component'; -@NgModule({ declarations: [ - ScreenSizeDetectorComponent, - PageNotFoundComponent, - ScrollYTopComponent, - BreadcrumbComponent, - ErrorPageComponent, - HeaderComponent, - FooterComponent, - AppComponent - ], - bootstrap: [AppComponent], imports: [BrowserAnimationsModule, - ReactiveFormsModule, - FontAwesomeModule, - DirectivesModule, - DataTablesModule, - ComponentsModule, - BrowserModule, - CommonModule, - MomentModule, - PipesModule, - FormsModule, - AuthModule, - MatToolbarModule, - MatButtonModule, - MatIconModule, - MatMenuModule, - MatCardModule, - MatTooltipModule, - MatSnackBarModule, - CoreComponentsModule, - NgbModule, - AppRoutingModule, // Main routes for the App - NgIdleKeepaliveModule.forRoot(), - StoreModule.forRoot({ configList: configReducer })], providers: [ - Title, - { - provide: HTTP_INTERCEPTORS, - useClass: AuthInterceptorService, - multi: true - }, - { - provide: HTTP_INTERCEPTORS, - useClass: HttpResInterceptor, - multi: true - }, - ThemeService, - AppPreloadingStrategy, - ConfigService, - { - provide: MAT_FORM_FIELD_DEFAULT_OPTIONS, - useValue: { appearance: 'outline' } - }, - { - provide: MAT_SNACK_BAR_DEFAULT_OPTIONS, - useValue: { duration: 2500, verticalPosition: 'top' } - }, - provideHttpClient(withInterceptorsFromDi()) - ] }) +@NgModule({ + declarations: [ + ScreenSizeDetectorComponent, + PageNotFoundComponent, + ScrollYTopComponent, + BreadcrumbComponent, + ErrorPageComponent, + HeaderComponent, + FooterComponent, + AppComponent + ], + bootstrap: [AppComponent], + imports: [ + BrowserAnimationsModule, + ReactiveFormsModule, + FontAwesomeModule, + DirectivesModule, + DataTablesModule, + ComponentsModule, + BrowserModule, + CommonModule, + MomentModule, + PipesModule, + FormsModule, + AuthModule, + MatToolbarModule, + MatButtonModule, + MatIconModule, + MatMenuModule, + MatCardModule, + MatTooltipModule, + MatSnackBarModule, + CoreComponentsModule, + NgbModule, + AppRoutingModule, // Main routes for the App + NgIdleKeepaliveModule.forRoot(), + StoreModule.forRoot({ configList: configReducer }) + ], + providers: [ + Title, + { + provide: HTTP_INTERCEPTORS, + useClass: AuthInterceptorService, + multi: true + }, + { + provide: HTTP_INTERCEPTORS, + useClass: HttpResInterceptor, + multi: true + }, + provideAppInitializer(() => inject(AppInitService).initializeApp()), + ThemeService, + AppPreloadingStrategy, + ConfigService, + { + provide: MAT_FORM_FIELD_DEFAULT_OPTIONS, + useValue: { appearance: 'outline' } + }, + { + provide: MAT_SNACK_BAR_DEFAULT_OPTIONS, + useValue: { duration: 2500, verticalPosition: 'top' } + }, + provideHttpClient(withInterceptorsFromDi()) + ] +}) export class AppModule { static injector: Injector; constructor(injector: Injector) { diff --git a/src/app/core/_guards/permission.guard.ts b/src/app/core/_guards/permission.guard.ts index fdea1632..22ef7e73 100644 --- a/src/app/core/_guards/permission.guard.ts +++ b/src/app/core/_guards/permission.guard.ts @@ -1,3 +1,5 @@ +import { Observable, catchError, map, of } from 'rxjs'; + import { Injectable, inject } from '@angular/core'; import { ActivatedRouteSnapshot, CanActivateFn } from '@angular/router'; @@ -36,21 +38,27 @@ export class PermissionGuard { return perm; } - canActivate(route: ActivatedRouteSnapshot): boolean { + canActivate(route: ActivatedRouteSnapshot): Observable { const requiredPermission = route.data['permission'] as PermissionValues; + return this.permissionService.hasPermission(requiredPermission).pipe( + map((has) => { + if (has) { + return true; + } - if (this.permissionService.hasPermissionSync(requiredPermission)) { - return true; - } - - const formattedPermission = this.formatPermissionName(requiredPermission); - const message = `You are not allowed to ${formattedPermission.toLowerCase()}`; - - this.alert.showErrorMessage(`Access denied: ${message}. Please contact your Administrator.`); - return false; + const formattedPermission = this.formatPermissionName(requiredPermission); + const message = `You are not allowed to ${formattedPermission.toLowerCase()}`; + this.alert.showErrorMessage(`Access denied: ${message}. Please contact your Administrator.`); + return false; + }), + catchError(() => { + this.alert.showErrorMessage(`Access denied: Unexpected error while checking permissions.`); + return of(false); + }) + ); } } -export const CheckPerm: CanActivateFn = (route: ActivatedRouteSnapshot): boolean => { +export const CheckPerm: CanActivateFn = (route: ActivatedRouteSnapshot): Observable => { return inject(PermissionGuard).canActivate(route); }; diff --git a/src/app/core/_services/app-init.service.ts b/src/app/core/_services/app-init.service.ts new file mode 100644 index 00000000..aaa6f1b1 --- /dev/null +++ b/src/app/core/_services/app-init.service.ts @@ -0,0 +1,34 @@ +import { firstValueFrom } from 'rxjs'; +import { take } from 'rxjs/operators'; + +import { Injectable } from '@angular/core'; + +import { AuthService } from '@services/access/auth.service'; +import { PermissionService } from '@services/permission/permission.service'; +import { AlertService } from '@services/shared/alert.service'; + +@Injectable({ providedIn: 'root' }) +export class AppInitService { + constructor( + private permissionService: PermissionService, + private auth: AuthService, + private alertService: AlertService + ) {} + + /** + * Call this on app start to ensure permissions are loaded, if user is already logged in + */ + async initializeApp(): Promise { + try { + const isLoggedIn = this.auth.token !== 'notoken'; + + if (isLoggedIn) { + await firstValueFrom(this.permissionService.loadPermissions().pipe(take(1))); + } + } catch (err) { + console.error('Error during app initialization:', err); + this.alertService.showErrorMessage('Failed to initialize application permissions.'); + throw err; + } + } +}