Refactor tutorial service for public access and enhance authentication handling

This commit is contained in:
liosha84
2025-07-06 19:37:32 +03:00
parent c39571ad70
commit 2ee97f8c86
25 changed files with 217 additions and 325 deletions
-55
View File
@@ -1,55 +0,0 @@
<mat-toolbar class="fixed-top">
<button mat-raised-button routerLink="/" href="#">
My App
</button>
<span class="example-spacer"></span>
<button mat-raised-button routerLink="tutorials">
Tutorials
</button>
<button mat-raised-button routerLink="add" *ngIf="isLoggedIn">
Add tutorial
</button>
<span class="example-spacer"></span>
@if (showAdminBoard) {
<button mat-raised-button routerLink="admin" >
<mat-icon>manage_accounts</mat-icon>
Admin Bord
</button>
}
@if (showAdminBoard) {
<button mat-raised-button routerLink="system" >
<mat-icon>settings_applications</mat-icon>
System
</button>
}
<span class="example-spacer"></span>
<button mat-raised-button routerLink="register" *ngIf="!isLoggedIn">
<mat-icon>app_registration</mat-icon>
Register
</button>
<button mat-raised-button (click)="openDialog('100ms', '5ms')" *ngIf="!isLoggedIn">
<mat-icon>login</mat-icon>
Login
</button>
<button mat-raised-button routerLink="profile" *ngIf="isLoggedIn">
<mat-icon>account_circle</mat-icon>
{{ username }}
</button>
<button mat-raised-button (click)="logout()"*ngIf="isLoggedIn">
<mat-icon>logout</mat-icon>
Logout
</button>
</mat-toolbar>
<div class="router_outlet_padding">
<router-outlet></router-outlet>
</div>
-23
View File
@@ -1,23 +0,0 @@
//#header_panel{
// background-color: #181d1f;
//}
.example-spacer {
flex: 1 1 auto;
}
mat-toolbar{
background-color: rgba(153,153,153,0.16);
backdrop-filter: blur(8px);
}
.fixed-top {
position: fixed;
top: 0;
right: 0;
left: 0;
z-index: 1000; // Ensure toolbar stays above other content
}
.router_outlet_padding{
padding-top: 60px;
scroll-padding-inline: 40%;
}
@@ -1,35 +0,0 @@
import { TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [
RouterTestingModule
],
declarations: [
AppComponent
],
}).compileComponents();
});
it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app).toBeTruthy();
});
// it(`should have as title 'spring-angular-ui'`, () => {
// const fixture = TestBed.createComponent(AppComponent);
// const app = fixture.componentInstance;
// expect(app.title).toEqual('spring-angular-ui');
// });
it('should render title', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.nativeElement;
expect(compiled.querySelector('.content span').textContent).toContain('spring-angular-ui app is running!');
});
});
-124
View File
@@ -1,124 +0,0 @@
import {Component, CUSTOM_ELEMENTS_SCHEMA, inject, OnInit} from '@angular/core';
import { TokenStorageService } from './services/token-storage.service';
import {MatDialog} from "@angular/material/dialog";
import {DialogComponent} from "./components/dialog/dialog.component";
import {AuthService} from "./services/auth.service";
import {Subscription} from "rxjs";
import {EventBusService} from "./_shared/event-bus.service";
import {RouterLink, RouterOutlet} from '@angular/router';
import {MatButton} from '@angular/material/button';
import {MatToolbar} from '@angular/material/toolbar';
import {MatIcon} from '@angular/material/icon';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
schemas: [CUSTOM_ELEMENTS_SCHEMA],
imports: [
RouterOutlet,
MatIcon,
MatButton,
RouterLink,
MatToolbar
],
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
readonly dialog = inject(MatDialog);
//private authService = new AuthService(provideHttpClient())
public dialogData : DialogData = {password: "", username: ""};
private roles: string[] = [];
isLoggedIn = false;
showAdminBoard = false;
showModeratorBoard = false;
username?: string;
eventBusSub?: Subscription;
private errorMessage: any;
private isLoginFailed: boolean = false;
constructor(
private storageService: TokenStorageService,
private authService: AuthService,
private eventBusService: EventBusService
) {}
ngOnInit(): void {
this.isLoggedIn = this.storageService.isLoggedIn();
if (this.isLoggedIn) {
const user = this.storageService.getUser();
this.roles = user.roles;
this.showAdminBoard = true;//this.roles.includes('ROLE_ADMIN');
this.showModeratorBoard = this.roles.includes('ROLE_MODERATOR');
this.username = user.username;
}
this.eventBusSub = this.eventBusService.on('logout', () => {
this.logout();
});
}
logout(): void {
this.authService.logout().subscribe({
next: res => {
console.log(res);
this.storageService.clean();
window.location.reload();
},
error: err => {
console.log(err);
}
});
}
openDialog(enterAnimationDuration: string, exitAnimationDuration: string) {
let dialogRef = this.dialog.open(DialogComponent, {
width: '350px',
enterAnimationDuration,
exitAnimationDuration,
data: {username: this.dialogData.username, password: this.dialogData.password}
});
dialogRef.afterClosed().subscribe(result => {
console.log('The dialog was closed');
if(result== null){
return;
}
this.dialogData = result;
this.authService.login(this.dialogData.username, this.dialogData.password).subscribe(
data => {
this.storageService.saveToken(data.accessToken);
this.storageService.saveUser(data);
this.isLoginFailed = false;
this.isLoggedIn = true;
let user = this.storageService.getUser();
this.roles = user.roles;
//this.username = user.username;
this.reloadPage();
},
err => {
this.errorMessage = err.error.message;
this.isLoginFailed = true;
}
);
});
}
reloadPage(): void {
window.location.reload();
}
}
export interface DialogData {
username: string;
password: string;
}
+3 -1
View File
@@ -12,5 +12,7 @@ import { RouterOutlet } from '@angular/router';
export class App {
protected title = 'jambotron-ui';
constructor() { }
constructor() {
}
}
@@ -74,7 +74,7 @@ const language = 'typescript';
description: this.tutorial.description
};
this.tutorialService.create(data)
/* this.tutorialService.create(data)
.subscribe(
response => {
console.log(response);
@@ -82,7 +82,7 @@ const language = 'typescript';
},
error => {
console.log(error);
});
});*/
}
newTutorial(): void {
@@ -36,7 +36,7 @@ export class TutorialDetailsComponent implements OnInit {
}
getTutorial(id: string): void {
this.tutorialService.get(id)
/* this.tutorialService.get(id)
.subscribe(
data => {
this.currentTutorial = data;
@@ -44,7 +44,7 @@ export class TutorialDetailsComponent implements OnInit {
},
error => {
console.log(error);
});
});*/
}
updatePublished(status: boolean): void {
@@ -56,7 +56,7 @@ export class TutorialDetailsComponent implements OnInit {
this.message = '';
this.tutorialService.update(this.currentTutorial.id, data)
/* this.tutorialService.update(this.currentTutorial.id, data)
.subscribe(
response => {
this.currentTutorial.published = status;
@@ -65,11 +65,11 @@ export class TutorialDetailsComponent implements OnInit {
},
error => {
console.log(error);
});
});*/
}
updateTutorial(): void {
this.message = '';
/* this.message = '';
this.tutorialService.update(this.currentTutorial.id, this.currentTutorial)
.subscribe(
@@ -79,11 +79,11 @@ export class TutorialDetailsComponent implements OnInit {
},
error => {
console.log(error);
});
});*/
}
deleteTutorial(): void {
this.tutorialService.delete(this.currentTutorial.id)
/* this.tutorialService.delete(this.currentTutorial.id)
.subscribe(
response => {
console.log(response);
@@ -91,6 +91,6 @@ export class TutorialDetailsComponent implements OnInit {
},
error => {
console.log(error);
});
});*/
}
}
@@ -31,9 +31,9 @@ export class TutorialsListComponent implements OnInit {
}
retrieveTutorials(): void {
this.tutorialService.getAll()
this.tutorialService.getAllPublic()
.subscribe(
data => {
(data: Tutorial[] ) => {
this.tutorials = data;
console.log(data);
},
@@ -54,7 +54,7 @@ export class TutorialsListComponent implements OnInit {
}
removeAllTutorials(): void {
this.tutorialService.deleteAll()
/* this.tutorialService.deleteAll()
.subscribe(
response => {
console.log(response);
@@ -62,7 +62,7 @@ export class TutorialsListComponent implements OnInit {
},
error => {
console.log(error);
});
});*/
}
searchTitle(): void {
@@ -36,14 +36,14 @@ export class TutorialsComponent {
// }
retrieveTutorials(): void {
this.tutorialService.getAll()
.subscribe(
data => {
this.tutorials = data;
console.log(data);
},
error => {
console.log(error);
});
this.tutorialService.getAllPublic().subscribe(
(data : Tutorial[]) =>{
this.tutorials = data;
console.log(data);
},
error => {
console.log(error);
}
);
}
}
@@ -1,33 +1,57 @@
import { HTTP_INTERCEPTORS, HttpEvent } from '@angular/common/http';
import {HTTP_INTERCEPTORS, HttpErrorResponse, HttpEvent} from '@angular/common/http';
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpHandler, HttpRequest } from '@angular/common/http';
import { TokenStorageService } from '../services/token-storage.service';
import { Observable } from 'rxjs';
import {catchError, Observable, throwError} from 'rxjs';
import {EventBusService} from '../_shared/event-bus.service';
import {EventData} from '../_shared/event.class';
import {environment} from '../../environments/environment';
const TOKEN_HEADER_KEY = 'Authorization'; // for Spring Boot back-end
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(private token: TokenStorageService) { }
private isRefreshing = false;
enviorment = environment;
constructor(private tokenStorageService: TokenStorageService, private eventBusService: EventBusService) { }
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
req = req.clone({
withCredentials: true,
});
return next.handle(req);
return next.handle(req).pipe(
catchError((error) => {
if (
error instanceof HttpErrorResponse &&
!req.url.includes('auth/signin') &&
(error.status === 401
|| error.status === 500
|| error.status === 0
) /// must be only 401 without 500 and 0
) {
return this.handle401Error(req, next);
}
/*let authReq = req;
const token = this.token.getToken();
if (token != null) {
authReq = req.clone({ headers: req.headers.set(TOKEN_HEADER_KEY, 'Bearer ' + token) });
return throwError(() => error);
})
);
}
private handle401Error(request: HttpRequest<any>, next: HttpHandler) {
if (!this.isRefreshing) {
this.isRefreshing = true;
if (this.tokenStorageService.isLoggedIn()) {
this.eventBusService.emit(new EventData('logout', null));
}
}
return next.handle(authReq);*/
return next.handle(request);
}
}
@@ -27,16 +27,19 @@
</button>
<mat-menu #menu="matMenu">
@if (showAdminBoard) {
<button mat-menu-item routerLink="admin/welcome">
<mat-icon>admin_panel_settings</mat-icon>
Admin Panel
</button>
<button mat-menu-item routerLink="admin/welcome">
<mat-icon>admin_panel_settings</mat-icon>
Admin Panel
</button>
}
@if (showUserBoard) {
<button mat-menu-item routerLink="user/user-welcome">
<mat-icon>space_dashboard</mat-icon>
User tools
</button>
}
<button mat-menu-item routerLink="user/user-welcome">
<mat-icon>space_dashboard</mat-icon>
User tools
</button>
<button mat-menu-item routerLink="profile">
<mat-icon>person</mat-icon>
Profile
@@ -61,6 +61,7 @@ export class MainComponent implements OnInit{
isLoggedIn = false;
showAdminBoard = false;
showModeratorBoard = false;
showUserBoard = false;
username?: string;
eventBusSub?: Subscription;
@@ -74,12 +75,9 @@ export class MainComponent implements OnInit{
constructor(private router: Router,private http: HttpClient) {
}
goToHome() {
this.router.navigate(['main/home']);
}
ngOnInit(): void {
this.isLoggedIn = this.storageService.isLoggedIn();
this.refreshToolbar();
@@ -89,12 +87,14 @@ export class MainComponent implements OnInit{
}
refreshToolbar() {
this.isLoggedIn = this.storageService.isLoggedIn();
if (this.isLoggedIn) {
const user = this.storageService.getUser();
this.roles = user.roles;
this.showAdminBoard = true;//this.roles.includes('ROLE_ADMIN');
this.showAdminBoard = this.roles.includes('ROLE_ADMIN');
this.showModeratorBoard = this.roles.includes('ROLE_MODERATOR');
this.showUserBoard = this.roles.includes('ROLE_USER');
this.username = user.username;
}
@@ -4,7 +4,7 @@ import { Observable } from 'rxjs';
import { Tutorial } from '../models/tutorial.model';
import {text} from 'node:stream/consumers';
const baseUrl = 'http://localhost:8080/api/tutorials';
const baseUrl = 'http://localhost:8080/api/public/tutorials';
@Injectable({
providedIn: 'root'
@@ -13,31 +13,31 @@ export class TutorialService {
constructor(private http: HttpClient) { }
getAll(): Observable<Tutorial[]> {
getAllPublic(): Observable<Tutorial[]> {
return this.http.get<Tutorial[]>(baseUrl);
}
get(id: any): Observable<Tutorial> {
return this.http.get(`${baseUrl}/${id}`);
}
create(data: any): Observable<any> {
return this.http.post(baseUrl, data);
}
update(id: any, data: any): Observable<any> {
return this.http.put(`${baseUrl}/${id}`, data);
}
delete(id: any): Observable<any> {
return this.http.delete(`${baseUrl}/${id}`);
}
deleteAll(): Observable<any> {
return this.http.delete(baseUrl);
}
//
// get(id: any): Observable<Tutorial> {
// return this.http.get(`${baseUrl}/${id}`);
// }
//
// create(data: any): Observable<any> {
// return this.http.post(baseUrl, data);
// }
//
//
// update(id: any, data: any): Observable<any> {
// return this.http.put(`${baseUrl}/${id}`, data);
// }
//
// delete(id: any): Observable<any> {
// return this.http.delete(`${baseUrl}/${id}`);
// }
//
// deleteAll(): Observable<any> {
// return this.http.delete(baseUrl);
// }
findByTitle(title: any): Observable<Tutorial[]> {
return this.http.get<Tutorial[]>(`${baseUrl}?title=${title}`);
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { TutorialAddComponent } from './tutorial-add.component';
describe('TutorialAddComponent', () => {
let component: TutorialAddComponent;
let fixture: ComponentFixture<TutorialAddComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [TutorialAddComponent]
})
.compileComponents();
fixture = TestBed.createComponent(TutorialAddComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,11 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-tutorial-add.component',
imports: [],
templateUrl: './tutorial-add.component.html',
styleUrl: './tutorial-add.component.scss'
})
export class TutorialAddComponent {
}
@@ -1,4 +1,7 @@
<p>tutorials-list.component works!</p>
<button mat-raised-button routerLink="../tutorial-add">Add tutorial</button>
<mat-list role="list">
@for (tutorial of tutorials; track tutorial) {
<!--
@@ -1,4 +1,4 @@
import {Component, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
import {Component, CUSTOM_ELEMENTS_SCHEMA, OnInit} from '@angular/core';
import {MatList, MatListItem} from '@angular/material/list';
import {Tutorial} from '../../models/tutorial.model';
import {TutorialService} from '../../services/tutorial.service';
@@ -6,6 +6,11 @@ import {UserApiService} from '../user-api.service';
import {MatButton} from '@angular/material/button';
import {MatLine, MatOption} from '@angular/material/core';
import {MatIcon} from '@angular/material/icon';
import {TokenStorageService} from '../../services/token-storage.service';
import {EventBusService} from '../../_shared/event-bus.service';
import {EventData} from '../../_shared/event.class';
import {Router, RouterLink} from '@angular/router';
import {AuthService} from '../../services/auth.service';
@Component({
selector: 'app-tutorials-list.component',
@@ -15,16 +20,27 @@ import {MatIcon} from '@angular/material/icon';
MatLine,
MatButton,
MatIcon,
MatOption
MatOption,
RouterLink
],
schemas:[CUSTOM_ELEMENTS_SCHEMA],
templateUrl: './tutorials-list.component.html',
styleUrl: './tutorials-list.component.scss'
})
export class TutorialsListComponent {
export class TutorialsListComponent implements OnInit {
tutorials?: Tutorial[];
constructor(private userApiService: UserApiService) {
constructor(private userApiService: UserApiService,
private storageService: TokenStorageService,
private eventBusService: EventBusService,
private router: Router, private authService: AuthService) {
}
ngOnInit(): void {
this.eventBusService.on('logout', () => {
this.logout();
})
this.retrieveTutorials();
}
@@ -37,10 +53,38 @@ export class TutorialsListComponent {
},
error => {
console.log(error);
if (
(
error.status === 401
|| error.status === 500
|| error.status === 0
)
&& this.storageService.isLoggedIn()
) {
this.eventBusService.emit(new EventData('logout', null));
}
});
}
deleteOption($event: MouseEvent, option: any) {
}
logout(): void {
this.authService.logout().subscribe({
next: res => {
console.log(res);
this.storageService.clean();
//window.location.reload();
this.router.navigate(['main/generate-image']).then(() => {
window.location.reload();
})
},
error: err => {
console.log(err);
}
});
}
}
@@ -14,6 +14,6 @@ export class UserApiService {
}
getUserAllTutorials(): Observable<Tutorial[]> {
return this.http.get<Tutorial[]>(`${this.baseUrl}/tutorials`);
return this.http.get<Tutorial[]>(`${this.baseUrl}/tutorials`,{withCredentials: true});
}
}
@@ -41,7 +41,9 @@ export class UserComponent implements OnInit {
ngOnInit(): void {
this.observer.observe(['(max-width: 800px)']).subscribe((screenSize) => {
this.isMobile = screenSize.matches;
});
})
}
toggleMenu() {
if(this.isMobile){
@@ -16,6 +16,10 @@ const USER_ROUTES: Routes = [
path: 'tutorials-list',
loadComponent: () => import('../user-module/tutorials-list.component/tutorials-list.component').then((c) => c.TutorialsListComponent)
},
{
path: 'tutorial-add',
loadComponent: () => import('../user-module/tutorial-add.component/tutorial-add.component').then((c) => c.TutorialAddComponent)
},
{
path: 'ai-models',
loadComponent: () => import('../user-module/ai-models.component/ai-models.component').then((c) => c.AiModelsComponent)