diff --git a/jambotron-ui/angular.json b/jambotron-ui/angular.json index 8624583..ba195ca 100644 --- a/jambotron-ui/angular.json +++ b/jambotron-ui/angular.json @@ -65,7 +65,13 @@ "development": { "optimization": false, "extractLicenses": false, - "sourceMap": true + "sourceMap": true, + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.development.ts" + } + ] } }, "defaultConfiguration": "production" diff --git a/jambotron-ui/src/app/app.component.html b/jambotron-ui/src/app/app.component.html deleted file mode 100644 index 85204c4..0000000 --- a/jambotron-ui/src/app/app.component.html +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - @if (showAdminBoard) { - - } - - @if (showAdminBoard) { - - } - - - - - - - - - - - - - -
- -
- diff --git a/jambotron-ui/src/app/app.component.scss b/jambotron-ui/src/app/app.component.scss deleted file mode 100644 index fee240c..0000000 --- a/jambotron-ui/src/app/app.component.scss +++ /dev/null @@ -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%; -} diff --git a/jambotron-ui/src/app/app.component.spec.ts b/jambotron-ui/src/app/app.component.spec.ts deleted file mode 100644 index 98d898f..0000000 --- a/jambotron-ui/src/app/app.component.spec.ts +++ /dev/null @@ -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!'); - }); -}); diff --git a/jambotron-ui/src/app/app.component.ts b/jambotron-ui/src/app/app.component.ts deleted file mode 100644 index b51a698..0000000 --- a/jambotron-ui/src/app/app.component.ts +++ /dev/null @@ -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; -} - diff --git a/jambotron-ui/src/app/app.config.ts b/jambotron-ui/src/app/app.config.ts index 6373f43..ffe0624 100644 --- a/jambotron-ui/src/app/app.config.ts +++ b/jambotron-ui/src/app/app.config.ts @@ -18,9 +18,11 @@ import 'prismjs'; import 'prismjs/components/prism-typescript.min.js'; import 'prismjs/plugins/line-numbers/prism-line-numbers.js'; import 'prismjs/plugins/line-highlight/prism-line-highlight.js'; +import {authInterceptorProviders} from './helpers/auth.interceptor'; export const appConfig: ApplicationConfig = { providers: [ + authInterceptorProviders, provideBrowserGlobalErrorListeners(), provideAnimations(), provideZoneChangeDetection({ eventCoalescing: true }), diff --git a/jambotron-ui/src/app/app.ts b/jambotron-ui/src/app/app.ts index 649c4a1..23348c4 100644 --- a/jambotron-ui/src/app/app.ts +++ b/jambotron-ui/src/app/app.ts @@ -12,5 +12,7 @@ import { RouterOutlet } from '@angular/router'; export class App { protected title = 'jambotron-ui'; - constructor() { } + constructor() { + + } } diff --git a/jambotron-ui/src/app/components/add-tutorial/add-tutorial.component.ts b/jambotron-ui/src/app/components/add-tutorial/add-tutorial.component.ts index 3fe0470..be8a111 100644 --- a/jambotron-ui/src/app/components/add-tutorial/add-tutorial.component.ts +++ b/jambotron-ui/src/app/components/add-tutorial/add-tutorial.component.ts @@ -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 { diff --git a/jambotron-ui/src/app/components/tutorial-details/tutorial-details.component.ts b/jambotron-ui/src/app/components/tutorial-details/tutorial-details.component.ts index 96a88d3..3991df1 100644 --- a/jambotron-ui/src/app/components/tutorial-details/tutorial-details.component.ts +++ b/jambotron-ui/src/app/components/tutorial-details/tutorial-details.component.ts @@ -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); - }); + });*/ } } diff --git a/jambotron-ui/src/app/components/tutorials-list/tutorials-list.component.ts b/jambotron-ui/src/app/components/tutorials-list/tutorials-list.component.ts index f1cdb3f..0442b48 100644 --- a/jambotron-ui/src/app/components/tutorials-list/tutorials-list.component.ts +++ b/jambotron-ui/src/app/components/tutorials-list/tutorials-list.component.ts @@ -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 { diff --git a/jambotron-ui/src/app/components/tutorials.component/tutorials.component.ts b/jambotron-ui/src/app/components/tutorials.component/tutorials.component.ts index 11913d6..d539f0d 100644 --- a/jambotron-ui/src/app/components/tutorials.component/tutorials.component.ts +++ b/jambotron-ui/src/app/components/tutorials.component/tutorials.component.ts @@ -3,6 +3,9 @@ import {Tutorial} from '../../models/tutorial.model'; import {TutorialService} from '../../services/tutorial.service'; import {MatCard, MatCardContent, MatCardHeader} from '@angular/material/card'; 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({ selector: 'app-tutorials.component', @@ -14,7 +17,12 @@ import {MarkdownComponent} from 'ngx-markdown'; ], templateUrl: './tutorials.component.html', styleUrl: './tutorials.component.scss', - schemas: [CUSTOM_ELEMENTS_SCHEMA] + schemas: [CUSTOM_ELEMENTS_SCHEMA], + providers: [authInterceptorProviders,{ + provide: HTTP_INTERCEPTORS, + useClass: CustomHttpInterceptor, + multi: true + }], }) export class TutorialsComponent { tutorials?: Tutorial[]; @@ -28,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); + } + ); } } diff --git a/jambotron-ui/src/app/helpers/auth.interceptor.ts b/jambotron-ui/src/app/helpers/auth.interceptor.ts index 47204e7..390b93c 100644 --- a/jambotron-ui/src/app/helpers/auth.interceptor.ts +++ b/jambotron-ui/src/app/helpers/auth.interceptor.ts @@ -1,26 +1,60 @@ -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, next: HttpHandler): Observable> { - let authReq = req; - const token = this.token.getToken(); - if (token != null) { - authReq = req.clone({ headers: req.headers.set(TOKEN_HEADER_KEY, 'Bearer ' + token) }); + + req = req.clone({ + withCredentials: true, + }); + + 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, 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); } } export const authInterceptorProviders = [ { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true } -]; \ No newline at end of file +]; diff --git a/jambotron-ui/src/app/main-module/main.component/main.component.html b/jambotron-ui/src/app/main-module/main.component/main.component.html index 19894b2..3f83962 100644 --- a/jambotron-ui/src/app/main-module/main.component/main.component.html +++ b/jambotron-ui/src/app/main-module/main.component/main.component.html @@ -27,16 +27,19 @@ @if (showAdminBoard) { - + + } + + @if (showUserBoard) { + } - + - Item 1 - Item 2 - Item 3 + @for (tutorial of tutorials; track tutorial) { + + +
+ {{tutorial.title}} + + X +
+
+ } +
diff --git a/jambotron-ui/src/app/user-module/tutorials-list.component/tutorials-list.component.scss b/jambotron-ui/src/app/user-module/tutorials-list.component/tutorials-list.component.scss index e69de29..d37713e 100644 --- a/jambotron-ui/src/app/user-module/tutorials-list.component/tutorials-list.component.scss +++ b/jambotron-ui/src/app/user-module/tutorials-list.component/tutorials-list.component.scss @@ -0,0 +1,3 @@ +.spacer{ + flex: 1 1 auto; +} diff --git a/jambotron-ui/src/app/user-module/tutorials-list.component/tutorials-list.component.ts b/jambotron-ui/src/app/user-module/tutorials-list.component/tutorials-list.component.ts index 6b34a98..26b88a2 100644 --- a/jambotron-ui/src/app/user-module/tutorials-list.component/tutorials-list.component.ts +++ b/jambotron-ui/src/app/user-module/tutorials-list.component/tutorials-list.component.ts @@ -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 {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({ selector: 'app-tutorials-list.component', imports: [ MatList, - MatListItem + MatListItem, + MatLine, + MatButton, + MatIcon, + 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, + 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); + } + }); + + } } diff --git a/jambotron-ui/src/app/user-module/user-api.service.spec.ts b/jambotron-ui/src/app/user-module/user-api.service.spec.ts new file mode 100644 index 0000000..17ce238 --- /dev/null +++ b/jambotron-ui/src/app/user-module/user-api.service.spec.ts @@ -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(); + }); +}); diff --git a/jambotron-ui/src/app/user-module/user-api.service.ts b/jambotron-ui/src/app/user-module/user-api.service.ts new file mode 100644 index 0000000..335f966 --- /dev/null +++ b/jambotron-ui/src/app/user-module/user-api.service.ts @@ -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 { + return this.http.get(`${this.baseUrl}/tutorials`,{withCredentials: true}); + } +} diff --git a/jambotron-ui/src/app/user-module/user.component/user.component.ts b/jambotron-ui/src/app/user-module/user.component/user.component.ts index b45bdb2..b76b805 100644 --- a/jambotron-ui/src/app/user-module/user.component/user.component.ts +++ b/jambotron-ui/src/app/user-module/user.component/user.component.ts @@ -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){ diff --git a/jambotron-ui/src/app/user-module/user.routing.ts b/jambotron-ui/src/app/user-module/user.routing.ts index c9f7228..cb542bb 100644 --- a/jambotron-ui/src/app/user-module/user.routing.ts +++ b/jambotron-ui/src/app/user-module/user.routing.ts @@ -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) diff --git a/jambotron-ui/src/environments/environment.development.ts b/jambotron-ui/src/environments/environment.development.ts new file mode 100644 index 0000000..dd5aecb --- /dev/null +++ b/jambotron-ui/src/environments/environment.development.ts @@ -0,0 +1,3 @@ +export const environment = { + default_page: 'main/generate-image' +}; diff --git a/jambotron-ui/src/environments/environment.ts b/jambotron-ui/src/environments/environment.ts new file mode 100644 index 0000000..dd5aecb --- /dev/null +++ b/jambotron-ui/src/environments/environment.ts @@ -0,0 +1,3 @@ +export const environment = { + default_page: 'main/generate-image' +}; diff --git a/jambotron-ui/src/server.ts b/jambotron-ui/src/server.ts index e6546c4..5a5b814 100644 --- a/jambotron-ui/src/server.ts +++ b/jambotron-ui/src/server.ts @@ -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. */ diff --git a/src/main/java/com/jambotronGroup/jambotron/ZhiPuAi/ImageController.java b/src/main/java/com/jambotronGroup/jambotron/ZhiPuAi/ImageController.java index f4effa1..d3efac7 100644 --- a/src/main/java/com/jambotronGroup/jambotron/ZhiPuAi/ImageController.java +++ b/src/main/java/com/jambotronGroup/jambotron/ZhiPuAi/ImageController.java @@ -12,7 +12,11 @@ import org.springframework.web.bind.annotation.*; 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 @RequestMapping("/api/zhipuai") public class ImageController { diff --git a/src/main/java/com/jambotronGroup/jambotron/controllers/AuthController.java b/src/main/java/com/jambotronGroup/jambotron/controllers/AuthController.java index c1b1b97..1e8401f 100644 --- a/src/main/java/com/jambotronGroup/jambotron/controllers/AuthController.java +++ b/src/main/java/com/jambotronGroup/jambotron/controllers/AuthController.java @@ -13,6 +13,8 @@ import com.jambotronGroup.jambotron.repository.UserRepository; import com.jambotronGroup.jambotron.security.jwt.JwtUtils; import com.jambotronGroup.jambotron.security.services.UserDetailsImpl; import jakarta.validation.Valid; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseCookie; @@ -34,6 +36,8 @@ import java.util.stream.Collectors; @RestController @RequestMapping("/api/auth") public class AuthController { + + private static final Logger logger = LoggerFactory.getLogger(AuthController.class); @Autowired AuthenticationManager authenticationManager; @@ -65,12 +69,16 @@ public class AuthController { .map(item -> item.getAuthority()) .collect(Collectors.toList()); + logger.info("User {} authenticated successfully with roles: {}", userDetails.getUsername(), roles); + return ResponseEntity.ok().header(HttpHeaders.SET_COOKIE, jwtCookie.toString()) .body(new UserInfoResponse( userDetails.getId(), userDetails.getUsername(), userDetails.getEmail(), - roles)); + roles, + jwtCookie.toString() + )); } @PostMapping("/signup") diff --git a/src/main/java/com/jambotronGroup/jambotron/controllers/TutorialController.java b/src/main/java/com/jambotronGroup/jambotron/controllers/TutorialController.java index 02ee9fd..f0d5a4e 100644 --- a/src/main/java/com/jambotronGroup/jambotron/controllers/TutorialController.java +++ b/src/main/java/com/jambotronGroup/jambotron/controllers/TutorialController.java @@ -2,10 +2,20 @@ package com.jambotronGroup.jambotron.controllers; import com.jambotronGroup.jambotron.model.Tutorial; +import com.jambotronGroup.jambotron.model.User; 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.http.HttpStatus; 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 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 @RequestMapping("/api") public class TutorialController { + @Autowired + AuthenticationFacade authenticationFacade; + @Autowired + UserRepository userRepository; + @Autowired TutorialRepository tutorialRepository; - @GetMapping("/tutorials") + @GetMapping("/user/tutorials") + public ResponseEntity> getUserTutorials(@RequestParam(required = false) String title) { + try { + List tutorials = new ArrayList(); + + 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> getAllTutorials(@RequestParam(required = false) String title) { try { List tutorials = new ArrayList(); - if (title == null) - tutorialRepository.findAll().forEach(tutorials::add); - else - tutorialRepository.findByTitleContaining(title).forEach(tutorials::add); +/* Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + UserDetails userDetails = (UserDetails) authentication.getPrincipal(); + String name = authenticationFacade.getAuthentication().getName(); + + 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()) { return new ResponseEntity<>(HttpStatus.NO_CONTENT); @@ -55,9 +119,15 @@ public class TutorialController { @PostMapping("/tutorials") public ResponseEntity createTutorial(@RequestBody Tutorial tutorial) { + + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + UserDetails userDetails = (UserDetails) authentication.getPrincipal(); + + User user = userRepository.findById(((UserDetailsImpl)userDetails).getId()).get(); + try { 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); } catch (Exception e) { return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR); diff --git a/src/main/java/com/jambotronGroup/jambotron/model/Tutorial.java b/src/main/java/com/jambotronGroup/jambotron/model/Tutorial.java index 0c92d84..1a773c3 100644 --- a/src/main/java/com/jambotronGroup/jambotron/model/Tutorial.java +++ b/src/main/java/com/jambotronGroup/jambotron/model/Tutorial.java @@ -21,14 +21,19 @@ public class Tutorial { @Column(name = "published") private boolean published; + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(name = "userID", nullable = false) + private User user; + public Tutorial() { } - public Tutorial(String title, String description, boolean published) { + public Tutorial(String title, String description, boolean published, User user) { this.title = title; this.description = description; this.published = published; + this.user = user; } public long getId() { diff --git a/src/main/java/com/jambotronGroup/jambotron/payload/response/UserInfoResponse.java b/src/main/java/com/jambotronGroup/jambotron/payload/response/UserInfoResponse.java index 708470a..50ccb62 100644 --- a/src/main/java/com/jambotronGroup/jambotron/payload/response/UserInfoResponse.java +++ b/src/main/java/com/jambotronGroup/jambotron/payload/response/UserInfoResponse.java @@ -10,11 +10,14 @@ public class UserInfoResponse { private String email; private List roles; - public UserInfoResponse(Long id, String username, String email, List roles) { + private String token; + + public UserInfoResponse(Long id, String username, String email, List roles, String token) { this.id = id; this.username = username; this.email = email; this.roles = roles; + this.token = token; } public Long getId() { @@ -44,4 +47,12 @@ public class UserInfoResponse { public List getRoles() { return roles; } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } } diff --git a/src/main/java/com/jambotronGroup/jambotron/repository/TutorialRepository.java b/src/main/java/com/jambotronGroup/jambotron/repository/TutorialRepository.java index df061a0..209b1b2 100644 --- a/src/main/java/com/jambotronGroup/jambotron/repository/TutorialRepository.java +++ b/src/main/java/com/jambotronGroup/jambotron/repository/TutorialRepository.java @@ -10,6 +10,9 @@ import java.util.List; @Repository public interface TutorialRepository extends JpaRepository { + + List findByUserId(Long userId); + List findByUserIdAndTitle(Long userId, String title); List findByPublished(boolean published); List findByTitleContaining(String title); } \ No newline at end of file diff --git a/src/main/java/com/jambotronGroup/jambotron/repository/UserRepository.java b/src/main/java/com/jambotronGroup/jambotron/repository/UserRepository.java index 0b9d998..7701ee0 100644 --- a/src/main/java/com/jambotronGroup/jambotron/repository/UserRepository.java +++ b/src/main/java/com/jambotronGroup/jambotron/repository/UserRepository.java @@ -9,6 +9,8 @@ import java.util.Optional; @Repository public interface UserRepository extends JpaRepository { + + Optional findByUsername(String username); Boolean existsByUsername(String username); diff --git a/src/main/java/com/jambotronGroup/jambotron/security/AuthenticationFacade.java b/src/main/java/com/jambotronGroup/jambotron/security/AuthenticationFacade.java new file mode 100644 index 0000000..2565d5c --- /dev/null +++ b/src/main/java/com/jambotronGroup/jambotron/security/AuthenticationFacade.java @@ -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; + } + + +} + diff --git a/src/main/java/com/jambotronGroup/jambotron/security/IAuthenticationFacade.java b/src/main/java/com/jambotronGroup/jambotron/security/IAuthenticationFacade.java new file mode 100644 index 0000000..1db514b --- /dev/null +++ b/src/main/java/com/jambotronGroup/jambotron/security/IAuthenticationFacade.java @@ -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(); +} \ No newline at end of file diff --git a/src/main/java/com/jambotronGroup/jambotron/security/WebSecurityConfig.java b/src/main/java/com/jambotronGroup/jambotron/security/WebSecurityConfig.java index 271c865..4fac5c5 100644 --- a/src/main/java/com/jambotronGroup/jambotron/security/WebSecurityConfig.java +++ b/src/main/java/com/jambotronGroup/jambotron/security/WebSecurityConfig.java @@ -43,10 +43,7 @@ public class WebSecurityConfig implements WebMvcConfigurer {// extends WebSecuri // authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); // } - @Override - public void addCorsMappings(CorsRegistry registry) { - // Do not add any mappings to enable complete disabling of CORS. - } + @Bean public DaoAuthenticationProvider authenticationProvider() { @@ -106,7 +103,7 @@ public class WebSecurityConfig implements WebMvcConfigurer {// extends WebSecuri @Bean 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)) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> @@ -118,6 +115,7 @@ public class WebSecurityConfig implements WebMvcConfigurer {// extends WebSecuri .requestMatchers("/main/**").permitAll() .requestMatchers("/api/test/**").permitAll() + .requestMatchers("/api/tutorials").permitAll() .requestMatchers("/api/zhipuai/image/**").permitAll() @@ -132,6 +130,12 @@ public class WebSecurityConfig implements WebMvcConfigurer {// extends WebSecuri .requestMatchers("/api/settings").permitAll() .requestMatchers("/api/system/**").permitAll() + + // Allow public access to tutorials (without login) + .requestMatchers("/api/public/tutorials").permitAll() + + .requestMatchers("/api/user").permitAll() + .anyRequest().authenticated() ); diff --git a/src/main/java/com/jambotronGroup/jambotron/security/jwt/AuthEntryPointJwt.java b/src/main/java/com/jambotronGroup/jambotron/security/jwt/AuthEntryPointJwt.java index cf238df..62108a2 100644 --- a/src/main/java/com/jambotronGroup/jambotron/security/jwt/AuthEntryPointJwt.java +++ b/src/main/java/com/jambotronGroup/jambotron/security/jwt/AuthEntryPointJwt.java @@ -26,6 +26,7 @@ public class AuthEntryPointJwt implements AuthenticationEntryPoint { throws IOException, ServletException { logger.error("Unauthorized error: {}", authException.getMessage()); + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Error: Unauthorized"); response.setContentType(MediaType.APPLICATION_JSON_VALUE); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index fe7b370..0babeee 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -20,7 +20,7 @@ spring.jpa.show-sql=true # App Properties app.jwtSecret= ======================spring=back==================== -app.jwtExpirationMs= 30000 +app.jwtExpirationMs= 800000 app.jwtCookieName=springangularts app.origin=http://localhost:4200