From 3fdb1b46401508eebdf9f67d86615f8a7f9c11a2 Mon Sep 17 00:00:00 2001 From: liosha84 <138026690+liosha84@users.noreply.github.com> Date: Sun, 6 Jul 2025 11:00:17 +0300 Subject: [PATCH 1/6] Implement user-specific tutorial access and enhance authentication handling --- jambotron-ui/angular.json | 8 ++- jambotron-ui/src/app/app.config.ts | 2 + .../tutorials.component.ts | 10 +++- .../src/app/helpers/auth.interceptor.ts | 16 ++++-- .../main.component/main.component.ts | 14 ++++-- .../src/app/main-module/main.module.ts | 5 +- jambotron-ui/src/app/services/auth.service.ts | 2 + .../src/app/services/tutorial.service.ts | 3 +- jambotron-ui/src/server.ts | 3 ++ .../jambotron/controllers/AuthController.java | 10 +++- .../controllers/TutorialController.java | 50 +++++++++++++++++-- .../jambotron/model/Tutorial.java | 7 ++- .../payload/response/UserInfoResponse.java | 13 ++++- .../repository/TutorialRepository.java | 2 + .../jambotron/repository/UserRepository.java | 2 + .../security/AuthenticationFacade.java | 16 ++++++ .../security/IAuthenticationFacade.java | 7 +++ .../jambotron/security/WebSecurityConfig.java | 8 ++- 18 files changed, 153 insertions(+), 25 deletions(-) create mode 100644 src/main/java/com/jambotronGroup/jambotron/security/AuthenticationFacade.java create mode 100644 src/main/java/com/jambotronGroup/jambotron/security/IAuthenticationFacade.java 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.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/components/tutorials.component/tutorials.component.ts b/jambotron-ui/src/app/components/tutorials.component/tutorials.component.ts index 11913d6..74325b8 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[]; diff --git a/jambotron-ui/src/app/helpers/auth.interceptor.ts b/jambotron-ui/src/app/helpers/auth.interceptor.ts index 47204e7..6d52bee 100644 --- a/jambotron-ui/src/app/helpers/auth.interceptor.ts +++ b/jambotron-ui/src/app/helpers/auth.interceptor.ts @@ -12,15 +12,25 @@ export class AuthInterceptor implements HttpInterceptor { constructor(private token: TokenStorageService) { } intercept(req: HttpRequest, next: HttpHandler): Observable> { - let authReq = req; + + + req = req.clone({ + withCredentials: true, + }); + + return next.handle(req); + + + /*let authReq = req; const token = this.token.getToken(); if (token != null) { authReq = req.clone({ headers: req.headers.set(TOKEN_HEADER_KEY, 'Bearer ' + token) }); } - return next.handle(authReq); + return next.handle(authReq);*/ + } } 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.ts b/jambotron-ui/src/app/main-module/main.component/main.component.ts index 4ea4697..86d226c 100644 --- a/jambotron-ui/src/app/main-module/main.component/main.component.ts +++ b/jambotron-ui/src/app/main-module/main.component/main.component.ts @@ -18,6 +18,9 @@ import {MatMenu, MatMenuItem, MatMenuTrigger} from '@angular/material/menu'; import {MatTooltip} from '@angular/material/tooltip'; import {DialogSignupComponent} from '../../components/dialog-signup.component/dialog-signup.component'; import {MatLabel} from '@angular/material/input'; +import {HttpClient} from '@angular/common/http'; + +import { environment } from '../../../environments/environment'; @Component({ selector: 'app-main.component', @@ -62,12 +65,13 @@ export class MainComponent implements OnInit{ eventBusSub?: Subscription; + private environment = environment; private storageService: TokenStorageService = inject(TokenStorageService); private authService: AuthService = inject(AuthService); private eventBusService: EventBusService = inject(EventBusService); - constructor(private router: Router) { + constructor(private router: Router,private http: HttpClient) { } goToHome() { @@ -136,7 +140,8 @@ export class MainComponent implements OnInit{ console.log(res); this.storageService.clean(); - window.location.reload(); + //window.location.reload(); + this.router.navigate([environment.default_page]); }, error: err => { console.log(err); @@ -167,8 +172,9 @@ export class MainComponent implements OnInit{ this.dialogLoginData = result; this.authService.login(this.dialogLoginData.username, this.dialogLoginData.password).subscribe( - data => { - this.storageService.saveToken(data.accessToken); + data => { + + //this.storageService.saveToken(data.token); this.storageService.saveUser(data); this.isLoggedIn = true; diff --git a/jambotron-ui/src/app/main-module/main.module.ts b/jambotron-ui/src/app/main-module/main.module.ts index c5b199f..0f580af 100644 --- a/jambotron-ui/src/app/main-module/main.module.ts +++ b/jambotron-ui/src/app/main-module/main.module.ts @@ -4,11 +4,12 @@ import {mainRouting} from './main.routing'; import {NgModule} from '@angular/core'; 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 {AngularImageViewerModule} from '@hreimer/angular-image-viewer'; import {BrowserModule} from '@angular/platform-browser'; import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; +import {authInterceptorProviders} from '../helpers/auth.interceptor'; @NgModule({ declarations: [ @@ -22,7 +23,7 @@ import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; ], exports: [RouterModule], - providers: [{ + providers: [authInterceptorProviders,provideHttpClient(withInterceptorsFromDi()),{ provide: HTTP_INTERCEPTORS, useClass: CustomHttpInterceptor, multi: true diff --git a/jambotron-ui/src/app/services/auth.service.ts b/jambotron-ui/src/app/services/auth.service.ts index e02cd4a..0a920d3 100644 --- a/jambotron-ui/src/app/services/auth.service.ts +++ b/jambotron-ui/src/app/services/auth.service.ts @@ -19,6 +19,8 @@ export class AuthService { username, password }, httpOptions); + + } register(username: string, email: string, password: string): Observable { diff --git a/jambotron-ui/src/app/services/tutorial.service.ts b/jambotron-ui/src/app/services/tutorial.service.ts index adbf3e4..9725c8c 100644 --- a/jambotron-ui/src/app/services/tutorial.service.ts +++ b/jambotron-ui/src/app/services/tutorial.service.ts @@ -2,6 +2,7 @@ import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; import { Tutorial } from '../models/tutorial.model'; +import {text} from 'node:stream/consumers'; const baseUrl = 'http://localhost:8080/api/tutorials'; @@ -39,4 +40,4 @@ export class TutorialService { findByTitle(title: any): Observable { return this.http.get(`${baseUrl}?title=${title}`); } -} \ No newline at end of file +} 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/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..4ae9c22 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; @@ -19,6 +29,11 @@ import java.util.Optional; @RequestMapping("/api") public class TutorialController { + @Autowired + AuthenticationFacade authenticationFacade; + @Autowired + UserRepository userRepository; + @Autowired TutorialRepository tutorialRepository; @@ -27,10 +42,29 @@ public class TutorialController { 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); + } + + + if (tutorials.isEmpty()) { return new ResponseEntity<>(HttpStatus.NO_CONTENT); @@ -55,9 +89,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..c11736b 100644 --- a/src/main/java/com/jambotronGroup/jambotron/repository/TutorialRepository.java +++ b/src/main/java/com/jambotronGroup/jambotron/repository/TutorialRepository.java @@ -10,6 +10,8 @@ import java.util.List; @Repository public interface TutorialRepository extends JpaRepository { + + List findByUserId(Long userId); 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..e0aef45 --- /dev/null +++ b/src/main/java/com/jambotronGroup/jambotron/security/AuthenticationFacade.java @@ -0,0 +1,16 @@ +package com.jambotronGroup.jambotron.security; + +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; + +@Component +public class AuthenticationFacade implements IAuthenticationFacade { + + @Override + public Authentication getAuthentication() { + + return SecurityContextHolder.getContext().getAuthentication(); + } +} + 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..6145de9 --- /dev/null +++ b/src/main/java/com/jambotronGroup/jambotron/security/IAuthenticationFacade.java @@ -0,0 +1,7 @@ +package com.jambotronGroup.jambotron.security; + +import org.springframework.security.core.Authentication; + +public interface IAuthenticationFacade { + Authentication getAuthentication(); +} \ 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..5fc4c7c 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() { @@ -118,7 +115,8 @@ public class WebSecurityConfig implements WebMvcConfigurer {// extends WebSecuri .requestMatchers("/main/**").permitAll() .requestMatchers("/api/test/**").permitAll() - .requestMatchers("/api/tutorials").permitAll() + + .requestMatchers("/api/tutorials").hasRole("ADMIN") .requestMatchers("/api/zhipuai/image/**").permitAll() From 83c1917651847a0fede52a9c5e6e221c0f1146b1 Mon Sep 17 00:00:00 2001 From: liosha84 <138026690+liosha84@users.noreply.github.com> Date: Sun, 6 Jul 2025 11:11:21 +0300 Subject: [PATCH 2/6] Add environment configuration for default page routing --- jambotron-ui/src/environments/environment.development.ts | 3 +++ jambotron-ui/src/environments/environment.ts | 3 +++ 2 files changed, 6 insertions(+) create mode 100644 jambotron-ui/src/environments/environment.development.ts create mode 100644 jambotron-ui/src/environments/environment.ts 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' +}; From f7b2b189b3439005eb3a3f64350542fb6311a397 Mon Sep 17 00:00:00 2001 From: liosha84 <138026690+liosha84@users.noreply.github.com> Date: Sun, 6 Jul 2025 14:04:19 +0300 Subject: [PATCH 3/6] Implement user-specific tutorial retrieval and enhance authentication details --- .../main.component/main.component.ts | 1 + .../src/app/services/tutorial.service.ts | 2 ++ .../tutorials-list.component.html | 26 ++++++++++++-- .../tutorials-list.component.scss | 3 ++ .../tutorials-list.component.ts | 35 +++++++++++++++++-- .../app/user-module/user-api.service.spec.ts | 16 +++++++++ .../src/app/user-module/user-api.service.ts | 19 ++++++++++ .../controllers/TutorialController.java | 28 +++++++++++++++ .../repository/TutorialRepository.java | 1 + .../security/AuthenticationFacade.java | 13 +++++++ .../security/IAuthenticationFacade.java | 3 ++ .../jambotron/security/WebSecurityConfig.java | 2 +- 12 files changed, 143 insertions(+), 6 deletions(-) create mode 100644 jambotron-ui/src/app/user-module/user-api.service.spec.ts create mode 100644 jambotron-ui/src/app/user-module/user-api.service.ts diff --git a/jambotron-ui/src/app/main-module/main.component/main.component.ts b/jambotron-ui/src/app/main-module/main.component/main.component.ts index 86d226c..e176e61 100644 --- a/jambotron-ui/src/app/main-module/main.component/main.component.ts +++ b/jambotron-ui/src/app/main-module/main.component/main.component.ts @@ -142,6 +142,7 @@ export class MainComponent implements OnInit{ //window.location.reload(); this.router.navigate([environment.default_page]); + window.location.reload(); }, error: err => { console.log(err); diff --git a/jambotron-ui/src/app/services/tutorial.service.ts b/jambotron-ui/src/app/services/tutorial.service.ts index 9725c8c..b967abd 100644 --- a/jambotron-ui/src/app/services/tutorial.service.ts +++ b/jambotron-ui/src/app/services/tutorial.service.ts @@ -17,6 +17,8 @@ export class TutorialService { return this.http.get(baseUrl); } + + get(id: any): Observable { return this.http.get(`${baseUrl}/${id}`); } diff --git a/jambotron-ui/src/app/user-module/tutorials-list.component/tutorials-list.component.html b/jambotron-ui/src/app/user-module/tutorials-list.component/tutorials-list.component.html index 088d130..9af23ff 100644 --- a/jambotron-ui/src/app/user-module/tutorials-list.component/tutorials-list.component.html +++ b/jambotron-ui/src/app/user-module/tutorials-list.component/tutorials-list.component.html @@ -1,6 +1,26 @@

tutorials-list.component works!

- 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..77d4c94 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,46 @@ -import { Component } from '@angular/core'; +import {Component, CUSTOM_ELEMENTS_SCHEMA} 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'; @Component({ selector: 'app-tutorials-list.component', imports: [ MatList, - MatListItem + MatListItem, + MatLine, + MatButton, + MatIcon, + MatOption ], + schemas:[CUSTOM_ELEMENTS_SCHEMA], templateUrl: './tutorials-list.component.html', styleUrl: './tutorials-list.component.scss' }) export class TutorialsListComponent { + tutorials?: Tutorial[]; + constructor(private userApiService: UserApiService) { + this.retrieveTutorials(); + } + + retrieveTutorials(): void { + this.userApiService.getUserAllTutorials() + .subscribe( + data => { + this.tutorials = data; + console.log(data); + }, + error => { + console.log(error); + }); + } + + deleteOption($event: MouseEvent, option: any) { + + } } 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..7d6226b --- /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`); + } +} diff --git a/src/main/java/com/jambotronGroup/jambotron/controllers/TutorialController.java b/src/main/java/com/jambotronGroup/jambotron/controllers/TutorialController.java index 4ae9c22..8ea36ce 100644 --- a/src/main/java/com/jambotronGroup/jambotron/controllers/TutorialController.java +++ b/src/main/java/com/jambotronGroup/jambotron/controllers/TutorialController.java @@ -37,6 +37,34 @@ public class TutorialController { @Autowired TutorialRepository tutorialRepository; + @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("/tutorials") public ResponseEntity> getAllTutorials(@RequestParam(required = false) String title) { try { diff --git a/src/main/java/com/jambotronGroup/jambotron/repository/TutorialRepository.java b/src/main/java/com/jambotronGroup/jambotron/repository/TutorialRepository.java index c11736b..209b1b2 100644 --- a/src/main/java/com/jambotronGroup/jambotron/repository/TutorialRepository.java +++ b/src/main/java/com/jambotronGroup/jambotron/repository/TutorialRepository.java @@ -12,6 +12,7 @@ import java.util.List; 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/security/AuthenticationFacade.java b/src/main/java/com/jambotronGroup/jambotron/security/AuthenticationFacade.java index e0aef45..2565d5c 100644 --- a/src/main/java/com/jambotronGroup/jambotron/security/AuthenticationFacade.java +++ b/src/main/java/com/jambotronGroup/jambotron/security/AuthenticationFacade.java @@ -1,7 +1,9 @@ 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 @@ -12,5 +14,16 @@ public class AuthenticationFacade implements IAuthenticationFacade { 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 index 6145de9..1db514b 100644 --- a/src/main/java/com/jambotronGroup/jambotron/security/IAuthenticationFacade.java +++ b/src/main/java/com/jambotronGroup/jambotron/security/IAuthenticationFacade.java @@ -1,7 +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 5fc4c7c..962deb0 100644 --- a/src/main/java/com/jambotronGroup/jambotron/security/WebSecurityConfig.java +++ b/src/main/java/com/jambotronGroup/jambotron/security/WebSecurityConfig.java @@ -116,7 +116,7 @@ public class WebSecurityConfig implements WebMvcConfigurer {// extends WebSecuri .requestMatchers("/main/**").permitAll() .requestMatchers("/api/test/**").permitAll() - .requestMatchers("/api/tutorials").hasRole("ADMIN") + //.requestMatchers("/api/tutorials").hasRole("ADMIN") .requestMatchers("/api/zhipuai/image/**").permitAll() From 2ee97f8c8668a1c343ab8caa6d1e3920778b1289 Mon Sep 17 00:00:00 2001 From: liosha84 <138026690+liosha84@users.noreply.github.com> Date: Sun, 6 Jul 2025 19:37:32 +0300 Subject: [PATCH 4/6] Refactor tutorial service for public access and enhance authentication handling --- jambotron-ui/src/app/app.component.html | 55 -------- jambotron-ui/src/app/app.component.scss | 23 ---- jambotron-ui/src/app/app.component.spec.ts | 35 ----- jambotron-ui/src/app/app.component.ts | 124 ------------------ jambotron-ui/src/app/app.ts | 4 +- .../add-tutorial/add-tutorial.component.ts | 4 +- .../tutorial-details.component.ts | 16 +-- .../tutorials-list.component.ts | 8 +- .../tutorials.component.ts | 18 +-- .../src/app/helpers/auth.interceptor.ts | 44 +++++-- .../main.component/main.component.html | 19 +-- .../main.component/main.component.ts | 10 +- .../src/app/services/tutorial.service.ts | 46 +++---- .../tutorial-add.component.scss | 0 .../tutorial-add.component.spec.ts | 23 ++++ .../tutorial-add.component.ts | 11 ++ .../tutorials-list.component.html | 3 + .../tutorials-list.component.ts | 54 +++++++- .../src/app/user-module/user-api.service.ts | 2 +- .../user.component/user.component.ts | 4 +- .../src/app/user-module/user.routing.ts | 4 + .../jambotron/ZhiPuAi/ImageController.java | 6 +- .../controllers/TutorialController.java | 18 +-- .../jambotron/security/WebSecurityConfig.java | 10 +- .../security/jwt/AuthEntryPointJwt.java | 1 + 25 files changed, 217 insertions(+), 325 deletions(-) delete mode 100644 jambotron-ui/src/app/app.component.html delete mode 100644 jambotron-ui/src/app/app.component.scss delete mode 100644 jambotron-ui/src/app/app.component.spec.ts delete mode 100644 jambotron-ui/src/app/app.component.ts create mode 100644 jambotron-ui/src/app/user-module/tutorial-add.component/tutorial-add.component.scss create mode 100644 jambotron-ui/src/app/user-module/tutorial-add.component/tutorial-add.component.spec.ts create mode 100644 jambotron-ui/src/app/user-module/tutorial-add.component/tutorial-add.component.ts 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.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 74325b8..d539f0d 100644 --- a/jambotron-ui/src/app/components/tutorials.component/tutorials.component.ts +++ b/jambotron-ui/src/app/components/tutorials.component/tutorials.component.ts @@ -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); + } + ); } } diff --git a/jambotron-ui/src/app/helpers/auth.interceptor.ts b/jambotron-ui/src/app/helpers/auth.interceptor.ts index 6d52bee..390b93c 100644 --- a/jambotron-ui/src/app/helpers/auth.interceptor.ts +++ b/jambotron-ui/src/app/helpers/auth.interceptor.ts @@ -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, next: HttpHandler): Observable> { - 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, 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); } } 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) { + } - + @for (tutorial of tutorials; track tutorial) {