Compare commits
2 Commits
53190c2db0
...
710735f56d
| Author | SHA1 | Date | |
|---|---|---|---|
| 710735f56d | |||
|
9452732730
|
55
.idea/.idea.DotNetAngular/.idea/workspace.xml
generated
55
.idea/.idea.DotNetAngular/.idea/workspace.xml
generated
@@ -13,11 +13,41 @@
|
|||||||
</component>
|
</component>
|
||||||
<component name="ChangeListManager">
|
<component name="ChangeListManager">
|
||||||
<list default="true" id="1ac72a4a-52ad-4e70-9b15-c330b1ed3e7a" name="Changes" comment="">
|
<list default="true" id="1ac72a4a-52ad-4e70-9b15-c330b1ed3e7a" name="Changes" comment="">
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/.idea.DotNetAngular/.idea/shelf/Uncommitted_changes_before_Update_at_15_03_26__19_07__Changes_.xml" beforeDir="false" />
|
<change afterPath="$PROJECT_DIR$/src/ClientApp/src/app/presentation/components/admin-dashboard/admin-dashboard.component.html" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/ClientApp/src/app/presentation/components/admin-dashboard/admin-dashboard.component.scss" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/ClientApp/src/app/presentation/components/admin-dashboard/admin-dashboard.component.spec.ts" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/ClientApp/src/app/presentation/components/admin-dashboard/admin-dashboard.component.ts" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/ClientApp/src/app/presentation/components/unauthorized/unauthorized.component.html" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/ClientApp/src/app/presentation/components/unauthorized/unauthorized.component.scss" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/ClientApp/src/app/presentation/components/unauthorized/unauthorized.component.spec.ts" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/ClientApp/src/app/presentation/components/unauthorized/unauthorized.component.ts" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/ClientApp/src/app/presentation/components/user-dashboard/user-dashboard.component.html" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/ClientApp/src/app/presentation/components/user-dashboard/user-dashboard.component.scss" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/ClientApp/src/app/presentation/components/user-dashboard/user-dashboard.component.spec.ts" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/ClientApp/src/app/presentation/components/user-dashboard/user-dashboard.component.ts" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/ClientApp/src/app/presentation/components/user-table/user-table.component.html" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/ClientApp/src/app/presentation/components/user-table/user-table.component.scss" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/ClientApp/src/app/presentation/components/user-table/user-table.component.spec.ts" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/ClientApp/src/app/presentation/components/user-table/user-table.component.ts" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/ClientApp/src/app/presentation/guards/admin.guard.spec.ts" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/ClientApp/src/app/presentation/guards/admin.guard.ts" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/ClientApp/src/app/presentation/guards/authentication.guard.spec.ts" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/ClientApp/src/app/presentation/guards/authentication.guard.ts" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/ClientApp/src/app/presentation/guards/guest.guard.spec.ts" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/ClientApp/src/app/presentation/guards/guest.guard.ts" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/ClientApp/src/app/presentation/interceptors/token.interceptor.spec.ts" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/ClientApp/src/app/presentation/interceptors/token.interceptor.ts" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/ClientApp/src/app/presentation/shared/popup-modal/popup-modal.component.html" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/ClientApp/src/app/presentation/shared/popup-modal/popup-modal.component.scss" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/ClientApp/src/app/presentation/shared/popup-modal/popup-modal.component.spec.ts" afterDir="false" />
|
||||||
|
<change afterPath="$PROJECT_DIR$/src/ClientApp/src/app/presentation/shared/popup-modal/popup-modal.component.ts" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/.idea.DotNetAngular/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/.idea.DotNetAngular/.idea/workspace.xml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/.idea/.idea.DotNetAngular/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/.idea.DotNetAngular/.idea/workspace.xml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/ClientApp/src/app/infrastructure/services/auth-service.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/ClientApp/src/app/infrastructure/services/auth-service.ts" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/src/ClientApp/src/app/app.config.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/ClientApp/src/app/app.config.ts" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/ClientApp/src/app/app.routes.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/ClientApp/src/app/app.routes.ts" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/ClientApp/src/app/domain/entities/user.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/ClientApp/src/app/domain/entities/user.ts" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/ClientApp/src/app/presentation/authentication/login/login.component.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/ClientApp/src/app/presentation/authentication/login/login.component.ts" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/ClientApp/src/app/presentation/components/header/header.component.html" beforeDir="false" afterPath="$PROJECT_DIR$/src/ClientApp/src/app/presentation/components/header/header.component.html" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/ClientApp/src/app/presentation/components/header/header.component.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/ClientApp/src/app/presentation/components/header/header.component.ts" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/src/ClientApp/src/app/presentation/components/header/header.component.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/ClientApp/src/app/presentation/components/header/header.component.ts" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/ClientApp/src/main.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/ClientApp/src/main.ts" afterDir="false" />
|
|
||||||
</list>
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
@@ -69,7 +99,7 @@
|
|||||||
"RunOnceActivity.git.unshallow": "true",
|
"RunOnceActivity.git.unshallow": "true",
|
||||||
"RunOnceActivity.typescript.service.memoryLimit.init": "true",
|
"RunOnceActivity.typescript.service.memoryLimit.init": "true",
|
||||||
"com.intellij.ml.llm.matterhorn.ej.ui.settings.DefaultModelSelectionForGA.v1": "true",
|
"com.intellij.ml.llm.matterhorn.ej.ui.settings.DefaultModelSelectionForGA.v1": "true",
|
||||||
"git-widget-placeholder": "feature/haeder",
|
"git-widget-placeholder": "feature/guards",
|
||||||
"junie.onboarding.icon.badge.shown": "true",
|
"junie.onboarding.icon.badge.shown": "true",
|
||||||
"node.js.detected.package.eslint": "true",
|
"node.js.detected.package.eslint": "true",
|
||||||
"node.js.detected.package.tslint": "true",
|
"node.js.detected.package.tslint": "true",
|
||||||
@@ -221,7 +251,9 @@
|
|||||||
<workItem from="1772664174287" duration="646000" />
|
<workItem from="1772664174287" duration="646000" />
|
||||||
<workItem from="1773053172796" duration="1432000" />
|
<workItem from="1773053172796" duration="1432000" />
|
||||||
<workItem from="1773597121547" duration="689000" />
|
<workItem from="1773597121547" duration="689000" />
|
||||||
<workItem from="1773599776473" duration="1496000" />
|
<workItem from="1773599776473" duration="3799000" />
|
||||||
|
<workItem from="1773605341198" duration="3147000" />
|
||||||
|
<workItem from="1773678089390" duration="1025000" />
|
||||||
</task>
|
</task>
|
||||||
<task id="LOCAL-00001" summary="updating template">
|
<task id="LOCAL-00001" summary="updating template">
|
||||||
<option name="closed" value="true" />
|
<option name="closed" value="true" />
|
||||||
@@ -279,7 +311,15 @@
|
|||||||
<option name="project" value="LOCAL" />
|
<option name="project" value="LOCAL" />
|
||||||
<updated>1773598259701</updated>
|
<updated>1773598259701</updated>
|
||||||
</task>
|
</task>
|
||||||
<option name="localTasksCounter" value="8" />
|
<task id="LOCAL-00008" summary="fix dropdown">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1773601314558</created>
|
||||||
|
<option name="number" value="00008" />
|
||||||
|
<option name="presentableId" value="LOCAL-00008" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1773601314558</updated>
|
||||||
|
</task>
|
||||||
|
<option name="localTasksCounter" value="9" />
|
||||||
<servers />
|
<servers />
|
||||||
</component>
|
</component>
|
||||||
<component name="TypeScriptGeneratedFilesManager">
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
@@ -322,7 +362,8 @@
|
|||||||
<MESSAGE value="update nuget packages to new versions" />
|
<MESSAGE value="update nuget packages to new versions" />
|
||||||
<MESSAGE value="update NuGet Package to new Version" />
|
<MESSAGE value="update NuGet Package to new Version" />
|
||||||
<MESSAGE value="update Angular core to new version" />
|
<MESSAGE value="update Angular core to new version" />
|
||||||
<option name="LAST_COMMIT_MESSAGE" value="update Angular core to new version" />
|
<MESSAGE value="fix dropdown" />
|
||||||
|
<option name="LAST_COMMIT_MESSAGE" value="fix dropdown" />
|
||||||
</component>
|
</component>
|
||||||
<component name="XDebuggerManager">
|
<component name="XDebuggerManager">
|
||||||
<breakpoint-manager>
|
<breakpoint-manager>
|
||||||
|
|||||||
@@ -2,7 +2,12 @@ import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
|
|||||||
import { provideRouter } from '@angular/router';
|
import { provideRouter } from '@angular/router';
|
||||||
|
|
||||||
import { routes } from './app.routes';
|
import { routes } from './app.routes';
|
||||||
|
import {provideHttpClient, withFetch, withInterceptors} from "@angular/common/http";
|
||||||
|
import {tokenInterceptor} from "./presentation/interceptors/token.interceptor";
|
||||||
|
import {provideClientHydration, withEventReplay} from "@angular/platform-browser";
|
||||||
|
|
||||||
export const appConfig: ApplicationConfig = {
|
export const appConfig: ApplicationConfig = {
|
||||||
providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes)]
|
providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes),
|
||||||
|
provideHttpClient(withFetch(), withInterceptors([tokenInterceptor
|
||||||
|
])), provideClientHydration(withEventReplay())]
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,10 +2,21 @@ import { Routes } from '@angular/router';
|
|||||||
import {RegisterComponent} from "./presentation/authentication/register/register.component";
|
import {RegisterComponent} from "./presentation/authentication/register/register.component";
|
||||||
import {LoginComponent} from "./presentation/authentication/login/login.component";
|
import {LoginComponent} from "./presentation/authentication/login/login.component";
|
||||||
import {StartpageComponent} from "./presentation/components/startpage/startpage.component";
|
import {StartpageComponent} from "./presentation/components/startpage/startpage.component";
|
||||||
|
import {AdminDashboardComponent} from "./presentation/components/admin-dashboard/admin-dashboard.component";
|
||||||
|
import {UserDashboardComponent} from "./presentation/components/user-dashboard/user-dashboard.component";
|
||||||
|
import {UnauthorizedComponent} from "./presentation/components/unauthorized/unauthorized.component";
|
||||||
|
import {guestGuard} from "./presentation/guards/guest.guard";
|
||||||
|
import {AuthenticationGuard} from "./presentation/guards/authentication.guard";
|
||||||
|
import {adminGuard} from "./presentation/guards/admin.guard";
|
||||||
|
import {UserTableComponent} from "./presentation/components/user-table/user-table.component";
|
||||||
|
|
||||||
export const routes: Routes = [
|
export const routes: Routes = [
|
||||||
{path: '', redirectTo: 'login', pathMatch: 'full'},
|
{path: '', redirectTo: 'startpage', pathMatch: 'full'},
|
||||||
{path: 'register', component: RegisterComponent},
|
{path: 'register', component: RegisterComponent, canActivate: [guestGuard]},
|
||||||
{path: 'login', component: LoginComponent},
|
{path: 'login', component: LoginComponent, canActivate: [guestGuard]},
|
||||||
{path: 'startpage', component: StartpageComponent}
|
{path: 'startpage', component: StartpageComponent, canActivate: [guestGuard]},
|
||||||
|
{path: 'user-table', component: UserTableComponent, canActivate: [adminGuard]},
|
||||||
|
{path: 'admin-dashboard', component: AdminDashboardComponent, canActivate: [adminGuard]},
|
||||||
|
{path: 'user-dashboard', component: UserDashboardComponent, canActivate: [AuthenticationGuard]},
|
||||||
|
{path: 'unauthorized', component: UnauthorizedComponent, canActivate: [AuthenticationGuard]}
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import {RssFeed} from "./rss-feed";
|
|
||||||
import {UserRole} from "./user-role";
|
import {UserRole} from "./user-role";
|
||||||
|
|
||||||
export interface User {
|
export interface User {
|
||||||
@@ -8,9 +7,5 @@ export interface User {
|
|||||||
password: string;
|
password: string;
|
||||||
lastLogin: Date;
|
lastLogin: Date;
|
||||||
|
|
||||||
rssFeedId: number;
|
|
||||||
rssFeed: RssFeed;
|
|
||||||
|
|
||||||
UserRoles: UserRole[];
|
UserRoles: UserRole[];
|
||||||
rssFeeds: RssFeed [];
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,7 +66,8 @@ export class LoginComponent {
|
|||||||
this.userStore.setEmailForStore(tokenPayload.email);
|
this.userStore.setEmailForStore(tokenPayload.email);
|
||||||
this.userStore.setRoleForStore(tokenPayload.role);
|
this.userStore.setRoleForStore(tokenPayload.role);
|
||||||
this.loadingService.hide()
|
this.loadingService.hide()
|
||||||
this.router.navigate(['startpage']).then(success => {
|
// TODO redirect to...
|
||||||
|
this.router.navigate(['user-dashboard']).then(success => {
|
||||||
if (success) {
|
if (success) {
|
||||||
this.loginForm.reset();
|
this.loginForm.reset();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<div class="container-fluid d-flex justify-content-center">
|
||||||
|
<div class="card mt-2 col-2 me-2" [routerLink]="['/user-table']" style="cursor: pointer;">
|
||||||
|
<div class="card-body text-center">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="150" height="150" fill="blue" class="bi bi-people-fill " viewBox="0 0 16 16">
|
||||||
|
<path d="M7 14s-1 0-1-1 1-4 5-4 5 3 5 4-1 1-1 1zm4-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6m-5.784 6A2.24 2.24 0 0 1 5 13c0-1.355.68-2.75 1.936-3.72A6.3 6.3 0 0 0 5 9c-4 0-5 3-5 4s1 1 1 1zM4.5 8a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5"/>
|
||||||
|
</svg>
|
||||||
|
<h4 class="my-3">Users</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { AdminDashboardComponent } from './admin-dashboard.component';
|
||||||
|
|
||||||
|
describe('AdminDashboardComponent', () => {
|
||||||
|
let component: AdminDashboardComponent;
|
||||||
|
let fixture: ComponentFixture<AdminDashboardComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [AdminDashboardComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(AdminDashboardComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import {RouterLink} from "@angular/router";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-admin-dashboard',
|
||||||
|
imports: [
|
||||||
|
RouterLink
|
||||||
|
],
|
||||||
|
templateUrl: './admin-dashboard.component.html',
|
||||||
|
styleUrl: './admin-dashboard.component.scss',
|
||||||
|
standalone: true
|
||||||
|
})
|
||||||
|
export class AdminDashboardComponent {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,28 +1,11 @@
|
|||||||
<nav class="navbar navbar-expand-lg border-bottom position-relative bg-body-tertiary">
|
<nav class="navbar navbar-expand-lg border-bottom position-relative bg-body-tertiary">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
@if (showOverviewTools){
|
|
||||||
<div class="sidebar-toggle p-2 me-1 text-secondary" data-bs-target="#offcanvasExample" data-bs-toggle="offcanvas"
|
|
||||||
type="button">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-list"
|
|
||||||
viewBox="0 0 16 16">
|
|
||||||
<path fill-rule="evenodd"
|
|
||||||
d="M2.5 12a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5m0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5m0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
<a class="navbar-brand" [routerLink]="['/#']" title="Refresh" data-bs-target="#offcanvasExample" data-bs-dismiss="offcanvas">
|
<a class="navbar-brand" [routerLink]="['/#']" title="Refresh" data-bs-target="#offcanvasExample" data-bs-dismiss="offcanvas">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-app" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-app" viewBox="0 0 16 16">
|
||||||
<path d="M11 2a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H5a3 3 0 0 1-3-3V5a3 3 0 0 1 3-3zM5 1a4 4 0 0 0-4 4v6a4 4 0 0 0 4 4h6a4 4 0 0 0 4-4V5a4 4 0 0 0-4-4z"/>
|
<path d="M11 2a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H5a3 3 0 0 1-3-3V5a3 3 0 0 1 3-3zM5 1a4 4 0 0 0-4 4v6a4 4 0 0 0 4 4h6a4 4 0 0 0 4-4V5a4 4 0 0 0-4-4z"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
</a>
|
</a>
|
||||||
<button aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"
|
</div>
|
||||||
class="navbar-toggler"
|
|
||||||
data-bs-target="#navbarSupportedContent" data-bs-toggle="collapse" type="button">
|
|
||||||
<span class="navbar-toggler-icon"></span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<!-- Dropdown -->
|
<!-- Dropdown -->
|
||||||
@if (!showDropdown){
|
@if (!showDropdown){
|
||||||
<ul class="navbar-nav ms-auto" data-bs-target="#offcanvasExample" data-bs-dismiss="offcanvas">
|
<ul class="navbar-nav ms-auto" data-bs-target="#offcanvasExample" data-bs-dismiss="offcanvas">
|
||||||
|
|||||||
@@ -50,8 +50,7 @@ export class HeaderComponent implements OnInit {
|
|||||||
// Header-Logic
|
// Header-Logic
|
||||||
// TODO hide dropdown for login register and legal
|
// TODO hide dropdown for login register and legal
|
||||||
const headerRoutes = ['/login', '/register', '/legal', '/startpage'];
|
const headerRoutes = ['/login', '/register', '/legal', '/startpage'];
|
||||||
this.showDropdown = headerRoutes.some(route => event.urlAfterRedirects.startsWith(route));
|
this.showDropdown = headerRoutes.some(route => event.urlAfterRedirects.startsWith(route))
|
||||||
this.showOverviewTools = event.urlAfterRedirects.startsWith('/rss-feed-overview');
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
<p>unauthorized works!</p>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { UnauthorizedComponent } from './unauthorized.component';
|
||||||
|
|
||||||
|
describe('UnauthorizedComponent', () => {
|
||||||
|
let component: UnauthorizedComponent;
|
||||||
|
let fixture: ComponentFixture<UnauthorizedComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [UnauthorizedComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(UnauthorizedComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-unauthorized',
|
||||||
|
imports: [],
|
||||||
|
templateUrl: './unauthorized.component.html',
|
||||||
|
styleUrl: './unauthorized.component.scss',
|
||||||
|
})
|
||||||
|
export class UnauthorizedComponent {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
<div class="container-fluid">
|
||||||
|
<div class="align-items-center justify-content-center mt-3 d-flex">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row justify-content-center ">
|
||||||
|
<div class="col-lg-3">
|
||||||
|
<div class="card mb-3">
|
||||||
|
<div class="card-body text-center">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" width="150" height="150" class="bi bi-person-fill bg-primary rounded-circle" viewBox="0 0 16 16">
|
||||||
|
<path d="M3 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6"/>
|
||||||
|
</svg>
|
||||||
|
<h4 class="my-3">{{username}}</h4>
|
||||||
|
<!-- <button class="btn btn-primary">Update Picture</button>-->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<div class="row mb-2">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card mb-3">
|
||||||
|
<div class="card-header text-center">
|
||||||
|
<h2 class="mb-4">Personal Data</h2>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<p class="mb-1"><strong>Username:</strong> <br> {{username}}</p> <br>
|
||||||
|
<p class="mb-1"><strong>E-Mail:</strong> <br>{{email}}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- <div class="col-md-6">-->
|
||||||
|
<!-- <div class="card mb-3" style="height: 92%">-->
|
||||||
|
<!-- <div class="card-header text-center">-->
|
||||||
|
<!-- <h2 class="mb-4">Payment Method</h2>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- <div class="card-body">-->
|
||||||
|
<!-- <p class="mb-1"></p>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
</div>
|
||||||
|
<a class="text-decoration-none">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<!-- <div class="card mb-3">-->
|
||||||
|
<!-- <div class="card-header text-center">-->
|
||||||
|
<!-- <h4 class="my-3">Change password</h4>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- <div class="card-body d-flex flex-wrap">-->
|
||||||
|
<!-- <input type="password" class="form-control mt-2" placeholder="Enter new password">-->
|
||||||
|
<!-- <input type="password" class="form-control mt-2" placeholder="Repeat new password">-->
|
||||||
|
<!-- <button class="btn btn-primary mt-4">Change password</button>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<!-- <div class="card mb-3">-->
|
||||||
|
<!-- <div class="card-header text-center">-->
|
||||||
|
<!-- <h4 class="my-3">Change E-Mail</h4>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- <div class="card-body d-flex flex-wrap">-->
|
||||||
|
<!-- <input type="password" class="form-control mt-2" placeholder="Enter new E-Mail">-->
|
||||||
|
<!-- <input type="password" class="form-control mt-2" placeholder="Repeat new E-Mail">-->
|
||||||
|
<!-- <button class="btn btn-primary mt-4">Change E-Mail</button>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { UserDashboardComponent } from './user-dashboard.component';
|
||||||
|
|
||||||
|
describe('UserDashboardComponent', () => {
|
||||||
|
let component: UserDashboardComponent;
|
||||||
|
let fixture: ComponentFixture<UserDashboardComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [UserDashboardComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(UserDashboardComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import {Component, inject} from '@angular/core';
|
||||||
|
import {Title} from "@angular/platform-browser";
|
||||||
|
import {UserStoreService} from "../../../infrastructure/services/user-store.service";
|
||||||
|
import {AuthService} from "../../../infrastructure/services/auth-service";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-user-dashboard',
|
||||||
|
imports: [],
|
||||||
|
templateUrl: './user-dashboard.component.html',
|
||||||
|
styleUrl: './user-dashboard.component.scss',
|
||||||
|
})
|
||||||
|
export class UserDashboardComponent {
|
||||||
|
public username!: string;
|
||||||
|
public email!: string;
|
||||||
|
public userId!: number;
|
||||||
|
userStore = inject(UserStoreService);
|
||||||
|
private authenticateService = inject(AuthService);
|
||||||
|
title = inject(Title)
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
//TODO App Name
|
||||||
|
this.title.setTitle('User Dashboard | App Name');
|
||||||
|
}
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.userStore.getUsernameFromStore()
|
||||||
|
.subscribe(val => {
|
||||||
|
const usernameFromToken = this.authenticateService.getUsernameFromToken();
|
||||||
|
this.username = val || usernameFromToken
|
||||||
|
});
|
||||||
|
|
||||||
|
this.userStore.getEmailFromStore()
|
||||||
|
.subscribe(val => {
|
||||||
|
const emailFromToken = this.authenticateService.getEmailFromToken();
|
||||||
|
this.email = val || emailFromToken
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
<div class="container-fluid p-4">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header bg-primary text-center text-dark"><h4>User-List</h4></div>
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<table class="table table-striped table-hover table-bordered table-sm mb-0">
|
||||||
|
<thead>
|
||||||
|
<tr class="text-center">
|
||||||
|
<th>#</th>
|
||||||
|
<th (click)="sort('username')" class="sortable">
|
||||||
|
Username
|
||||||
|
<i class="bi"
|
||||||
|
[ngClass]="{
|
||||||
|
'bi bi-arrow-up': sortColumn === 'username' && sortDirection === 'asc',
|
||||||
|
'bi bi-arrow-down': sortColumn === 'username' && sortDirection === 'desc',
|
||||||
|
'bi bi-arrow-down-up': sortColumn !== 'username'
|
||||||
|
}"></i>
|
||||||
|
</th>
|
||||||
|
<th (click)="sort('email')" class="sortable">
|
||||||
|
E-Mail
|
||||||
|
<i class="bi"
|
||||||
|
[ngClass]="{
|
||||||
|
'bi bi-arrow-up': sortColumn === 'email' && sortDirection === 'asc',
|
||||||
|
'bi bi-arrow-down': sortColumn === 'email' && sortDirection === 'desc',
|
||||||
|
'bi bi-arrow-down-up': sortColumn !== 'email'
|
||||||
|
}"></i>
|
||||||
|
</th>
|
||||||
|
<th (click)="sort('lastLogin')" class="sortable">
|
||||||
|
Last Login
|
||||||
|
<i class="bi"
|
||||||
|
[ngClass]="{
|
||||||
|
'bi bi-arrow-up': sortColumn === 'lastLogin' && sortDirection === 'asc',
|
||||||
|
'bi bi-arrow-down': sortColumn === 'lastLogin' && sortDirection === 'desc',
|
||||||
|
'bi bi-arrow-down-up': sortColumn !== 'lastLogin'
|
||||||
|
}"></i>
|
||||||
|
</th>
|
||||||
|
<th>Action</th>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="table-group-divider">
|
||||||
|
@for (user of users; track user.id; let i = $index;) {
|
||||||
|
<tr>
|
||||||
|
<td class="text-center">{{ i + 1 }}</td>
|
||||||
|
<td>{{ user.username }}</td>
|
||||||
|
<td>{{ user.email }}</td>
|
||||||
|
<td class="text-center">{{ user.lastLogin | date:'dd.MM.yyyy HH:mm:ss':'Europe/Berlin' }}</td>
|
||||||
|
<td class="d-flex justify-content-center">
|
||||||
|
<button (click)="selectUser(user)" class="btn btn-sm btn-danger"
|
||||||
|
data-bs-target="#warningModal"
|
||||||
|
data-bs-toggle="modal"
|
||||||
|
><i class="bi bi-trash3-fill"></i></button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!-- Pagination Buttons Bootstrap -->
|
||||||
|
<nav aria-label="Page navigation">
|
||||||
|
<ul class="pagination justify-content-center mt-3">
|
||||||
|
|
||||||
|
<!-- Previous Button -->
|
||||||
|
<li class="page-item me-1" [class.disabled]="pageNumber === 1">
|
||||||
|
<a class="btn btn-primary" (click)="previousPage()" href="javascript:void(0)" aria-label="Previous">
|
||||||
|
<span aria-hidden="true">«</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<!-- Dynamic Page Buttons -->
|
||||||
|
@for (Page of [].constructor(totalPages); track Page ;let i = $index;) {
|
||||||
|
<li class="page-item me-1">
|
||||||
|
<a class="btn btn-outline-primary" href="javascript:void(0)" (click)="goToPage(i + 1)">
|
||||||
|
{{ i + 1 }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
|
||||||
|
<!-- Next Button -->
|
||||||
|
<li class="page-item me-1" [class.disabled]="pageNumber === totalPages">
|
||||||
|
<a class="btn btn-primary" (click)="nextPage()" href="javascript:void(0)" aria-label="Next">
|
||||||
|
<span aria-hidden="true">»</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-center align-items-center mb-3">
|
||||||
|
<span>Page {{ pageNumber }} of {{ totalPages }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<app-popup-modal
|
||||||
|
[title]="'Warning'"
|
||||||
|
[headerClass]="'bg-warning text-dark'"
|
||||||
|
(confirm)="handleConfirm()">
|
||||||
|
<p>
|
||||||
|
Do you really want to delete
|
||||||
|
<strong class="text-danger">{{ selectedUser?.username }}</strong> ?
|
||||||
|
</p></app-popup-modal>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { UserTableComponent } from './user-table.component';
|
||||||
|
|
||||||
|
describe('UserTableComponent', () => {
|
||||||
|
let component: UserTableComponent;
|
||||||
|
let fixture: ComponentFixture<UserTableComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [UserTableComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(UserTableComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
import {Component, inject} from '@angular/core';
|
||||||
|
import {User} from "../../../domain/entities/user";
|
||||||
|
import {UserService} from "../../../infrastructure/services/user.service";
|
||||||
|
import {ToastService} from "../../../infrastructure/services/toast.service";
|
||||||
|
import {Title} from "@angular/platform-browser";
|
||||||
|
import {PopupModalComponent} from "../../shared/popup-modal/popup-modal.component";
|
||||||
|
import {DatePipe, NgClass} from "@angular/common";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-user-table',
|
||||||
|
imports: [
|
||||||
|
PopupModalComponent,
|
||||||
|
DatePipe,
|
||||||
|
NgClass
|
||||||
|
],
|
||||||
|
templateUrl: './user-table.component.html',
|
||||||
|
styleUrl: './user-table.component.scss',
|
||||||
|
standalone: true
|
||||||
|
})
|
||||||
|
export class UserTableComponent {
|
||||||
|
public users: User[] = [];
|
||||||
|
selectedUser: User | null = null;
|
||||||
|
|
||||||
|
pageNumber: number = 1;
|
||||||
|
pageSize: number = 10;
|
||||||
|
totalPages: number = 0;
|
||||||
|
|
||||||
|
sortColumn: string = '';
|
||||||
|
sortDirection: 'asc' | 'desc' = 'asc';
|
||||||
|
|
||||||
|
private apiService = inject(UserService)
|
||||||
|
toastService = inject(ToastService);
|
||||||
|
title = inject(Title)
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.title.setTitle('User Table | RSS Feed Reader');
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.getAllUsers()
|
||||||
|
}
|
||||||
|
getAllUsers(): void {
|
||||||
|
this.apiService.getAllUsers(this.pageNumber, this.pageSize).subscribe(result => {
|
||||||
|
this.users = result.value.items;
|
||||||
|
this.totalPages = result.value.totalPages;
|
||||||
|
this.sort(this.sortColumn);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
nextPage(): void {
|
||||||
|
if (this.pageNumber < this.totalPages) {
|
||||||
|
this.pageNumber++;
|
||||||
|
this.getAllUsers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
previousPage(): void {
|
||||||
|
if (this.pageNumber > 1) {
|
||||||
|
this.pageNumber--;
|
||||||
|
this.getAllUsers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
goToPage(page: number): void {
|
||||||
|
if (page >= 1 && page <= this.totalPages) {
|
||||||
|
this.pageNumber = page;
|
||||||
|
this.getAllUsers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Sortiermethode
|
||||||
|
sort(column: string): void {
|
||||||
|
if (this.sortColumn === column) {
|
||||||
|
// Wenn die Spalte bereits sortiert ist, wechsle die Richtung
|
||||||
|
this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc';
|
||||||
|
} else {
|
||||||
|
// Andernfalls aufsteigend sortieren
|
||||||
|
this.sortDirection = 'asc';
|
||||||
|
this.sortColumn = column;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sortiere die Tabelle basierend auf Spalte und Richtung
|
||||||
|
this.users.sort((a: any, b: any) => {
|
||||||
|
const valueA = a[column];
|
||||||
|
const valueB = b[column];
|
||||||
|
if (valueA < valueB) {
|
||||||
|
return this.sortDirection === 'asc' ? -1 : 1;
|
||||||
|
}
|
||||||
|
if (valueA > valueB) {
|
||||||
|
return this.sortDirection === 'asc' ? 1 : -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteUser(id: number): void {
|
||||||
|
this.apiService.deleteUser(id).subscribe({
|
||||||
|
next: (result) => {
|
||||||
|
console.log('delete',result);
|
||||||
|
this.toastService.show(result.value, {
|
||||||
|
classname: 'bg-success text-light',
|
||||||
|
delay: 3000
|
||||||
|
})
|
||||||
|
this.users = this.users.filter(user => user.id !== id);
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
console.error(err.error.error.message);
|
||||||
|
this.toastService.show(err.error.error.message, {
|
||||||
|
classname: 'bg-danger text-light',
|
||||||
|
delay: 3000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
selectUser(user: User): void {
|
||||||
|
this.selectedUser = user;
|
||||||
|
console.log('selectedUser', user);
|
||||||
|
}
|
||||||
|
handleConfirm() {
|
||||||
|
this.deleteUser(this.selectedUser!.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import {TestBed} from '@angular/core/testing';
|
||||||
|
import {CanActivateFn} from '@angular/router';
|
||||||
|
|
||||||
|
import {adminGuard} from './admin.guard';
|
||||||
|
|
||||||
|
describe('adminGuard', () => {
|
||||||
|
const executeGuard: CanActivateFn = (...guardParameters) =>
|
||||||
|
TestBed.runInInjectionContext(() => adminGuard(...guardParameters));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(executeGuard).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
43
src/ClientApp/src/app/presentation/guards/admin.guard.ts
Normal file
43
src/ClientApp/src/app/presentation/guards/admin.guard.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import {CanActivateFn, Router} from '@angular/router';
|
||||||
|
import {inject} from "@angular/core";
|
||||||
|
import {AuthService} from "../../infrastructure/services/auth-service";
|
||||||
|
import {ToastService} from "../../infrastructure/services/toast.service";
|
||||||
|
import {UserStoreService} from "../../infrastructure/services/user-store.service";
|
||||||
|
import {catchError, map} from 'rxjs/operators';
|
||||||
|
import {of} from 'rxjs';
|
||||||
|
|
||||||
|
export const adminGuard: CanActivateFn = (route, state) => {
|
||||||
|
|
||||||
|
|
||||||
|
const userStore = inject(UserStoreService);
|
||||||
|
const authenticateService = inject(AuthService);
|
||||||
|
const toastService = inject(ToastService);
|
||||||
|
const router = inject(Router);
|
||||||
|
|
||||||
|
return userStore.getRoleFromStore().pipe(
|
||||||
|
map(val => {
|
||||||
|
const roleFromToken = authenticateService.getRoleFromToken();
|
||||||
|
const role = val || roleFromToken;
|
||||||
|
|
||||||
|
if (role === "Admin" || role === "SuperAdmin") {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
toastService.show("Access denied. Admins only.", {
|
||||||
|
classname: 'bg-warning text-dark',
|
||||||
|
delay: 3000
|
||||||
|
});
|
||||||
|
void router.navigate(['/unauthorized']);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
catchError(() => {
|
||||||
|
toastService.show("An error occurred. Please try again later.", {
|
||||||
|
classname: 'bg-danger text-light',
|
||||||
|
delay: 2000
|
||||||
|
});
|
||||||
|
void router.navigate(['/login']);
|
||||||
|
return of(false);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import {TestBed} from '@angular/core/testing';
|
||||||
|
|
||||||
|
import {AuthenticationGuard} from './authentication.guard';
|
||||||
|
import {provideHttpClient} from "@angular/common/http";
|
||||||
|
import {ActivatedRoute} from "@angular/router";
|
||||||
|
import {of} from "rxjs";
|
||||||
|
|
||||||
|
describe('AuthenticationGuard', () => {
|
||||||
|
let guard: AuthenticationGuard;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
providers: [provideHttpClient(),
|
||||||
|
{ provide: ActivatedRoute, useValue: { data: of({}), firstChild: null } },
|
||||||
|
]
|
||||||
|
});
|
||||||
|
guard = TestBed.inject(AuthenticationGuard);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(guard).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import {inject, Injectable} from '@angular/core';
|
||||||
|
import {CanActivate, Router} from '@angular/router';
|
||||||
|
import {AuthService} from "../../infrastructure/services/auth-service";
|
||||||
|
import {ToastService} from "../../infrastructure/services/toast.service";
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class AuthenticationGuard implements CanActivate {
|
||||||
|
toastService = inject(ToastService);
|
||||||
|
private authenticateService = inject(AuthService)
|
||||||
|
private router = inject(Router)
|
||||||
|
|
||||||
|
canActivate(): boolean {
|
||||||
|
if (this.authenticateService.isLoggedIn()) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
this.toastService.show("Please Login first", {
|
||||||
|
classname: 'bg-warning text-dark',
|
||||||
|
delay: 2000
|
||||||
|
});
|
||||||
|
this.router.navigate(['/login']).catch(error => {
|
||||||
|
console.error('Navigation error:', error);
|
||||||
|
})
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
import { CanActivateFn } from '@angular/router';
|
||||||
|
|
||||||
|
import { guestGuard } from './guest.guard';
|
||||||
|
|
||||||
|
describe('guestGuard', () => {
|
||||||
|
const executeGuard: CanActivateFn = (...guardParameters) =>
|
||||||
|
TestBed.runInInjectionContext(() => guestGuard(...guardParameters));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(executeGuard).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
16
src/ClientApp/src/app/presentation/guards/guest.guard.ts
Normal file
16
src/ClientApp/src/app/presentation/guards/guest.guard.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import {CanActivateFn, Router} from '@angular/router';
|
||||||
|
import {inject} from "@angular/core";
|
||||||
|
import {AuthService} from "../../infrastructure/services/auth-service";
|
||||||
|
|
||||||
|
|
||||||
|
export const guestGuard: CanActivateFn = (route, state) => {
|
||||||
|
const authService = inject(AuthService);
|
||||||
|
const router = inject(Router);
|
||||||
|
|
||||||
|
if (!authService.isLoggedIn()){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// TODO rout
|
||||||
|
void router.navigate(['/user-dashboard']);
|
||||||
|
return false;
|
||||||
|
};
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import {TestBed} from '@angular/core/testing';
|
||||||
|
import {HttpInterceptorFn} from '@angular/common/http';
|
||||||
|
|
||||||
|
import {tokenInterceptor} from './token.interceptor';
|
||||||
|
|
||||||
|
describe('tokenInterceptor', () => {
|
||||||
|
const interceptor: HttpInterceptorFn = (req, next) =>
|
||||||
|
TestBed.runInInjectionContext(() => tokenInterceptor(req, next));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(interceptor).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import {HttpErrorResponse, HttpEvent, HttpHandlerFn, HttpInterceptorFn, HttpRequest} from '@angular/common/http';
|
||||||
|
import {inject} from "@angular/core";
|
||||||
|
import {AuthService} from "../../infrastructure/services/auth-service";
|
||||||
|
import {catchError, Observable, switchMap, throwError} from "rxjs";
|
||||||
|
import {ToastService} from "../../infrastructure/services/toast.service";
|
||||||
|
import {Router} from '@angular/router';
|
||||||
|
import {RefreshTokenRequest} from "../models/refresh-token-request";
|
||||||
|
|
||||||
|
export const tokenInterceptor: HttpInterceptorFn = (req, next) => {
|
||||||
|
const authService = inject(AuthService);
|
||||||
|
const token = authService.getToken();
|
||||||
|
const toastService = inject(ToastService);
|
||||||
|
const router = inject(Router);
|
||||||
|
|
||||||
|
if (token) {
|
||||||
|
req = req.clone({
|
||||||
|
setHeaders: {Authorization: `Bearer ${token}`}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return next(req).pipe(
|
||||||
|
catchError((err: any) => {
|
||||||
|
if (err instanceof HttpErrorResponse) {
|
||||||
|
if (err.status === 401) {
|
||||||
|
return handleUnAuthorizedError(req, next, authService, router, toastService);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log("interceptor error",err.error?.error?.message);
|
||||||
|
return throwError(() => err);
|
||||||
|
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
function handleUnAuthorizedError(
|
||||||
|
req: HttpRequest<any>,
|
||||||
|
next: HttpHandlerFn,
|
||||||
|
authenticateService: AuthService,
|
||||||
|
router: Router,
|
||||||
|
toastService: ToastService
|
||||||
|
): Observable<HttpEvent<any>> {
|
||||||
|
|
||||||
|
const refreshTokenRequest = new RefreshTokenRequest();
|
||||||
|
refreshTokenRequest.refreshToken = authenticateService.getRefreshToken()!;
|
||||||
|
refreshTokenRequest.userId = authenticateService.getUserIdFromToken()!;
|
||||||
|
|
||||||
|
return authenticateService.renewToken(refreshTokenRequest).pipe(
|
||||||
|
switchMap((data) => {
|
||||||
|
const tokenResponse = data.value;
|
||||||
|
authenticateService.storeRefreshToken(tokenResponse.refreshToken);
|
||||||
|
authenticateService.storeToken(tokenResponse.accessToken);
|
||||||
|
|
||||||
|
req = req.clone({
|
||||||
|
setHeaders: {Authorization: `Bearer ${tokenResponse.accessToken}`}
|
||||||
|
});
|
||||||
|
|
||||||
|
return next(req);
|
||||||
|
}),
|
||||||
|
catchError((err) => {
|
||||||
|
return throwError(() => {
|
||||||
|
authenticateService.signOut();
|
||||||
|
router.navigate(['login']).then(success => {
|
||||||
|
if (success) {
|
||||||
|
toastService.show(err.error?.error?.message ||'Token is expired, login again!', {
|
||||||
|
classname: 'bg-warning text-light',
|
||||||
|
delay: 2000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<div class="modal fade" id="warningModal" tabindex="-1" aria-labelledby="warningModalLabel" aria-hidden="true" data-bs-backdrop="static">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header" [ngClass]="headerClass">
|
||||||
|
<h1 class="modal-title fs-5 text-center w-100" id="warningModalLabel">{{ title }}</h1>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<ng-content></ng-content>
|
||||||
|
@if (body) {
|
||||||
|
<p>{{ body }}</p>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-danger" data-bs-dismiss="modal">No</button>
|
||||||
|
<button type="button" class="btn btn-warning" (click)="onConfirm()" data-bs-dismiss="modal">Yes</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { PopupModalComponent } from './popup-modal.component';
|
||||||
|
|
||||||
|
describe('PopupModalComponent', () => {
|
||||||
|
let component: PopupModalComponent;
|
||||||
|
let fixture: ComponentFixture<PopupModalComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [PopupModalComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(PopupModalComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import {Component, EventEmitter, Input, Output} from '@angular/core';
|
||||||
|
import {NgClass} from "@angular/common";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-popup-modal',
|
||||||
|
imports: [
|
||||||
|
NgClass
|
||||||
|
],
|
||||||
|
templateUrl: './popup-modal.component.html',
|
||||||
|
styleUrl: './popup-modal.component.scss',
|
||||||
|
standalone: true
|
||||||
|
})
|
||||||
|
export class PopupModalComponent {
|
||||||
|
|
||||||
|
|
||||||
|
@Input() headerClass: string = 'bg-light';
|
||||||
|
@Input() title: string = '';
|
||||||
|
@Input() body: string = '';
|
||||||
|
|
||||||
|
@Output() confirm = new EventEmitter<void>();
|
||||||
|
@Output() cancel = new EventEmitter<void>();
|
||||||
|
|
||||||
|
onConfirm() {
|
||||||
|
this.confirm.emit();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user