Merge pull request #39 from liosha84/development

Development
This commit is contained in:
liosha84
2025-07-06 20:29:54 +03:00
committed by GitHub
43 changed files with 500 additions and 336 deletions
+7 -1
View File
@@ -65,7 +65,13 @@
"development": { "development": {
"optimization": false, "optimization": false,
"extractLicenses": false, "extractLicenses": false,
"sourceMap": true "sourceMap": true,
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.development.ts"
}
]
} }
}, },
"defaultConfiguration": "production" "defaultConfiguration": "production"
-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;
}
+2
View File
@@ -18,9 +18,11 @@ import 'prismjs';
import 'prismjs/components/prism-typescript.min.js'; import 'prismjs/components/prism-typescript.min.js';
import 'prismjs/plugins/line-numbers/prism-line-numbers.js'; import 'prismjs/plugins/line-numbers/prism-line-numbers.js';
import 'prismjs/plugins/line-highlight/prism-line-highlight.js'; import 'prismjs/plugins/line-highlight/prism-line-highlight.js';
import {authInterceptorProviders} from './helpers/auth.interceptor';
export const appConfig: ApplicationConfig = { export const appConfig: ApplicationConfig = {
providers: [ providers: [
authInterceptorProviders,
provideBrowserGlobalErrorListeners(), provideBrowserGlobalErrorListeners(),
provideAnimations(), provideAnimations(),
provideZoneChangeDetection({ eventCoalescing: true }), provideZoneChangeDetection({ eventCoalescing: true }),
+3 -1
View File
@@ -12,5 +12,7 @@ import { RouterOutlet } from '@angular/router';
export class App { export class App {
protected title = 'jambotron-ui'; protected title = 'jambotron-ui';
constructor() { } constructor() {
}
} }
@@ -74,7 +74,7 @@ const language = 'typescript';
description: this.tutorial.description description: this.tutorial.description
}; };
this.tutorialService.create(data) /* this.tutorialService.create(data)
.subscribe( .subscribe(
response => { response => {
console.log(response); console.log(response);
@@ -82,7 +82,7 @@ const language = 'typescript';
}, },
error => { error => {
console.log(error); console.log(error);
}); });*/
} }
newTutorial(): void { newTutorial(): void {
@@ -36,7 +36,7 @@ export class TutorialDetailsComponent implements OnInit {
} }
getTutorial(id: string): void { getTutorial(id: string): void {
this.tutorialService.get(id) /* this.tutorialService.get(id)
.subscribe( .subscribe(
data => { data => {
this.currentTutorial = data; this.currentTutorial = data;
@@ -44,7 +44,7 @@ export class TutorialDetailsComponent implements OnInit {
}, },
error => { error => {
console.log(error); console.log(error);
}); });*/
} }
updatePublished(status: boolean): void { updatePublished(status: boolean): void {
@@ -56,7 +56,7 @@ export class TutorialDetailsComponent implements OnInit {
this.message = ''; this.message = '';
this.tutorialService.update(this.currentTutorial.id, data) /* this.tutorialService.update(this.currentTutorial.id, data)
.subscribe( .subscribe(
response => { response => {
this.currentTutorial.published = status; this.currentTutorial.published = status;
@@ -65,11 +65,11 @@ export class TutorialDetailsComponent implements OnInit {
}, },
error => { error => {
console.log(error); console.log(error);
}); });*/
} }
updateTutorial(): void { updateTutorial(): void {
this.message = ''; /* this.message = '';
this.tutorialService.update(this.currentTutorial.id, this.currentTutorial) this.tutorialService.update(this.currentTutorial.id, this.currentTutorial)
.subscribe( .subscribe(
@@ -79,11 +79,11 @@ export class TutorialDetailsComponent implements OnInit {
}, },
error => { error => {
console.log(error); console.log(error);
}); });*/
} }
deleteTutorial(): void { deleteTutorial(): void {
this.tutorialService.delete(this.currentTutorial.id) /* this.tutorialService.delete(this.currentTutorial.id)
.subscribe( .subscribe(
response => { response => {
console.log(response); console.log(response);
@@ -91,6 +91,6 @@ export class TutorialDetailsComponent implements OnInit {
}, },
error => { error => {
console.log(error); console.log(error);
}); });*/
} }
} }
@@ -31,9 +31,9 @@ export class TutorialsListComponent implements OnInit {
} }
retrieveTutorials(): void { retrieveTutorials(): void {
this.tutorialService.getAll() this.tutorialService.getAllPublic()
.subscribe( .subscribe(
data => { (data: Tutorial[] ) => {
this.tutorials = data; this.tutorials = data;
console.log(data); console.log(data);
}, },
@@ -54,7 +54,7 @@ export class TutorialsListComponent implements OnInit {
} }
removeAllTutorials(): void { removeAllTutorials(): void {
this.tutorialService.deleteAll() /* this.tutorialService.deleteAll()
.subscribe( .subscribe(
response => { response => {
console.log(response); console.log(response);
@@ -62,7 +62,7 @@ export class TutorialsListComponent implements OnInit {
}, },
error => { error => {
console.log(error); console.log(error);
}); });*/
} }
searchTitle(): void { searchTitle(): void {
@@ -3,6 +3,9 @@ import {Tutorial} from '../../models/tutorial.model';
import {TutorialService} from '../../services/tutorial.service'; import {TutorialService} from '../../services/tutorial.service';
import {MatCard, MatCardContent, MatCardHeader} from '@angular/material/card'; import {MatCard, MatCardContent, MatCardHeader} from '@angular/material/card';
import {MarkdownComponent} from 'ngx-markdown'; import {MarkdownComponent} from 'ngx-markdown';
import {authInterceptorProviders} from '../../helpers/auth.interceptor';
import {HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi} from '@angular/common/http';
import {CustomHttpInterceptor} from '../../helpers/custom-http-interceptor';
@Component({ @Component({
selector: 'app-tutorials.component', selector: 'app-tutorials.component',
@@ -14,7 +17,12 @@ import {MarkdownComponent} from 'ngx-markdown';
], ],
templateUrl: './tutorials.component.html', templateUrl: './tutorials.component.html',
styleUrl: './tutorials.component.scss', styleUrl: './tutorials.component.scss',
schemas: [CUSTOM_ELEMENTS_SCHEMA] schemas: [CUSTOM_ELEMENTS_SCHEMA],
providers: [authInterceptorProviders,{
provide: HTTP_INTERCEPTORS,
useClass: CustomHttpInterceptor,
multi: true
}],
}) })
export class TutorialsComponent { export class TutorialsComponent {
tutorials?: Tutorial[]; tutorials?: Tutorial[];
@@ -28,14 +36,14 @@ export class TutorialsComponent {
// } // }
retrieveTutorials(): void { retrieveTutorials(): void {
this.tutorialService.getAll() this.tutorialService.getAllPublic().subscribe(
.subscribe( (data : Tutorial[]) =>{
data => { this.tutorials = data;
this.tutorials = data; console.log(data);
console.log(data); },
}, error => {
error => { console.log(error);
console.log(error); }
}); );
} }
} }
@@ -1,23 +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 { Injectable } from '@angular/core';
import { HttpInterceptor, HttpHandler, HttpRequest } from '@angular/common/http'; import { HttpInterceptor, HttpHandler, HttpRequest } from '@angular/common/http';
import { TokenStorageService } from '../services/token-storage.service'; 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 const TOKEN_HEADER_KEY = 'Authorization'; // for Spring Boot back-end
@Injectable() @Injectable()
export class AuthInterceptor implements HttpInterceptor { 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>> { intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
let authReq = req;
const token = this.token.getToken(); req = req.clone({
if (token != null) { withCredentials: true,
authReq = req.clone({ headers: req.headers.set(TOKEN_HEADER_KEY, 'Bearer ' + token) }); });
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);
}
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> </button>
<mat-menu #menu="matMenu"> <mat-menu #menu="matMenu">
@if (showAdminBoard) { @if (showAdminBoard) {
<button mat-menu-item routerLink="admin/welcome"> <button mat-menu-item routerLink="admin/welcome">
<mat-icon>admin_panel_settings</mat-icon> <mat-icon>admin_panel_settings</mat-icon>
Admin Panel Admin Panel
</button> </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"> <button mat-menu-item routerLink="profile">
<mat-icon>person</mat-icon> <mat-icon>person</mat-icon>
Profile Profile
@@ -18,6 +18,9 @@ import {MatMenu, MatMenuItem, MatMenuTrigger} from '@angular/material/menu';
import {MatTooltip} from '@angular/material/tooltip'; import {MatTooltip} from '@angular/material/tooltip';
import {DialogSignupComponent} from '../../components/dialog-signup.component/dialog-signup.component'; import {DialogSignupComponent} from '../../components/dialog-signup.component/dialog-signup.component';
import {MatLabel} from '@angular/material/input'; import {MatLabel} from '@angular/material/input';
import {HttpClient} from '@angular/common/http';
import { environment } from '../../../environments/environment';
@Component({ @Component({
selector: 'app-main.component', selector: 'app-main.component',
@@ -58,24 +61,23 @@ export class MainComponent implements OnInit{
isLoggedIn = false; isLoggedIn = false;
showAdminBoard = false; showAdminBoard = false;
showModeratorBoard = false; showModeratorBoard = false;
showUserBoard = false;
username?: string; username?: string;
eventBusSub?: Subscription; eventBusSub?: Subscription;
private environment = environment;
private storageService: TokenStorageService = inject(TokenStorageService); private storageService: TokenStorageService = inject(TokenStorageService);
private authService: AuthService = inject(AuthService); private authService: AuthService = inject(AuthService);
private eventBusService: EventBusService = inject(EventBusService); private eventBusService: EventBusService = inject(EventBusService);
constructor(private router: Router) { constructor(private router: Router,private http: HttpClient) {
} }
goToHome() {
this.router.navigate(['main/home']);
}
ngOnInit(): void { ngOnInit(): void {
this.isLoggedIn = this.storageService.isLoggedIn();
this.refreshToolbar(); this.refreshToolbar();
@@ -85,12 +87,14 @@ export class MainComponent implements OnInit{
} }
refreshToolbar() { refreshToolbar() {
this.isLoggedIn = this.storageService.isLoggedIn();
if (this.isLoggedIn) { if (this.isLoggedIn) {
const user = this.storageService.getUser(); const user = this.storageService.getUser();
this.roles = user.roles; 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.showModeratorBoard = this.roles.includes('ROLE_MODERATOR');
this.showUserBoard = this.roles.includes('ROLE_USER');
this.username = user.username; this.username = user.username;
} }
@@ -136,6 +140,8 @@ export class MainComponent implements OnInit{
console.log(res); console.log(res);
this.storageService.clean(); this.storageService.clean();
//window.location.reload();
this.router.navigate([environment.default_page]);
window.location.reload(); window.location.reload();
}, },
error: err => { error: err => {
@@ -167,8 +173,9 @@ export class MainComponent implements OnInit{
this.dialogLoginData = result; this.dialogLoginData = result;
this.authService.login(this.dialogLoginData.username, this.dialogLoginData.password).subscribe( this.authService.login(this.dialogLoginData.username, this.dialogLoginData.password).subscribe(
data => { data => {
this.storageService.saveToken(data.accessToken);
//this.storageService.saveToken(data.token);
this.storageService.saveUser(data); this.storageService.saveUser(data);
this.isLoggedIn = true; this.isLoggedIn = true;
@@ -4,11 +4,12 @@ import {mainRouting} from './main.routing';
import {NgModule} from '@angular/core'; import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common'; import {CommonModule} from '@angular/common';
import {HTTP_INTERCEPTORS} from '@angular/common/http'; import {HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi} from '@angular/common/http';
import {CustomHttpInterceptor} from '../helpers/custom-http-interceptor'; import {CustomHttpInterceptor} from '../helpers/custom-http-interceptor';
import {AngularImageViewerModule} from '@hreimer/angular-image-viewer'; import {AngularImageViewerModule} from '@hreimer/angular-image-viewer';
import {BrowserModule} from '@angular/platform-browser'; import {BrowserModule} from '@angular/platform-browser';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {authInterceptorProviders} from '../helpers/auth.interceptor';
@NgModule({ @NgModule({
declarations: [ declarations: [
@@ -22,7 +23,7 @@ import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
], ],
exports: [RouterModule], exports: [RouterModule],
providers: [{ providers: [authInterceptorProviders,provideHttpClient(withInterceptorsFromDi()),{
provide: HTTP_INTERCEPTORS, provide: HTTP_INTERCEPTORS,
useClass: CustomHttpInterceptor, useClass: CustomHttpInterceptor,
multi: true multi: true
@@ -19,6 +19,8 @@ export class AuthService {
username, username,
password password
}, httpOptions); }, httpOptions);
} }
register(username: string, email: string, password: string): Observable<any> { register(username: string, email: string, password: string): Observable<any> {
@@ -2,8 +2,9 @@ import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { Tutorial } from '../models/tutorial.model'; 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({ @Injectable({
providedIn: 'root' providedIn: 'root'
@@ -12,29 +13,31 @@ export class TutorialService {
constructor(private http: HttpClient) { } constructor(private http: HttpClient) { }
getAll(): Observable<Tutorial[]> { getAllPublic(): Observable<Tutorial[]> {
return this.http.get<Tutorial[]>(baseUrl); return this.http.get<Tutorial[]>(baseUrl);
} }
get(id: any): Observable<Tutorial> { //
return this.http.get(`${baseUrl}/${id}`); // get(id: any): Observable<Tutorial> {
} // return this.http.get(`${baseUrl}/${id}`);
// }
create(data: any): Observable<any> { //
return this.http.post(baseUrl, data); // 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); //
} // 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}`); //
} // delete(id: any): Observable<any> {
// return this.http.delete(`${baseUrl}/${id}`);
deleteAll(): Observable<any> { // }
return this.http.delete(baseUrl); //
} // deleteAll(): Observable<any> {
// return this.http.delete(baseUrl);
// }
findByTitle(title: any): Observable<Tutorial[]> { findByTitle(title: any): Observable<Tutorial[]> {
return this.http.get<Tutorial[]>(`${baseUrl}?title=${title}`); return this.http.get<Tutorial[]>(`${baseUrl}?title=${title}`);
@@ -0,0 +1 @@
<p>tutorial-add.component works!</p>
@@ -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,6 +1,29 @@
<p>tutorials-list.component works!</p> <p>tutorials-list.component works!</p>
<button mat-raised-button routerLink="../tutorial-add">Add tutorial</button>
<mat-list role="list"> <mat-list role="list">
<mat-list-item role="listitem">Item 1</mat-list-item> @for (tutorial of tutorials; track tutorial) {
<mat-list-item role="listitem">Item 2</mat-list-item> <!--
<mat-list-item role="listitem">Item 3</mat-list-item> <mat-list-item role="listitem">
{{tutorial.title}}
<span class="spacer"></span>
<button mat-button>
<mat-icon>edit</mat-icon>
</button>
<button mat-button>
<mat-icon>delete</mat-icon>
</button>
</mat-list-item>-->
<mat-option >
<div style="display:flex; justify-content: space-between">
<span>{{tutorial.title}}</span>
<span></span>
<span (click)="deleteOption($event, tutorial)">X</span>
</div>
</mat-option>
}
</mat-list> </mat-list>
@@ -0,0 +1,3 @@
.spacer{
flex: 1 1 auto;
}
@@ -1,15 +1,90 @@
import { Component } from '@angular/core'; import {Component, CUSTOM_ELEMENTS_SCHEMA, OnInit} from '@angular/core';
import {MatList, MatListItem} from '@angular/material/list'; import {MatList, MatListItem} from '@angular/material/list';
import {Tutorial} from '../../models/tutorial.model';
import {TutorialService} from '../../services/tutorial.service';
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({ @Component({
selector: 'app-tutorials-list.component', selector: 'app-tutorials-list.component',
imports: [ imports: [
MatList, MatList,
MatListItem MatListItem,
MatLine,
MatButton,
MatIcon,
MatOption,
RouterLink
], ],
schemas:[CUSTOM_ELEMENTS_SCHEMA],
templateUrl: './tutorials-list.component.html', templateUrl: './tutorials-list.component.html',
styleUrl: './tutorials-list.component.scss' styleUrl: './tutorials-list.component.scss'
}) })
export class TutorialsListComponent { export class TutorialsListComponent implements OnInit {
tutorials?: Tutorial[];
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();
}
retrieveTutorials(): void {
this.userApiService.getUserAllTutorials()
.subscribe(
data => {
this.tutorials = data;
console.log(data);
},
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);
}
});
}
} }
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { UserApiService } from './user-api.service';
describe('UserApiService', () => {
let service: UserApiService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(UserApiService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
@@ -0,0 +1,19 @@
import { Injectable } from '@angular/core';
import {Observable} from 'rxjs';
import {Tutorial} from '../models/tutorial.model';
import {HttpClient} from '@angular/common/http';
@Injectable({
providedIn: 'root'
})
export class UserApiService {
baseUrl = 'http://localhost:8080/api/user';
constructor(private http: HttpClient) {
}
getUserAllTutorials(): Observable<Tutorial[]> {
return this.http.get<Tutorial[]>(`${this.baseUrl}/tutorials`,{withCredentials: true});
}
}
@@ -41,7 +41,9 @@ export class UserComponent implements OnInit {
ngOnInit(): void { ngOnInit(): void {
this.observer.observe(['(max-width: 800px)']).subscribe((screenSize) => { this.observer.observe(['(max-width: 800px)']).subscribe((screenSize) => {
this.isMobile = screenSize.matches; this.isMobile = screenSize.matches;
}); })
} }
toggleMenu() { toggleMenu() {
if(this.isMobile){ if(this.isMobile){
@@ -16,6 +16,10 @@ const USER_ROUTES: Routes = [
path: 'tutorials-list', path: 'tutorials-list',
loadComponent: () => import('../user-module/tutorials-list.component/tutorials-list.component').then((c) => c.TutorialsListComponent) 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', path: 'ai-models',
loadComponent: () => import('../user-module/ai-models.component/ai-models.component').then((c) => c.AiModelsComponent) loadComponent: () => import('../user-module/ai-models.component/ai-models.component').then((c) => c.AiModelsComponent)
@@ -0,0 +1,3 @@
export const environment = {
default_page: 'main/generate-image'
};
@@ -0,0 +1,3 @@
export const environment = {
default_page: 'main/generate-image'
};
+3
View File
@@ -62,6 +62,9 @@ if (isMainModule(import.meta.url)) {
}); });
} }
/** /**
* Request handler used by the Angular CLI (for dev-server and during build) or Firebase Cloud Functions. * Request handler used by the Angular CLI (for dev-server and during build) or Firebase Cloud Functions.
*/ */
@@ -12,7 +12,11 @@ import org.springframework.web.bind.annotation.*;
import java.util.List; import java.util.List;
@CrossOrigin(origins = "http://localhost:4200, https://pony-sincere-chimp.ngrok-free.app/", maxAge = 3600, allowCredentials="true") @CrossOrigin(origins = "http://localhost:4200, https://pony-sincere-chimp.ngrok-free.app/",
maxAge = 3600,
allowCredentials="true",
allowedHeaders = {"Content-Type", "Authorization", "X-Requested-With"}
)
@RestController @RestController
@RequestMapping("/api/zhipuai") @RequestMapping("/api/zhipuai")
public class ImageController { public class ImageController {
@@ -13,6 +13,8 @@ import com.jambotronGroup.jambotron.repository.UserRepository;
import com.jambotronGroup.jambotron.security.jwt.JwtUtils; import com.jambotronGroup.jambotron.security.jwt.JwtUtils;
import com.jambotronGroup.jambotron.security.services.UserDetailsImpl; import com.jambotronGroup.jambotron.security.services.UserDetailsImpl;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseCookie; import org.springframework.http.ResponseCookie;
@@ -34,6 +36,8 @@ import java.util.stream.Collectors;
@RestController @RestController
@RequestMapping("/api/auth") @RequestMapping("/api/auth")
public class AuthController { public class AuthController {
private static final Logger logger = LoggerFactory.getLogger(AuthController.class);
@Autowired @Autowired
AuthenticationManager authenticationManager; AuthenticationManager authenticationManager;
@@ -65,12 +69,16 @@ public class AuthController {
.map(item -> item.getAuthority()) .map(item -> item.getAuthority())
.collect(Collectors.toList()); .collect(Collectors.toList());
logger.info("User {} authenticated successfully with roles: {}", userDetails.getUsername(), roles);
return ResponseEntity.ok().header(HttpHeaders.SET_COOKIE, jwtCookie.toString()) return ResponseEntity.ok().header(HttpHeaders.SET_COOKIE, jwtCookie.toString())
.body(new UserInfoResponse( .body(new UserInfoResponse(
userDetails.getId(), userDetails.getId(),
userDetails.getUsername(), userDetails.getUsername(),
userDetails.getEmail(), userDetails.getEmail(),
roles)); roles,
jwtCookie.toString()
));
} }
@PostMapping("/signup") @PostMapping("/signup")
@@ -2,10 +2,20 @@ package com.jambotronGroup.jambotron.controllers;
import com.jambotronGroup.jambotron.model.Tutorial; import com.jambotronGroup.jambotron.model.Tutorial;
import com.jambotronGroup.jambotron.model.User;
import com.jambotronGroup.jambotron.repository.TutorialRepository; import com.jambotronGroup.jambotron.repository.TutorialRepository;
import com.jambotronGroup.jambotron.repository.UserRepository;
import com.jambotronGroup.jambotron.security.AuthenticationFacade;
import com.jambotronGroup.jambotron.security.services.UserDetailsImpl;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.util.ArrayList; import java.util.ArrayList;
@@ -14,23 +24,77 @@ import java.util.Optional;
@CrossOrigin(origins = "${app.origin}", maxAge = 3600, allowCredentials="true") @CrossOrigin(origins = "http://localhost:4200", maxAge = 3600, allowCredentials="true")
@RestController @RestController
@RequestMapping("/api") @RequestMapping("/api")
public class TutorialController { public class TutorialController {
@Autowired
AuthenticationFacade authenticationFacade;
@Autowired
UserRepository userRepository;
@Autowired @Autowired
TutorialRepository tutorialRepository; TutorialRepository tutorialRepository;
@GetMapping("/tutorials") @GetMapping("/user/tutorials")
public ResponseEntity<List<Tutorial>> getUserTutorials(@RequestParam(required = false) String title) {
try {
List<Tutorial> tutorials = new ArrayList<Tutorial>();
User user = userRepository.findById(authenticationFacade.getUserDetails().getId()).get();
if(title == null){
// If no title is provided, return all tutorials for the user
tutorialRepository.findByUserId(user.getId()).forEach(tutorials::add);
} else {
// If a title is provided, filter tutorials by user and title
tutorialRepository.findByUserIdAndTitle(user.getId(), title).forEach(tutorials::add);
}
if (tutorials.isEmpty()) {
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
return new ResponseEntity<>(tutorials, HttpStatus.OK);
} catch (Exception e) {
e.printStackTrace();
return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@GetMapping("/public/tutorials")
public ResponseEntity<List<Tutorial>> getAllTutorials(@RequestParam(required = false) String title) { public ResponseEntity<List<Tutorial>> getAllTutorials(@RequestParam(required = false) String title) {
try { try {
List<Tutorial> tutorials = new ArrayList<Tutorial>(); List<Tutorial> tutorials = new ArrayList<Tutorial>();
if (title == null) /* Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
tutorialRepository.findAll().forEach(tutorials::add); UserDetails userDetails = (UserDetails) authentication.getPrincipal();
else String name = authenticationFacade.getAuthentication().getName();
tutorialRepository.findByTitleContaining(title).forEach(tutorials::add);
User user = userRepository.findById(((UserDetailsImpl)userDetails).getId()).get();
if (user.getRoles().stream().anyMatch(role -> role.getName().name().equals("ROLE_ADMIN"))) {*/
/*
if (title == null)
tutorialRepository.findAll().forEach(tutorials::add);
else
tutorialRepository.findByTitleContaining(title).forEach(tutorials::add);
*/
/* } else {
// If the user is not an admin, filter tutorials by user
tutorialRepository.findByUserId(user.getId()).forEach(tutorials::add);
if (tutorials.isEmpty()) {
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
return new ResponseEntity<>(tutorials, HttpStatus.OK);
}*/
//all published tutorials without authentication
tutorialRepository.findByPublished(true).forEach(tutorials::add);
if (tutorials.isEmpty()) { if (tutorials.isEmpty()) {
return new ResponseEntity<>(HttpStatus.NO_CONTENT); return new ResponseEntity<>(HttpStatus.NO_CONTENT);
@@ -55,9 +119,15 @@ public class TutorialController {
@PostMapping("/tutorials") @PostMapping("/tutorials")
public ResponseEntity<Tutorial> createTutorial(@RequestBody Tutorial tutorial) { public ResponseEntity<Tutorial> createTutorial(@RequestBody Tutorial tutorial) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
User user = userRepository.findById(((UserDetailsImpl)userDetails).getId()).get();
try { try {
Tutorial _tutorial = tutorialRepository Tutorial _tutorial = tutorialRepository
.save(new Tutorial(tutorial.getTitle(), tutorial.getDescription(), false)); .save(new Tutorial(tutorial.getTitle(), tutorial.getDescription(), false, user));
return new ResponseEntity<>(_tutorial, HttpStatus.CREATED); return new ResponseEntity<>(_tutorial, HttpStatus.CREATED);
} catch (Exception e) { } catch (Exception e) {
return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR); return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
@@ -21,14 +21,19 @@ public class Tutorial {
@Column(name = "published") @Column(name = "published")
private boolean published; private boolean published;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "userID", nullable = false)
private User user;
public Tutorial() { public Tutorial() {
} }
public Tutorial(String title, String description, boolean published) { public Tutorial(String title, String description, boolean published, User user) {
this.title = title; this.title = title;
this.description = description; this.description = description;
this.published = published; this.published = published;
this.user = user;
} }
public long getId() { public long getId() {
@@ -10,11 +10,14 @@ public class UserInfoResponse {
private String email; private String email;
private List<String> roles; private List<String> roles;
public UserInfoResponse(Long id, String username, String email, List<String> roles) { private String token;
public UserInfoResponse(Long id, String username, String email, List<String> roles, String token) {
this.id = id; this.id = id;
this.username = username; this.username = username;
this.email = email; this.email = email;
this.roles = roles; this.roles = roles;
this.token = token;
} }
public Long getId() { public Long getId() {
@@ -44,4 +47,12 @@ public class UserInfoResponse {
public List<String> getRoles() { public List<String> getRoles() {
return roles; return roles;
} }
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
} }
@@ -10,6 +10,9 @@ import java.util.List;
@Repository @Repository
public interface TutorialRepository extends JpaRepository<Tutorial, Long> { public interface TutorialRepository extends JpaRepository<Tutorial, Long> {
List<Tutorial> findByUserId(Long userId);
List<Tutorial> findByUserIdAndTitle(Long userId, String title);
List<Tutorial> findByPublished(boolean published); List<Tutorial> findByPublished(boolean published);
List<Tutorial> findByTitleContaining(String title); List<Tutorial> findByTitleContaining(String title);
} }
@@ -9,6 +9,8 @@ import java.util.Optional;
@Repository @Repository
public interface UserRepository extends JpaRepository<User, Long> { public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username); Optional<User> findByUsername(String username);
Boolean existsByUsername(String username); Boolean existsByUsername(String username);
@@ -0,0 +1,29 @@
package com.jambotronGroup.jambotron.security;
import com.jambotronGroup.jambotron.security.services.UserDetailsImpl;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
@Component
public class AuthenticationFacade implements IAuthenticationFacade {
@Override
public Authentication getAuthentication() {
return SecurityContextHolder.getContext().getAuthentication();
}
@Override
public UserDetailsImpl getUserDetails() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
return (UserDetailsImpl) userDetails;
}
}
@@ -0,0 +1,10 @@
package com.jambotronGroup.jambotron.security;
import com.jambotronGroup.jambotron.security.services.UserDetailsImpl;
import org.springframework.security.core.Authentication;
public interface IAuthenticationFacade {
Authentication getAuthentication();
UserDetailsImpl getUserDetails();
}
@@ -43,10 +43,7 @@ public class WebSecurityConfig implements WebMvcConfigurer {// extends WebSecuri
// authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); // authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
// } // }
@Override
public void addCorsMappings(CorsRegistry registry) {
// Do not add any mappings to enable complete disabling of CORS.
}
@Bean @Bean
public DaoAuthenticationProvider authenticationProvider() { public DaoAuthenticationProvider authenticationProvider() {
@@ -106,7 +103,7 @@ public class WebSecurityConfig implements WebMvcConfigurer {// extends WebSecuri
@Bean @Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf(csrf -> csrf.disable()) http.csrf(csrf -> csrf.disable()).cors(cors -> cors.disable())
.exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler)) .exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler))
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> .authorizeHttpRequests(auth ->
@@ -118,6 +115,7 @@ public class WebSecurityConfig implements WebMvcConfigurer {// extends WebSecuri
.requestMatchers("/main/**").permitAll() .requestMatchers("/main/**").permitAll()
.requestMatchers("/api/test/**").permitAll() .requestMatchers("/api/test/**").permitAll()
.requestMatchers("/api/tutorials").permitAll() .requestMatchers("/api/tutorials").permitAll()
.requestMatchers("/api/zhipuai/image/**").permitAll() .requestMatchers("/api/zhipuai/image/**").permitAll()
@@ -132,6 +130,12 @@ public class WebSecurityConfig implements WebMvcConfigurer {// extends WebSecuri
.requestMatchers("/api/settings").permitAll() .requestMatchers("/api/settings").permitAll()
.requestMatchers("/api/system/**").permitAll() .requestMatchers("/api/system/**").permitAll()
// Allow public access to tutorials (without login)
.requestMatchers("/api/public/tutorials").permitAll()
.requestMatchers("/api/user").permitAll()
.anyRequest().authenticated() .anyRequest().authenticated()
); );
@@ -26,6 +26,7 @@ public class AuthEntryPointJwt implements AuthenticationEntryPoint {
throws IOException, ServletException { throws IOException, ServletException {
logger.error("Unauthorized error: {}", authException.getMessage()); logger.error("Unauthorized error: {}", authException.getMessage());
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Error: Unauthorized");
response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+1 -1
View File
@@ -20,7 +20,7 @@ spring.jpa.show-sql=true
# App Properties # App Properties
app.jwtSecret= ======================spring=back==================== app.jwtSecret= ======================spring=back====================
app.jwtExpirationMs= 30000 app.jwtExpirationMs= 800000
app.jwtCookieName=springangularts app.jwtCookieName=springangularts
app.origin=http://localhost:4200 app.origin=http://localhost:4200