From f2b599b2c7e0196be241ec78fb460306aaa7fa33 Mon Sep 17 00:00:00 2001 From: SD-W1972 Date: Sat, 12 Apr 2025 21:17:48 -0300 Subject: [PATCH 01/42] minor changes --- .../com/kuarion/backend/entities/User.java | 89 ------------------- src/main/resources/application.properties | 6 +- 2 files changed, 2 insertions(+), 93 deletions(-) diff --git a/src/main/java/com/kuarion/backend/entities/User.java b/src/main/java/com/kuarion/backend/entities/User.java index 5b305b6..e69de29 100644 --- a/src/main/java/com/kuarion/backend/entities/User.java +++ b/src/main/java/com/kuarion/backend/entities/User.java @@ -1,89 +0,0 @@ -package com.kuarion.backend.entities; - -import java.util.Collection; -import java.util.List; - -import lombok.AllArgsConstructor; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.GenerationType; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.Table; -import jakarta.persistence.Id; -import jakarta.persistence.Enumerated; -import jakarta.persistence.EnumType; - -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.authority.SimpleGrantedAuthority; - -import com.kuarion.backend.roles.Roles; - -@Entity @Table(name = "users") -@Getter @NoArgsConstructor @AllArgsConstructor @EqualsAndHashCode -public class User implements UserDetails { - - @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; // primary key - - @Setter @Column(nullable = false, unique = false) - private String firstName; - - @Setter @Column(nullable = true, unique = false) - private String lastName; - - @Setter @Column(nullable = true, unique = true) - private String username; - - @Setter @Column(nullable = true, unique = true) - private String email; - - @Setter @Column(nullable = false, unique = false) - private String password; - - @Setter @Enumerated(EnumType.STRING) - private Roles role; // user role - - // required methods from UserDetails interface - @Override - public Collection getAuthorities() { - - // if user is ADMIN, he has all roles - if (this.role == role.ADMIN) { - return List.of( - new SimpleGrantedAuthority("ROLE_ADMIN"), - new SimpleGrantedAuthority("ROLE_USER"), - new SimpleGrantedAuthority("ROLE_ENTERPRISE") - ); - } else if (this.role == role.USER) { - return List.of(new SimpleGrantedAuthority("ROLE_USER")); - } else { - return List.of(new SimpleGrantedAuthority("ROLE_ENTERPRISE")); - } - } - - @Override - public boolean isAccountNonExpired() { - return true; - } - - @Override - public boolean isAccountNonLocked() { - return true; - } - - @Override - public boolean isCredentialsNonExpired() { - return true; - } - - @Override - public boolean isEnabled() { - return true; - } -} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 5573d13..547a1ab 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -22,12 +22,10 @@ cors.origins=${CORS_ORIGINS:http://localhost:5173,http://localhost:3000} <<<<<<< HEAD -serve.port:8081 +server.port:8081 spring.config.import=application-local.properties gemini.api.base-url=https://generativelanguage.googleapis.com gemini.api.model=gemini-1.5-pro-latest -======= -# Server port -server.port=8080 + >>>>>>> 337813ee78d80a39bbdc3a1723084cab436f11c6 From ef7ebdeb6cfbe24ff07ed8c0dff55eab9858302b Mon Sep 17 00:00:00 2001 From: SD-W1972 Date: Sat, 12 Apr 2025 21:23:35 -0300 Subject: [PATCH 02/42] setting new branch to our chatbot API and configuration --- Dockerfile | 15 +++++ .../controller/AuthenticationController.java | 62 +++++++++++++++++++ .../com/kuarion/backend/dtos/RegisterDTO.java | 5 ++ .../errors/EmailOrUsernameAlreadyExists.java | 7 +++ .../backend/repositories/UserRepository.java | 17 +++++ .../service/AuthenticationService.java | 44 +++++++++++++ .../kuarion/backend/service/UserService.java | 47 ++++++++++++++ src/main/resources/application.properties | 8 +++ 8 files changed, 205 insertions(+) create mode 100644 Dockerfile create mode 100644 src/main/java/com/kuarion/backend/controller/AuthenticationController.java create mode 100644 src/main/java/com/kuarion/backend/dtos/RegisterDTO.java create mode 100644 src/main/java/com/kuarion/backend/errors/EmailOrUsernameAlreadyExists.java create mode 100644 src/main/java/com/kuarion/backend/repositories/UserRepository.java create mode 100644 src/main/java/com/kuarion/backend/service/AuthenticationService.java create mode 100644 src/main/java/com/kuarion/backend/service/UserService.java diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5935cf9 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +# example Dockerfile + + +FROM maven:3.9.0-eclipse-temurin-17 AS build +WORKDIR /app +COPY . . +RUN mvn clean package -DskipTests + +FROM eclipse-temurin:17-jdk-alpine +WORKDIR /app +COPY --from=build /app/target/*.jar app.jar + +EXPOSE 8080 + +ENTRYPOINT ["java", "-jar", "app.jar"] \ No newline at end of file diff --git a/src/main/java/com/kuarion/backend/controller/AuthenticationController.java b/src/main/java/com/kuarion/backend/controller/AuthenticationController.java new file mode 100644 index 0000000..e31e55c --- /dev/null +++ b/src/main/java/com/kuarion/backend/controller/AuthenticationController.java @@ -0,0 +1,62 @@ +package com.kuarion.backend.controller; + +import java.util.Map; +import java.util.Optional; + +import org.springframework.http.ResponseEntity; +import org.springframework.http.ResponseCookie; +import jakarta.servlet.http.Cookie; +import org.springframework.http.HttpStatus; +import org.springframework.http.HttpHeaders; + +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import jakarta.servlet.http.HttpServletResponse; + +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.LockedException; + +import com.kuarion.backend.dtos.RegisterDTO; +import com.kuarion.backend.entities.User; +import com.kuarion.backend.errors.EmailOrUsernameAlreadyExists; +import com.kuarion.backend.roles.Roles; + +@RestController @RequestMapping(value = "/authentication") +public class AuthenticationService { + private UserService userService; + + // Dependencies Injection + public AuthenticationController(UserService userService) { + this.userService = userService; + } + + @PostMapping(value = "{type}/register") + public ResponseEntity signup(@PathVariable String type, @RequestBody RegisterDTO data, Roles role) { + try { + + // condition to verify if it's an user + if (type.equalsIgnoreCase("pf")) { + var email = this.userService.emailExists(data.email()); + var username = this.userService.usernameExists(data.username()); + if (email || username) { + throw new EmailOrUsernameAlreadyExists("Email or username already exists!"); + } + String encryptedPassword = new BCryptPasswordEncoder.encode(data.password()); + this.userService.createUser(data.firstName(), data.lastName(), data.username(), data.email(), encryptedPassword, role.fromString()); + + return ResponseEntity.status(HttpStatus.OK).body(Map.of("Message", "User created successfully!")); + // condition to verify if it's an enterprise + } else { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(Map.of("message", "URI not found!")); + } + } catch (EmailOrUsernameAlreadyExists e) { + return ResponseEntity.status(HttpStatus.CONFLICT).body(Map.of("message", e.getMessage())); + } + } +} diff --git a/src/main/java/com/kuarion/backend/dtos/RegisterDTO.java b/src/main/java/com/kuarion/backend/dtos/RegisterDTO.java new file mode 100644 index 0000000..a5342ad --- /dev/null +++ b/src/main/java/com/kuarion/backend/dtos/RegisterDTO.java @@ -0,0 +1,5 @@ +package com.kuarion.backend.dtos; + +public record RegisterDTO(String firstName, String lastName, String username, String email, String password) { + +} diff --git a/src/main/java/com/kuarion/backend/errors/EmailOrUsernameAlreadyExists.java b/src/main/java/com/kuarion/backend/errors/EmailOrUsernameAlreadyExists.java new file mode 100644 index 0000000..7a701da --- /dev/null +++ b/src/main/java/com/kuarion/backend/errors/EmailOrUsernameAlreadyExists.java @@ -0,0 +1,7 @@ +package com.kuarion.backend.errors; + +public class EmailOrUsernameAlreadyExists extends Exception { + public EmailOrUsernameAlreadyExists(String message) { + super(message); + } +} diff --git a/src/main/java/com/kuarion/backend/repositories/UserRepository.java b/src/main/java/com/kuarion/backend/repositories/UserRepository.java new file mode 100644 index 0000000..efa9c7b --- /dev/null +++ b/src/main/java/com/kuarion/backend/repositories/UserRepository.java @@ -0,0 +1,17 @@ +package com.kuarion.backend.repositories; + +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.kuarion.backend.entities.User; + +// UserRepository interface extends JpaRepository interface +public interface UserRepository extends JpaRepository { + + // find an user by username + Optional findByUsername(String username); + + // find an user by email + Optional findByEmail(String email); +} \ No newline at end of file diff --git a/src/main/java/com/kuarion/backend/service/AuthenticationService.java b/src/main/java/com/kuarion/backend/service/AuthenticationService.java new file mode 100644 index 0000000..23b51dc --- /dev/null +++ b/src/main/java/com/kuarion/backend/service/AuthenticationService.java @@ -0,0 +1,44 @@ +package com.kuarion.backend.service; + +import java.util.Optional; + +import org.springframework.stereotype.Service; + +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; + +import com.kuarion.backend.entities.User; +import com.kuarion.backend.repositories.UserRepository; + +@Service +public class AuthenticationService implements UserDetailsService { + private UserRepository userRepository; + + // Dependency Injection (UserRepository) + public AuthenticationService(UserRepository userRepository) { + this.userRepository = userRepository; + } + + // required UserDetailsService method. It loads an user for AuthenticationProvider + @Override + public UserDetails loadUserByUsername(String user) { + // the method uses username or email + + // by default, it loads an user by username + Optional userDetails = this.userRepository.findByUsername(user); + + // if 'user' is not username, it tries to find by email + if (userDetails.isEmpty()) { + userDetails = this.userRepository.findByEmail(user); + } + + // if 'user' is not email, it throws an exception + if (userDetails.isEmpty()) { + throw new UsernameNotFoundException("User not found!"); + } + + // return the user + return userDetails.get(); + } +} diff --git a/src/main/java/com/kuarion/backend/service/UserService.java b/src/main/java/com/kuarion/backend/service/UserService.java new file mode 100644 index 0000000..c12f711 --- /dev/null +++ b/src/main/java/com/kuarion/backend/service/UserService.java @@ -0,0 +1,47 @@ +package com.kuarion.backend.service; + +import java.util.Optional; + +import org.springframework.stereotype.Service; + +import com.kuarion.backend.entities.User; +import com.kuarion.backend.repositories.UserRepository; +import com.kuarion.backend.roles.Roles; + +@Service +public class UserService { + private UserRepository userRepository; + + // Dependencies Injection + public UserService(UserRepository userRepository) { + this.userRepository = userRepository; + } + + // method to create a new user + public void createUser(String firstName, String lastName, String username, String email, String password, Roles role) { + User user = new User(); + user.setFirstName(firstName); + user.setLastName(lastName); + user.setUsername(username); + user.setEmail(email); + user.setPassword(password); + user.setRole(role); + userRepository.save(user); + } + + public boolean emailExists(String email) { + Optional user = this.userRepository.findByEmail(email); + if (!user.isEmpty()) { + return true; + } + return false; + } + + public boolean usernameExists(String username) { + Optional user = this.userRepository.findByUsername(username); + if (!user.isEmpty()) { + return true; + } + return false; + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 547a1ab..fb3a0e1 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -20,12 +20,20 @@ spring.jpa.open-in-view=false cors.origins=${CORS_ORIGINS:http://localhost:5173,http://localhost:3000} +<<<<<<< HEAD <<<<<<< HEAD +======= +# Server port +>>>>>>> ec35785a6293d4c34ae3786c5f180f1adbedf744 server.port:8081 spring.config.import=application-local.properties gemini.api.base-url=https://generativelanguage.googleapis.com +<<<<<<< HEAD gemini.api.model=gemini-1.5-pro-latest >>>>>>> 337813ee78d80a39bbdc3a1723084cab436f11c6 +======= +gemini.api.model=gemini-1.5-pro-latest +>>>>>>> ec35785a6293d4c34ae3786c5f180f1adbedf744 From 24eecc80e9393d1cf8c7fab9406f479fdc5b88df Mon Sep 17 00:00:00 2001 From: SD-W1972 Date: Sat, 12 Apr 2025 21:39:29 -0300 Subject: [PATCH 03/42] fix minor bugs lol --- .../kuarion/backend/controller/AuthenticationController.java | 3 ++- .../java/com/kuarion/backend/repositories/UserRepository.java | 4 ++-- .../com/kuarion/backend/service/AuthenticationService.java | 3 ++- src/main/java/com/kuarion/backend/service/UserService.java | 3 ++- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/kuarion/backend/controller/AuthenticationController.java b/src/main/java/com/kuarion/backend/controller/AuthenticationController.java index e31e55c..7c5d0a2 100644 --- a/src/main/java/com/kuarion/backend/controller/AuthenticationController.java +++ b/src/main/java/com/kuarion/backend/controller/AuthenticationController.java @@ -1,4 +1,4 @@ -package com.kuarion.backend.controller; +/*package com.kuarion.backend.controller; import java.util.Map; import java.util.Optional; @@ -60,3 +60,4 @@ public ResponseEntity signup(@PathVariable String type, @RequestBody RegisterDTO } } } +*/ \ No newline at end of file diff --git a/src/main/java/com/kuarion/backend/repositories/UserRepository.java b/src/main/java/com/kuarion/backend/repositories/UserRepository.java index efa9c7b..9b2c514 100644 --- a/src/main/java/com/kuarion/backend/repositories/UserRepository.java +++ b/src/main/java/com/kuarion/backend/repositories/UserRepository.java @@ -1,4 +1,4 @@ -package com.kuarion.backend.repositories; +/*package com.kuarion.backend.repositories; import java.util.Optional; @@ -14,4 +14,4 @@ public interface UserRepository extends JpaRepository { // find an user by email Optional findByEmail(String email); -} \ No newline at end of file +}*/ \ No newline at end of file diff --git a/src/main/java/com/kuarion/backend/service/AuthenticationService.java b/src/main/java/com/kuarion/backend/service/AuthenticationService.java index afd2c22..761c8f5 100644 --- a/src/main/java/com/kuarion/backend/service/AuthenticationService.java +++ b/src/main/java/com/kuarion/backend/service/AuthenticationService.java @@ -1,4 +1,4 @@ -package com.kuarion.backend.service; +/*package com.kuarion.backend.service; import java.util.Optional; @@ -40,3 +40,4 @@ public UserDetails loadUserByUsername(String user) { return userDetails.get(); } } +*/ \ No newline at end of file diff --git a/src/main/java/com/kuarion/backend/service/UserService.java b/src/main/java/com/kuarion/backend/service/UserService.java index c12f711..00e6592 100644 --- a/src/main/java/com/kuarion/backend/service/UserService.java +++ b/src/main/java/com/kuarion/backend/service/UserService.java @@ -1,4 +1,4 @@ -package com.kuarion.backend.service; +/*package com.kuarion.backend.service; import java.util.Optional; @@ -45,3 +45,4 @@ public boolean usernameExists(String username) { return false; } } +*/ \ No newline at end of file From 1f0708c0a1015b30ae45c33669a431cd10f705fe Mon Sep 17 00:00:00 2001 From: SD-W1972 Date: Sun, 13 Apr 2025 00:24:06 -0300 Subject: [PATCH 04/42] temporary fix --- .../com/kuarion/backend/entities/User.java | 115 ++++++++++++++++++ .../backend/repositories/UserRepository.java | 13 +- 2 files changed, 120 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/kuarion/backend/entities/User.java b/src/main/java/com/kuarion/backend/entities/User.java index e69de29..715a121 100644 --- a/src/main/java/com/kuarion/backend/entities/User.java +++ b/src/main/java/com/kuarion/backend/entities/User.java @@ -0,0 +1,115 @@ +package com.kuarion.backend.entities; + +import java.util.Collection; +import java.util.List; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import com.kuarion.backend.roles.Roles; + +import jakarta.persistence.*; +import lombok.*; +/* +@Entity +@Table(name = "users") +@Getter @Setter +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(of = "id") +public class User implements UserDetails { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private String firstName; + + @Column(nullable = true) + private String lastName; + + @Column(nullable = true, unique = true) + private String username; + + @Column(nullable = true, unique = true) + private String email; + + @Column(nullable = false) + private String password; + + @Enumerated(EnumType.STRING) + private Roles role; + + @OneToOne(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) + private UserStatistics userStatistics; + + // Métodos do UserDetails... + @Override + public Collection getAuthorities() { + if (this.role == Roles.ADMIN) { + return List.of( + new SimpleGrantedAuthority("ROLE_ADMIN"), + new SimpleGrantedAuthority("ROLE_USER"), + new SimpleGrantedAuthority("ROLE_ENTERPRISE") + ); + } else if (this.role == Roles.USER) { + return List.of(new SimpleGrantedAuthority("ROLE_USER")); + } else { + return List.of(new SimpleGrantedAuthority("ROLE_ENTERPRISE")); + } + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } + + @Override + public String getPassword() { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getUsername() { + // TODO Auto-generated method stub + return null; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public UserStatistics getUserStatistics() { + return userStatistics; + } + + public void setUserStatistics(UserStatistics userStatistics) { + this.userStatistics = userStatistics; + } + + + + +}*/ \ No newline at end of file diff --git a/src/main/java/com/kuarion/backend/repositories/UserRepository.java b/src/main/java/com/kuarion/backend/repositories/UserRepository.java index 9b2c514..b8d440f 100644 --- a/src/main/java/com/kuarion/backend/repositories/UserRepository.java +++ b/src/main/java/com/kuarion/backend/repositories/UserRepository.java @@ -1,17 +1,14 @@ -/*package com.kuarion.backend.repositories; -import java.util.Optional; +package com.kuarion.backend.repositories; import org.springframework.data.jpa.repository.JpaRepository; -import com.kuarion.backend.entities.User; - // UserRepository interface extends JpaRepository interface -public interface UserRepository extends JpaRepository { +public class UserRepository { // find an user by username - Optional findByUsername(String username); +// Optional findByUsername(String username); // find an user by email - Optional findByEmail(String email); -}*/ \ No newline at end of file + //Optional findByEmail(String email); +} \ No newline at end of file From a45bc35431f88643f0b464346e52e05ea541f923 Mon Sep 17 00:00:00 2001 From: SD-W1972 Date: Sun, 13 Apr 2025 00:24:28 -0300 Subject: [PATCH 05/42] logic implementation to UserStatistics --- .../controller/UserStatisticsController.java | 57 ++++++++++++ .../backend/dtos/UserStatisticsRequest.java | 44 ++++++++++ .../backend/entities/UserStatistics.java | 88 +++++++++++++++++++ .../UserStatisticsRepository.java | 13 +++ .../service/UserStatisticsService.java | 30 +++++++ 5 files changed, 232 insertions(+) create mode 100644 src/main/java/com/kuarion/backend/controller/UserStatisticsController.java create mode 100644 src/main/java/com/kuarion/backend/dtos/UserStatisticsRequest.java create mode 100644 src/main/java/com/kuarion/backend/entities/UserStatistics.java create mode 100644 src/main/java/com/kuarion/backend/repositories/UserStatisticsRepository.java create mode 100644 src/main/java/com/kuarion/backend/service/UserStatisticsService.java diff --git a/src/main/java/com/kuarion/backend/controller/UserStatisticsController.java b/src/main/java/com/kuarion/backend/controller/UserStatisticsController.java new file mode 100644 index 0000000..4b3db40 --- /dev/null +++ b/src/main/java/com/kuarion/backend/controller/UserStatisticsController.java @@ -0,0 +1,57 @@ +package com.kuarion.backend.controller; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; + +import com.kuarion.backend.dtos.UserStatisticsRequest; +import com.kuarion.backend.entities.UserStatistics; +import com.kuarion.backend.repositories.UserRepository; +import com.kuarion.backend.repositories.UserStatisticsRepository; +import com.kuarion.backend.service.UserStatisticsService; + +@Controller +public class UserStatisticsController { + + private final UserStatisticsRepository userStatisticsRepository; + + @Autowired + private UserStatisticsService userStatisticsService; + + /* + @Autowired + private User user; + */ + + + @Autowired + private UserRepository userRepository; + + @Autowired + public UserStatisticsController(UserStatisticsRepository userStatisticsRepository) { + this.userStatisticsRepository = userStatisticsRepository; + } + + + @PostMapping("/customizeExperience") + public void userStatistics(@RequestBody UserStatisticsRequest request) { + UserStatistics userStats = new UserStatistics(); + + userStats.setDado1(request.getDado1()); + userStats.setDado2(request.getDado2()); + userStats.setDado3(request.getDado3()); + userStats.setDado4(request.getDado4()); + userStats.setDado5(request.getDado5()); + + // userStatisticsService.saveUserStatistics(user.getId(), userStats); + } +/* + + @GetMapping("/all") + public List getAllUserStatistics() { + return userStatisticsRepository.findAll(); + } +*/ + +} diff --git a/src/main/java/com/kuarion/backend/dtos/UserStatisticsRequest.java b/src/main/java/com/kuarion/backend/dtos/UserStatisticsRequest.java new file mode 100644 index 0000000..47efae3 --- /dev/null +++ b/src/main/java/com/kuarion/backend/dtos/UserStatisticsRequest.java @@ -0,0 +1,44 @@ +package com.kuarion.backend.dtos; + +public class UserStatisticsRequest { + + + private String dado1; + private String dado2; + private String dado3; + private String dado4; + private String dado5; + + public String getDado1() { + return dado1; + } + public void setDado1(String dado1) { + this.dado1 = dado1; + } + public String getDado2() { + return dado2; + } + public void setDado2(String dado2) { + this.dado2 = dado2; + } + public String getDado3() { + return dado3; + } + public void setDado3(String dado3) { + this.dado3 = dado3; + } + public String getDado4() { + return dado4; + } + public void setDado4(String dado4) { + this.dado4 = dado4; + } + public String getDado5() { + return dado5; + } + public void setDado5(String dado5) { + this.dado5 = dado5; + } + + +} diff --git a/src/main/java/com/kuarion/backend/entities/UserStatistics.java b/src/main/java/com/kuarion/backend/entities/UserStatistics.java new file mode 100644 index 0000000..0b081f9 --- /dev/null +++ b/src/main/java/com/kuarion/backend/entities/UserStatistics.java @@ -0,0 +1,88 @@ +package com.kuarion.backend.entities; + +import jakarta.persistence.*; + +@Entity +public class UserStatistics { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + /* + @OneToOne + @JoinColumn(name = "user_id") // Esta é a coluna de FK na tabela user_statistics + private User user; + */ + private String dado1; + private String dado2; + private String dado3; + private String dado4; + private String dado5; + + + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getDado1() { + return dado1; + } + + public void setDado1(String dado1) { + this.dado1 = dado1; + } + + public String getDado2() { + return dado2; + } + + public void setDado2(String dado2) { + this.dado2 = dado2; + } + + public String getDado3() { + return dado3; + } + + public void setDado3(String dado3) { + this.dado3 = dado3; + } + + public String getDado4() { + return dado4; + } + + public void setDado4(String dado4) { + this.dado4 = dado4; + } + + public String getDado5() { + return dado5; + } + + public void setDado5(String dado5) { + this.dado5 = dado5; + } + + public UserStatistics() { + super(); + } + + /* + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } */ + //aaaaaaaaaaaa + + +} \ No newline at end of file diff --git a/src/main/java/com/kuarion/backend/repositories/UserStatisticsRepository.java b/src/main/java/com/kuarion/backend/repositories/UserStatisticsRepository.java new file mode 100644 index 0000000..0f87812 --- /dev/null +++ b/src/main/java/com/kuarion/backend/repositories/UserStatisticsRepository.java @@ -0,0 +1,13 @@ +package com.kuarion.backend.repositories; + +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.kuarion.backend.entities.UserStatistics; + +public interface UserStatisticsRepository extends JpaRepository{ + Optional findByUsername(String username); + + +} diff --git a/src/main/java/com/kuarion/backend/service/UserStatisticsService.java b/src/main/java/com/kuarion/backend/service/UserStatisticsService.java new file mode 100644 index 0000000..9f74fc4 --- /dev/null +++ b/src/main/java/com/kuarion/backend/service/UserStatisticsService.java @@ -0,0 +1,30 @@ +package com.kuarion.backend.service; + +import org.springframework.beans.factory.annotation.Autowired; + +import com.kuarion.backend.entities.UserStatistics; +import com.kuarion.backend.repositories.UserRepository; +import com.kuarion.backend.repositories.UserStatisticsRepository; + +public class UserStatisticsService { + + @Autowired + private UserRepository userRepository; + + @Autowired + private UserStatisticsRepository userStatisticsRepository; + + + public void saveUserStatistics(Long id, UserStatistics userStats) { + /*User user = + userRepository.findById(id).orElseThrow(() -> new RuntimeException("Usuario nao encontrado")); + + userStatisticsRepository.save(userStats); + + user.setUserStatistics(userStats); + userRepository.save(user); + */ + + } + +} From 28f7bafbeb75f1ae541d7e11dfa74475b38b8b46 Mon Sep 17 00:00:00 2001 From: Gustanol Date: Sun, 13 Apr 2025 13:44:49 -0300 Subject: [PATCH 06/42] feat: creating TokenService.java to create and validate JWT tokens --- pom.xml | 5 ++ .../com/kuarion/backend/dtos/LoginDTO.java | 5 ++ .../kuarion/backend/service/TokenService.java | 59 +++++++++++++++++++ src/main/resources/application.properties | 9 ++- 4 files changed, 75 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/kuarion/backend/dtos/LoginDTO.java create mode 100644 src/main/java/com/kuarion/backend/service/TokenService.java diff --git a/pom.xml b/pom.xml index 0238f02..6e67b1f 100644 --- a/pom.xml +++ b/pom.xml @@ -93,6 +93,11 @@ spring-security-test test + + com.auth0 + java-jwt + 4.5.0 + diff --git a/src/main/java/com/kuarion/backend/dtos/LoginDTO.java b/src/main/java/com/kuarion/backend/dtos/LoginDTO.java new file mode 100644 index 0000000..050c2e7 --- /dev/null +++ b/src/main/java/com/kuarion/backend/dtos/LoginDTO.java @@ -0,0 +1,5 @@ +package com.kuarion.backend.dtos; + +public record LoginDTO(String username, String password) { + +} diff --git a/src/main/java/com/kuarion/backend/service/TokenService.java b/src/main/java/com/kuarion/backend/service/TokenService.java new file mode 100644 index 0000000..f395262 --- /dev/null +++ b/src/main/java/com/kuarion/backend/service/TokenService.java @@ -0,0 +1,59 @@ +package com.kuarion.backend.service; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; + +import org.springframework.stereotype.Service; +import org.springframework.beans.factory.annotation.Value; + +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.exceptions.JWTCreationException; +import com.auth0.jwt.exceptions.JWTVerificationException; +import com.auth0.jwt.JWT; + +import com.kuarion.backend.entities.User; + +@Service +public class TokenService { + + // environment variable + @Value("${token.secret}") + private String tokenSecret; + + public String createToken(Object user) { + try { + + // the environment variable is hashed using HMAC 256 bits + Algorithm algorithm = Algorithm.HMAC256(tokenSecret); + + return JWT.create() + .withIssuer("jwtToken") + .withSubject(user.getUsername()) + .withExpiresAt(expirationTime()) + .sign(algorithm); + } catch (JWTVerificationException e) { + throw new RuntimeException("Token could not be created: ", e); + } + } + + public String validateToken(String token) { + try { + Algorithm algorithm = Algorithm.HMAC256(tokenSecret); + + return JWT.require(algorithm) + .withIssuer("jwtToken") + .build() + .verify(token) + .getSubject(); + } catch (JWTVerificationException e) { + return ""; + } + } + + private Instant expirationTime() { + + // the JWT token has 5 hours of duration + return LocalDateTime.now().plusHours(5).toInstant(ZoneOffset.of("-03:00")); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index b7a4e24..c92e6d6 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -3,9 +3,9 @@ spring.application.name=kuarion spring.profiles.active=dev # Database configuration -spring.datasource.url={DB_URL} -spring.datasource.username={DB_USER} -spring.datasource.password={DB_PASS} +spring.datasource.url=${DB_URL} +spring.datasource.username=${DB_USER} +spring.datasource.password=${DB_PASS} spring.datasource.driver-class-name=org.postgresql.Driver spring.datasource.hikari.maximum-pool-size=10 @@ -13,6 +13,9 @@ spring.datasource.hikari.minimum-idle=2 spring.datasource.hikari.idle-timeout=30000 spring.datasource.hikari.connection-timeout=20000 +# Environment Variables +token.secret=${TOKEN_SECRET} + # Hibernate (JPA) spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect spring.jpa.hibernate.ddl-auto=update From b36138f8d41f2f7b034dee45c29df7f81b12f384 Mon Sep 17 00:00:00 2001 From: Gustanol Date: Sun, 13 Apr 2025 14:16:13 -0300 Subject: [PATCH 07/42] perf: modifying SecurityConfig.java: CSFR disabled; Creating two beans: authenticationManager and passwordEnconder --- .../backend/config/SecurityConfig.java | 51 +++++++++++++------ .../controller/AuthenticationController.java | 10 ++-- 2 files changed, 43 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/kuarion/backend/config/SecurityConfig.java b/src/main/java/com/kuarion/backend/config/SecurityConfig.java index f8b8b25..9c144e4 100644 --- a/src/main/java/com/kuarion/backend/config/SecurityConfig.java +++ b/src/main/java/com/kuarion/backend/config/SecurityConfig.java @@ -1,32 +1,53 @@ package com.kuarion.backend.config; import org.springframework.context.annotation.Bean; +import org.springframework.http.HttpMethod; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.config.http.SessionCreationPolicy; @Configuration @EnableWebSecurity public class SecurityConfig { @Bean - public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception{ - return httpSecurity - .csrf(AbstractHttpConfigurer::disable) - .formLogin(httpForm ->{ - httpForm.loginPage("/login").permitAll(); - httpForm.defaultSuccessUrl("/index", true).permitAll(); - - }) + public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception { + return httpSecurity + .csrf(csrf -> csrf.disable()) + + // authentication based in token: stateless security policy + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests(auth -> auth + .requestMatchers(HttpMethod.GET, "/", "/login", "/index", "/api/chat").permitAll() + .requestMatchers(HttpMethod.POST, "**/authentication/**", "/api/chat").permitAll() + .requestMatchers(HttpMethod.GET, "/dashboard").authenticated() + .anyRequest().denyAll() + ) +// .logout(logout -> logout +// .logoutUrl("/dashboard/logout") +// .logoutSuccessUrl("/") +// .invalidateHttpSession(true) +// .deleteCookies("token") +// .authenticated() +// ) + // .addFilterBefore() + .build(); + } + + @Bean + public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { + return authenticationConfiguration.getAuthenticationManager(); + } - - .authorizeHttpRequests(registry ->{ - registry.requestMatchers("/api/chat").permitAll(); - registry.anyRequest().authenticated(); - - }) - .build(); + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); } } diff --git a/src/main/java/com/kuarion/backend/controller/AuthenticationController.java b/src/main/java/com/kuarion/backend/controller/AuthenticationController.java index e31e55c..21e221f 100644 --- a/src/main/java/com/kuarion/backend/controller/AuthenticationController.java +++ b/src/main/java/com/kuarion/backend/controller/AuthenticationController.java @@ -18,7 +18,7 @@ import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.LockedException; @@ -30,10 +30,12 @@ @RestController @RequestMapping(value = "/authentication") public class AuthenticationService { private UserService userService; + private PasswordEncoder passwordEncoder; // Dependencies Injection - public AuthenticationController(UserService userService) { + public AuthenticationController(UserService userService, PasswordEncoder, passwordEncoder) { this.userService = userService; + this.passwordEncoder = passwordEncoder; } @PostMapping(value = "{type}/register") @@ -47,7 +49,9 @@ public ResponseEntity signup(@PathVariable String type, @RequestBody RegisterDTO if (email || username) { throw new EmailOrUsernameAlreadyExists("Email or username already exists!"); } - String encryptedPassword = new BCryptPasswordEncoder.encode(data.password()); + + // it calls the bean `passwordEncoder` + String encryptedPassword = this.passwordEncoder.encode(data.password()); this.userService.createUser(data.firstName(), data.lastName(), data.username(), data.email(), encryptedPassword, role.fromString()); return ResponseEntity.status(HttpStatus.OK).body(Map.of("Message", "User created successfully!")); From 9ea417fc4ca82475091c2f76af3d2eee4df81c44 Mon Sep 17 00:00:00 2001 From: Gustanol Date: Sun, 13 Apr 2025 15:04:26 -0300 Subject: [PATCH 08/42] feat: adding TokenFilter.java that will verify the authentication token and authenticate user --- .../backend/config/SecurityConfig.java | 1 - .../kuarion/backend/config/TokenFilter.java | 83 +++++++++++++++++++ .../kuarion/backend/service/TokenService.java | 1 + 3 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/kuarion/backend/config/TokenFilter.java diff --git a/src/main/java/com/kuarion/backend/config/SecurityConfig.java b/src/main/java/com/kuarion/backend/config/SecurityConfig.java index 9c144e4..48f2a40 100644 --- a/src/main/java/com/kuarion/backend/config/SecurityConfig.java +++ b/src/main/java/com/kuarion/backend/config/SecurityConfig.java @@ -5,7 +5,6 @@ import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; diff --git a/src/main/java/com/kuarion/backend/config/TokenFilter.java b/src/main/java/com/kuarion/backend/config/TokenFilter.java new file mode 100644 index 0000000..765b79c --- /dev/null +++ b/src/main/java/com/kuarion/backend/config/TokenFilter.java @@ -0,0 +1,83 @@ +package com.kuarion.backend.config; + +import java.io.IOException; + +import org.springframework.stereotype.Component; + +import org.springframework.web.filter.OncePerRequestFilter; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.Cookie; + +import com.kuarion.backend.service.AuthenticationService; +import com.kuarion.backend.service.TokenService; + +@Component +public class TokenFilter extends OncePerRequestFilter { + private AuthenticationService authenticationService; + private TokenService tokenService; + + // Dependencies Injection + public TokenFilter(AuthenticationService authenticationService, TokenService tokenService) { + this.authenticationService = authenticationService; + this.tokenService = tokenService; + } + + @Override + protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain filterChain) throws ServletException, IOException { + // get current URI + String uri = res.getRequestURI(); + // it calls "recoverToken" method to get token + var token = this.recoverToken(res); + // condition to verify if token were found + if (token != null) { + // recover user from tokenService "validateToken" method + var tokenSubject = this.tokenService.validateToken(token); + // loads an user using authenticationService "loadUserByUsername" method + var user = this.authenticationService.loadUserByUsername(tokenSubject); + // create a token to keep the current user authenticated, password (in this case, password is null because user has already been authenticated by AuthenticationController) and its roles + var authentication = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities()); + // create a new object of SecurityContext from SecurityContextHolder class + SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); + // put the "authentication" token into SecurityContext + securityContext.setAuthentication(authentication); + // now, the modified securityContext is placed in SecurityContextHolder. It keeps the user authenticated while the session is active + SecurityContextHolder.setContext(securityContext); + // if the URI starts with "/dashboard" (o.g. /dashboard/account) or it's a file, the requisition continues + if (uri.startsWith("/dashboard") || uri.matches(".*\\.(css|js|png|jpg|svg|ico)$")) { + filterChain.doFilter(res, req); + return; + } + // for all other cases, the user will be redirected to "/dashboard" + filterChain.sendRedirect("/dashboard"); + return; + } + // if token is null, the requisition continues + filterChain.doFilter(res, req); + return; + } + + private String recoverToken(HttpServletRequest req) { + // condition that verify if has active cookies in HTTP session + if (req.getCookies() != null) { + // loop that iterate for each cookie + for (Cookie cookie : req.getCookies()) { + // condition that tries to found a cookie with name "jwtCookie" + if ("jwtCookie".equals(cookie.getName())) { + // if it were found, return it + return cookie.getValue(); + } + // if there's no cookie with name "jwtCookie", return null + return null; + } + } + // if there's no cookie, return null + return null; + } +} diff --git a/src/main/java/com/kuarion/backend/service/TokenService.java b/src/main/java/com/kuarion/backend/service/TokenService.java index f395262..70e9804 100644 --- a/src/main/java/com/kuarion/backend/service/TokenService.java +++ b/src/main/java/com/kuarion/backend/service/TokenService.java @@ -45,6 +45,7 @@ public String validateToken(String token) { .withIssuer("jwtToken") .build() .verify(token) + // return authenticated user .getSubject(); } catch (JWTVerificationException e) { return ""; From dbce31fc6bc38c8f1901c5ae3d43c6877d6222f2 Mon Sep 17 00:00:00 2001 From: Gustanol Date: Sun, 13 Apr 2025 15:55:35 -0300 Subject: [PATCH 09/42] feat: adding login method for common users. Now, after the user be authenticated, a cookie is created to keep the token that will be verified on each request --- .../backend/config/SecurityConfig.java | 23 +++++--- .../controller/AuthenticationController.java | 57 +++++++++++++++++-- 2 files changed, 66 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/kuarion/backend/config/SecurityConfig.java b/src/main/java/com/kuarion/backend/config/SecurityConfig.java index 48f2a40..db18bed 100644 --- a/src/main/java/com/kuarion/backend/config/SecurityConfig.java +++ b/src/main/java/com/kuarion/backend/config/SecurityConfig.java @@ -11,10 +11,16 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration @EnableWebSecurity public class SecurityConfig { + private TokenFilter tokenFilter; + + private SecurityConfig(TokenFilter tokenFilter) { + this.tokenFilter = tokenFilter; + } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception { @@ -29,14 +35,15 @@ public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws .requestMatchers(HttpMethod.GET, "/dashboard").authenticated() .anyRequest().denyAll() ) -// .logout(logout -> logout -// .logoutUrl("/dashboard/logout") -// .logoutSuccessUrl("/") -// .invalidateHttpSession(true) -// .deleteCookies("token") -// .authenticated() -// ) - // .addFilterBefore() + .logout(logout -> logout + .logoutUrl("/dashboard/logout") + .logoutSuccessUrl("/") + .invalidateHttpSession(true) + .deleteCookies("jwtToken") + .authenticated() + ) + // the tokenFilter will intercept all protected routes before UsernamePasswordAuthenticationFilter + .addFilterBefore(tokenFilter, UsernamePasswordAuthenticationFilter.class) .build(); } diff --git a/src/main/java/com/kuarion/backend/controller/AuthenticationController.java b/src/main/java/com/kuarion/backend/controller/AuthenticationController.java index 21e221f..46b1076 100644 --- a/src/main/java/com/kuarion/backend/controller/AuthenticationController.java +++ b/src/main/java/com/kuarion/backend/controller/AuthenticationController.java @@ -22,38 +22,43 @@ import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.LockedException; +import com.kuarion.backend.dtos.LoginDTO; import com.kuarion.backend.dtos.RegisterDTO; import com.kuarion.backend.entities.User; import com.kuarion.backend.errors.EmailOrUsernameAlreadyExists; import com.kuarion.backend.roles.Roles; +import com.kuarion.backend.service.TokenService; @RestController @RequestMapping(value = "/authentication") public class AuthenticationService { private UserService userService; private PasswordEncoder passwordEncoder; + private AuthenticationManager authenticationManager; + private TokenService tokenService; // Dependencies Injection - public AuthenticationController(UserService userService, PasswordEncoder, passwordEncoder) { + public AuthenticationController(UserService userService, PasswordEncoder passwordEncoder, AuthenticationManager authenticationManager, TokenService tokenService) { this.userService = userService; this.passwordEncoder = passwordEncoder; + this.authenticationManager = authenticationManager; + this.tokenService = tokenService; } - @PostMapping(value = "{type}/register") + @PostMapping(value = "/{type}/register") public ResponseEntity signup(@PathVariable String type, @RequestBody RegisterDTO data, Roles role) { try { - - // condition to verify if it's an user + // condition to verify if it's a common user if (type.equalsIgnoreCase("pf")) { var email = this.userService.emailExists(data.email()); var username = this.userService.usernameExists(data.username()); + // condition to verify if email or username already exists if (email || username) { throw new EmailOrUsernameAlreadyExists("Email or username already exists!"); } - // it calls the bean `passwordEncoder` String encryptedPassword = this.passwordEncoder.encode(data.password()); + // create a new user this.userService.createUser(data.firstName(), data.lastName(), data.username(), data.email(), encryptedPassword, role.fromString()); - return ResponseEntity.status(HttpStatus.OK).body(Map.of("Message", "User created successfully!")); // condition to verify if it's an enterprise } else { @@ -63,4 +68,44 @@ public ResponseEntity signup(@PathVariable String type, @RequestBody RegisterDTO return ResponseEntity.status(HttpStatus.CONFLICT).body(Map.of("message", e.getMessage())); } } + + @PostMapping(value = "/{type}/login") + public ResponseEntity signin(@RequestBody LoginDTO data, @PathVariable String type, HttpServletResponse res) { + try { + // condition to verify if it's a common user + if (type.equalsIgnoreCase("pf")) { + // create a new authentication token to represents user authentication + var authenticationToken = new UsernamePasswordAuthenticationToken(data.username(), data.password()); + // it calls AuthenticationManager bean that will call an AuthenticationProvider. After this, AuthenticationProvider will use UserDetailsService and compare the password with the hashed one in the database + var auth = this.authenticationManager.authenticate(authenticationToken); + // create the JWT token using current username as subject + var token = this.tokenService.createToken((User) auth.getPrincipal()); + // it calls the generateCookie method to create cookie that will keep token + Cookie cookie = this.generateCookie(token); + // add cookie in HTTP header + res.addCookie(cookie); + return ResponseEntity.status(HttpStatus.OK).body(Map.of("token", token)); + + // important: the AuthenticationManager will not authenticate user itself. It will be a trigger to create cookie and token that will verified by TokenFilter + } else { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(Map.of("message", "URI not found!")); + } + } catch (BadCredentialsException e) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of("message", "Invalid credentials!")); + } catch (LockedException e) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).body(Map.of("message", "Please, verify your email!")); + } + } + + private Cookie generateCookie(String token) { + Cookie cookie = new Cookie("jwtToken", token); + + cookie.setHttpOnly(true); // avoid XSS attacks + cookie.setSecure(false); // it must be true (use false only for tests) + cookie.setPath("/"); // all paths + cookie.setAttribute("SameSite", "Strict"); // the cookie will be active only in the same sime it was created (avoid CSRF attacks) + cookie.setMaxAge(18000); // it will expire in five hours + + return cookie; + } } From e52e14f123e3cc94d2e8ccbc84c30592adc2850d Mon Sep 17 00:00:00 2001 From: Gustanol Date: Sun, 13 Apr 2025 19:40:07 -0300 Subject: [PATCH 10/42] fix: fixing some bugs --- pom.xml | 64 +++++++++---------- .../backend/config/SecurityConfig.java | 9 +-- .../kuarion/backend/config/TokenFilter.java | 11 ++-- .../controller/AuthenticationController.java | 6 +- .../kuarion/backend/service/TokenService.java | 2 +- 5 files changed, 48 insertions(+), 44 deletions(-) diff --git a/pom.xml b/pom.xml index 6e67b1f..fb88e83 100644 --- a/pom.xml +++ b/pom.xml @@ -1,6 +1,6 @@ - + 4.0.0 org.springframework.boot @@ -27,7 +27,7 @@ - 24 + 21 1.0.0-M6 @@ -115,34 +115,35 @@ - - - org.apache.maven.plugins - maven-compiler-plugin - - - - org.projectlombok - lombok - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - org.projectlombok - lombok - - - - - - - + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.projectlombok + lombok + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + + spring-snapshots Spring Snapshots @@ -163,5 +164,4 @@ - \ No newline at end of file diff --git a/src/main/java/com/kuarion/backend/config/SecurityConfig.java b/src/main/java/com/kuarion/backend/config/SecurityConfig.java index db18bed..90df501 100644 --- a/src/main/java/com/kuarion/backend/config/SecurityConfig.java +++ b/src/main/java/com/kuarion/backend/config/SecurityConfig.java @@ -23,8 +23,8 @@ private SecurityConfig(TokenFilter tokenFilter) { } @Bean - public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception { - return httpSecurity + public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception { + return httpSecurity .csrf(csrf -> csrf.disable()) // authentication based in token: stateless security policy @@ -40,12 +40,13 @@ public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws .logoutSuccessUrl("/") .invalidateHttpSession(true) .deleteCookies("jwtToken") - .authenticated() + .permitAll() ) // the tokenFilter will intercept all protected routes before UsernamePasswordAuthenticationFilter + // the tokenFilter will intercept all protected routes before UsernamePasswordAuthenticationFilter .addFilterBefore(tokenFilter, UsernamePasswordAuthenticationFilter.class) .build(); - } + } @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { diff --git a/src/main/java/com/kuarion/backend/config/TokenFilter.java b/src/main/java/com/kuarion/backend/config/TokenFilter.java index 765b79c..d728eaa 100644 --- a/src/main/java/com/kuarion/backend/config/TokenFilter.java +++ b/src/main/java/com/kuarion/backend/config/TokenFilter.java @@ -8,6 +8,7 @@ import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.context.SecurityContext; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; @@ -32,9 +33,9 @@ public TokenFilter(AuthenticationService authenticationService, TokenService tok @Override protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain filterChain) throws ServletException, IOException { // get current URI - String uri = res.getRequestURI(); + String uri = req.getRequestURI(); // it calls "recoverToken" method to get token - var token = this.recoverToken(res); + var token = this.recoverToken(req); // condition to verify if token were found if (token != null) { // recover user from tokenService "validateToken" method @@ -51,15 +52,15 @@ protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, SecurityContextHolder.setContext(securityContext); // if the URI starts with "/dashboard" (o.g. /dashboard/account) or it's a file, the requisition continues if (uri.startsWith("/dashboard") || uri.matches(".*\\.(css|js|png|jpg|svg|ico)$")) { - filterChain.doFilter(res, req); + filterChain.doFilter(req, res); return; } // for all other cases, the user will be redirected to "/dashboard" - filterChain.sendRedirect("/dashboard"); + res.sendRedirect("/dashboard"); return; } // if token is null, the requisition continues - filterChain.doFilter(res, req); + filterChain.doFilter(req, res); return; } diff --git a/src/main/java/com/kuarion/backend/controller/AuthenticationController.java b/src/main/java/com/kuarion/backend/controller/AuthenticationController.java index 46b1076..dc969d2 100644 --- a/src/main/java/com/kuarion/backend/controller/AuthenticationController.java +++ b/src/main/java/com/kuarion/backend/controller/AuthenticationController.java @@ -9,6 +9,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.HttpHeaders; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -28,9 +29,10 @@ import com.kuarion.backend.errors.EmailOrUsernameAlreadyExists; import com.kuarion.backend.roles.Roles; import com.kuarion.backend.service.TokenService; +import com.kuarion.backend.service.UserService; @RestController @RequestMapping(value = "/authentication") -public class AuthenticationService { +public class AuthenticationController { private UserService userService; private PasswordEncoder passwordEncoder; private AuthenticationManager authenticationManager; @@ -58,7 +60,7 @@ public ResponseEntity signup(@PathVariable String type, @RequestBody RegisterDTO // it calls the bean `passwordEncoder` String encryptedPassword = this.passwordEncoder.encode(data.password()); // create a new user - this.userService.createUser(data.firstName(), data.lastName(), data.username(), data.email(), encryptedPassword, role.fromString()); + this.userService.createUser(data.firstName(), data.lastName(), data.username(), data.email(), encryptedPassword, role.fromString("ROLE_USER")); return ResponseEntity.status(HttpStatus.OK).body(Map.of("Message", "User created successfully!")); // condition to verify if it's an enterprise } else { diff --git a/src/main/java/com/kuarion/backend/service/TokenService.java b/src/main/java/com/kuarion/backend/service/TokenService.java index 70e9804..f602a25 100644 --- a/src/main/java/com/kuarion/backend/service/TokenService.java +++ b/src/main/java/com/kuarion/backend/service/TokenService.java @@ -21,7 +21,7 @@ public class TokenService { @Value("${token.secret}") private String tokenSecret; - public String createToken(Object user) { + public String createToken(User user) { try { // the environment variable is hashed using HMAC 256 bits From 1f6d79e6bdbb5ad6c55c65a3074b1fd4bc0e3576 Mon Sep 17 00:00:00 2001 From: Gustanol Date: Sun, 13 Apr 2025 19:42:20 -0300 Subject: [PATCH 11/42] fix: changing Java version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index fb88e83..54f2d18 100644 --- a/pom.xml +++ b/pom.xml @@ -27,7 +27,7 @@ - 21 + 24 1.0.0-M6 From 546a8d61c1d1c6f2faffde0699850985d1c6d7de Mon Sep 17 00:00:00 2001 From: Gustanol Date: Mon, 14 Apr 2025 11:18:45 -0300 Subject: [PATCH 12/42] fix: changing SecurityConfig constructor access modifier from private to public --- src/main/java/com/kuarion/backend/config/SecurityConfig.java | 2 +- src/main/java/com/kuarion/backend/config/TokenFilter.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/kuarion/backend/config/SecurityConfig.java b/src/main/java/com/kuarion/backend/config/SecurityConfig.java index 90df501..895f97f 100644 --- a/src/main/java/com/kuarion/backend/config/SecurityConfig.java +++ b/src/main/java/com/kuarion/backend/config/SecurityConfig.java @@ -18,7 +18,7 @@ public class SecurityConfig { private TokenFilter tokenFilter; - private SecurityConfig(TokenFilter tokenFilter) { + public SecurityConfig(TokenFilter tokenFilter) { this.tokenFilter = tokenFilter; } diff --git a/src/main/java/com/kuarion/backend/config/TokenFilter.java b/src/main/java/com/kuarion/backend/config/TokenFilter.java index d728eaa..69e322a 100644 --- a/src/main/java/com/kuarion/backend/config/TokenFilter.java +++ b/src/main/java/com/kuarion/backend/config/TokenFilter.java @@ -36,7 +36,7 @@ protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, String uri = req.getRequestURI(); // it calls "recoverToken" method to get token var token = this.recoverToken(req); - // condition to verify if token were found + // condition to verify if token was found if (token != null) { // recover user from tokenService "validateToken" method var tokenSubject = this.tokenService.validateToken(token); From 3b2cb7dd9830c18b7cd126cc10e38ca4f999adb7 Mon Sep 17 00:00:00 2001 From: SD-W1972 Date: Mon, 14 Apr 2025 18:44:13 -0300 Subject: [PATCH 13/42] fixing some bugs before merging with main --- .../backend/config/SecurityConfig.java | 4 +- .../kuarion/backend/config/TokenFilter.java | 4 +- .../com/kuarion/backend/entities/User.java | 168 ++++++++---------- .../backend/repositories/UserRepository.java | 11 +- .../kuarion/backend/service/UserService.java | 7 +- 5 files changed, 85 insertions(+), 109 deletions(-) diff --git a/src/main/java/com/kuarion/backend/config/SecurityConfig.java b/src/main/java/com/kuarion/backend/config/SecurityConfig.java index 90df501..341daf7 100644 --- a/src/main/java/com/kuarion/backend/config/SecurityConfig.java +++ b/src/main/java/com/kuarion/backend/config/SecurityConfig.java @@ -18,7 +18,7 @@ public class SecurityConfig { private TokenFilter tokenFilter; - private SecurityConfig(TokenFilter tokenFilter) { + public SecurityConfig(TokenFilter tokenFilter) { this.tokenFilter = tokenFilter; } @@ -57,4 +57,4 @@ public AuthenticationManager authenticationManager(AuthenticationConfiguration a public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } -} +} \ No newline at end of file diff --git a/src/main/java/com/kuarion/backend/config/TokenFilter.java b/src/main/java/com/kuarion/backend/config/TokenFilter.java index d728eaa..160a5c3 100644 --- a/src/main/java/com/kuarion/backend/config/TokenFilter.java +++ b/src/main/java/com/kuarion/backend/config/TokenFilter.java @@ -36,7 +36,7 @@ protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, String uri = req.getRequestURI(); // it calls "recoverToken" method to get token var token = this.recoverToken(req); - // condition to verify if token were found + // condition to verify if token was found if (token != null) { // recover user from tokenService "validateToken" method var tokenSubject = this.tokenService.validateToken(token); @@ -81,4 +81,4 @@ private String recoverToken(HttpServletRequest req) { // if there's no cookie, return null return null; } -} +} \ No newline at end of file diff --git a/src/main/java/com/kuarion/backend/entities/User.java b/src/main/java/com/kuarion/backend/entities/User.java index 715a121..5b305b6 100644 --- a/src/main/java/com/kuarion/backend/entities/User.java +++ b/src/main/java/com/kuarion/backend/entities/User.java @@ -3,113 +3,87 @@ import java.util.Collection; import java.util.List; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GenerationType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Table; +import jakarta.persistence.Id; +import jakarta.persistence.Enumerated; +import jakarta.persistence.EnumType; + import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import com.kuarion.backend.roles.Roles; -import jakarta.persistence.*; -import lombok.*; -/* -@Entity -@Table(name = "users") -@Getter @Setter -@NoArgsConstructor -@AllArgsConstructor -@EqualsAndHashCode(of = "id") +@Entity @Table(name = "users") +@Getter @NoArgsConstructor @AllArgsConstructor @EqualsAndHashCode public class User implements UserDetails { + + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; // primary key + + @Setter @Column(nullable = false, unique = false) + private String firstName; + + @Setter @Column(nullable = true, unique = false) + private String lastName; - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @Column(nullable = false) - private String firstName; - - @Column(nullable = true) - private String lastName; - - @Column(nullable = true, unique = true) - private String username; - - @Column(nullable = true, unique = true) - private String email; - - @Column(nullable = false) - private String password; - - @Enumerated(EnumType.STRING) - private Roles role; - - @OneToOne(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) - private UserStatistics userStatistics; + @Setter @Column(nullable = true, unique = true) + private String username; + + @Setter @Column(nullable = true, unique = true) + private String email; + + @Setter @Column(nullable = false, unique = false) + private String password; + + @Setter @Enumerated(EnumType.STRING) + private Roles role; // user role + + // required methods from UserDetails interface + @Override + public Collection getAuthorities() { - // Métodos do UserDetails... - @Override - public Collection getAuthorities() { - if (this.role == Roles.ADMIN) { - return List.of( - new SimpleGrantedAuthority("ROLE_ADMIN"), - new SimpleGrantedAuthority("ROLE_USER"), - new SimpleGrantedAuthority("ROLE_ENTERPRISE") - ); - } else if (this.role == Roles.USER) { - return List.of(new SimpleGrantedAuthority("ROLE_USER")); - } else { - return List.of(new SimpleGrantedAuthority("ROLE_ENTERPRISE")); - } + // if user is ADMIN, he has all roles + if (this.role == role.ADMIN) { + return List.of( + new SimpleGrantedAuthority("ROLE_ADMIN"), + new SimpleGrantedAuthority("ROLE_USER"), + new SimpleGrantedAuthority("ROLE_ENTERPRISE") + ); + } else if (this.role == role.USER) { + return List.of(new SimpleGrantedAuthority("ROLE_USER")); + } else { + return List.of(new SimpleGrantedAuthority("ROLE_ENTERPRISE")); } + } - @Override - public boolean isAccountNonExpired() { - return true; - } - - @Override - public boolean isAccountNonLocked() { - return true; - } - - @Override - public boolean isCredentialsNonExpired() { - return true; - } - - @Override - public boolean isEnabled() { - return true; - } - - @Override - public String getPassword() { - // TODO Auto-generated method stub - return null; - } - - @Override - public String getUsername() { - // TODO Auto-generated method stub - return null; - } - - public Long getId() { - return id; - } + @Override + public boolean isAccountNonExpired() { + return true; + } - public void setId(Long id) { - this.id = id; - } + @Override + public boolean isAccountNonLocked() { + return true; + } - public UserStatistics getUserStatistics() { - return userStatistics; - } + @Override + public boolean isCredentialsNonExpired() { + return true; + } - public void setUserStatistics(UserStatistics userStatistics) { - this.userStatistics = userStatistics; - } - - - - -}*/ \ No newline at end of file + @Override + public boolean isEnabled() { + return true; + } +} \ No newline at end of file diff --git a/src/main/java/com/kuarion/backend/repositories/UserRepository.java b/src/main/java/com/kuarion/backend/repositories/UserRepository.java index b8d440f..efa9c7b 100644 --- a/src/main/java/com/kuarion/backend/repositories/UserRepository.java +++ b/src/main/java/com/kuarion/backend/repositories/UserRepository.java @@ -1,14 +1,17 @@ - package com.kuarion.backend.repositories; +import java.util.Optional; + import org.springframework.data.jpa.repository.JpaRepository; +import com.kuarion.backend.entities.User; + // UserRepository interface extends JpaRepository interface -public class UserRepository { +public interface UserRepository extends JpaRepository { // find an user by username -// Optional findByUsername(String username); + Optional findByUsername(String username); // find an user by email - //Optional findByEmail(String email); + Optional findByEmail(String email); } \ No newline at end of file diff --git a/src/main/java/com/kuarion/backend/service/UserService.java b/src/main/java/com/kuarion/backend/service/UserService.java index 00e6592..5c76f1f 100644 --- a/src/main/java/com/kuarion/backend/service/UserService.java +++ b/src/main/java/com/kuarion/backend/service/UserService.java @@ -1,4 +1,4 @@ -/*package com.kuarion.backend.service; +package com.kuarion.backend.service; import java.util.Optional; @@ -28,7 +28,7 @@ public void createUser(String firstName, String lastName, String username, Strin user.setRole(role); userRepository.save(user); } - + public boolean emailExists(String email) { Optional user = this.userRepository.findByEmail(email); if (!user.isEmpty()) { @@ -44,5 +44,4 @@ public boolean usernameExists(String username) { } return false; } -} -*/ \ No newline at end of file +} \ No newline at end of file From 1039595b1b008e8734b22015382b114e79749fc7 Mon Sep 17 00:00:00 2001 From: Gustanol Date: Mon, 14 Apr 2025 20:22:35 -0300 Subject: [PATCH 14/42] fix: removing '**/authentication/**' and adding '/authentication/**' on SecurityConfig.java --- pom.xml | 2 +- src/main/java/com/kuarion/backend/config/SecurityConfig.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 54f2d18..fb88e83 100644 --- a/pom.xml +++ b/pom.xml @@ -27,7 +27,7 @@ - 24 + 21 1.0.0-M6 diff --git a/src/main/java/com/kuarion/backend/config/SecurityConfig.java b/src/main/java/com/kuarion/backend/config/SecurityConfig.java index 895f97f..706bed9 100644 --- a/src/main/java/com/kuarion/backend/config/SecurityConfig.java +++ b/src/main/java/com/kuarion/backend/config/SecurityConfig.java @@ -31,7 +31,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth .requestMatchers(HttpMethod.GET, "/", "/login", "/index", "/api/chat").permitAll() - .requestMatchers(HttpMethod.POST, "**/authentication/**", "/api/chat").permitAll() + .requestMatchers(HttpMethod.POST, "/authentication/**", "/api/chat").permitAll() .requestMatchers(HttpMethod.GET, "/dashboard").authenticated() .anyRequest().denyAll() ) From e19455e9a8a1d73970aec0ffd40bc45e9097658d Mon Sep 17 00:00:00 2001 From: SD-W1972 Date: Mon, 14 Apr 2025 20:23:26 -0300 Subject: [PATCH 15/42] bug fix --- pom.xml | 3 +- .../controller/AuthenticationController.java | 5 +- .../controller/UserStatisticsController.java | 57 ------------- .../com/kuarion/backend/entities/User.java | 80 +++++++++++++++---- .../UserStatisticsRepository.java | 13 --- .../service/AuthenticationService.java | 9 ++- .../service/UserStatisticsService.java | 30 ------- 7 files changed, 74 insertions(+), 123 deletions(-) delete mode 100644 src/main/java/com/kuarion/backend/controller/UserStatisticsController.java delete mode 100644 src/main/java/com/kuarion/backend/repositories/UserStatisticsRepository.java delete mode 100644 src/main/java/com/kuarion/backend/service/UserStatisticsService.java diff --git a/pom.xml b/pom.xml index 54f2d18..bf99449 100644 --- a/pom.xml +++ b/pom.xml @@ -27,7 +27,7 @@ - 24 + 21 1.0.0-M6 @@ -82,6 +82,7 @@ org.projectlombok lombok true + 1.18.36 org.springframework.boot diff --git a/src/main/java/com/kuarion/backend/controller/AuthenticationController.java b/src/main/java/com/kuarion/backend/controller/AuthenticationController.java index 1b350c0..168f50c 100644 --- a/src/main/java/com/kuarion/backend/controller/AuthenticationController.java +++ b/src/main/java/com/kuarion/backend/controller/AuthenticationController.java @@ -1,4 +1,4 @@ -/*package com.kuarion.backend.controller; +package com.kuarion.backend.controller; import java.util.Map; import java.util.Optional; @@ -110,5 +110,4 @@ private Cookie generateCookie(String token) { return cookie; } -} -*/ \ No newline at end of file +} \ No newline at end of file diff --git a/src/main/java/com/kuarion/backend/controller/UserStatisticsController.java b/src/main/java/com/kuarion/backend/controller/UserStatisticsController.java deleted file mode 100644 index 4b3db40..0000000 --- a/src/main/java/com/kuarion/backend/controller/UserStatisticsController.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.kuarion.backend.controller; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; - -import com.kuarion.backend.dtos.UserStatisticsRequest; -import com.kuarion.backend.entities.UserStatistics; -import com.kuarion.backend.repositories.UserRepository; -import com.kuarion.backend.repositories.UserStatisticsRepository; -import com.kuarion.backend.service.UserStatisticsService; - -@Controller -public class UserStatisticsController { - - private final UserStatisticsRepository userStatisticsRepository; - - @Autowired - private UserStatisticsService userStatisticsService; - - /* - @Autowired - private User user; - */ - - - @Autowired - private UserRepository userRepository; - - @Autowired - public UserStatisticsController(UserStatisticsRepository userStatisticsRepository) { - this.userStatisticsRepository = userStatisticsRepository; - } - - - @PostMapping("/customizeExperience") - public void userStatistics(@RequestBody UserStatisticsRequest request) { - UserStatistics userStats = new UserStatistics(); - - userStats.setDado1(request.getDado1()); - userStats.setDado2(request.getDado2()); - userStats.setDado3(request.getDado3()); - userStats.setDado4(request.getDado4()); - userStats.setDado5(request.getDado5()); - - // userStatisticsService.saveUserStatistics(user.getId(), userStats); - } -/* - - @GetMapping("/all") - public List getAllUserStatistics() { - return userStatisticsRepository.findAll(); - } -*/ - -} diff --git a/src/main/java/com/kuarion/backend/entities/User.java b/src/main/java/com/kuarion/backend/entities/User.java index 5b305b6..1a2c738 100644 --- a/src/main/java/com/kuarion/backend/entities/User.java +++ b/src/main/java/com/kuarion/backend/entities/User.java @@ -3,26 +3,25 @@ import java.util.Collection; import java.util.List; -import lombok.AllArgsConstructor; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import com.kuarion.backend.roles.Roles; import jakarta.persistence.Column; import jakarta.persistence.Entity; -import jakarta.persistence.GenerationType; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; import jakarta.persistence.GeneratedValue; -import jakarta.persistence.Table; +import jakarta.persistence.GenerationType; import jakarta.persistence.Id; -import jakarta.persistence.Enumerated; -import jakarta.persistence.EnumType; - -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.authority.SimpleGrantedAuthority; - -import com.kuarion.backend.roles.Roles; +import jakarta.persistence.Table; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; @Entity @Table(name = "users") @Getter @NoArgsConstructor @AllArgsConstructor @EqualsAndHashCode @@ -86,4 +85,55 @@ public boolean isCredentialsNonExpired() { public boolean isEnabled() { return true; } + +public String getFirstName() { + return firstName; +} + +public void setFirstName(String firstName) { + this.firstName = firstName; +} + +public String getLastName() { + return lastName; +} + +public void setLastName(String lastName) { + this.lastName = lastName; +} + +public String getUsername() { + return username; +} + +public void setUsername(String username) { + this.username = username; +} + +public String getEmail() { + return email; +} + +public void setEmail(String email) { + this.email = email; +} + +public String getPassword() { + return password; +} + +public void setPassword(String password) { + this.password = password; +} + +public Roles getRole() { + return role; +} + +public void setRole(Roles role) { + this.role = role; +} + + + } \ No newline at end of file diff --git a/src/main/java/com/kuarion/backend/repositories/UserStatisticsRepository.java b/src/main/java/com/kuarion/backend/repositories/UserStatisticsRepository.java deleted file mode 100644 index 0f87812..0000000 --- a/src/main/java/com/kuarion/backend/repositories/UserStatisticsRepository.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.kuarion.backend.repositories; - -import java.util.Optional; - -import org.springframework.data.jpa.repository.JpaRepository; - -import com.kuarion.backend.entities.UserStatistics; - -public interface UserStatisticsRepository extends JpaRepository{ - Optional findByUsername(String username); - - -} diff --git a/src/main/java/com/kuarion/backend/service/AuthenticationService.java b/src/main/java/com/kuarion/backend/service/AuthenticationService.java index 761c8f5..eb95de2 100644 --- a/src/main/java/com/kuarion/backend/service/AuthenticationService.java +++ b/src/main/java/com/kuarion/backend/service/AuthenticationService.java @@ -1,12 +1,14 @@ -/*package com.kuarion.backend.service; +package com.kuarion.backend.service; import java.util.Optional; +import org.springframework.stereotype.Service; + import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.stereotype.Service; +import com.kuarion.backend.entities.User; import com.kuarion.backend.repositories.UserRepository; @Service @@ -39,5 +41,4 @@ public UserDetails loadUserByUsername(String user) { // return the user return userDetails.get(); } -} -*/ \ No newline at end of file +} \ No newline at end of file diff --git a/src/main/java/com/kuarion/backend/service/UserStatisticsService.java b/src/main/java/com/kuarion/backend/service/UserStatisticsService.java deleted file mode 100644 index 9f74fc4..0000000 --- a/src/main/java/com/kuarion/backend/service/UserStatisticsService.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.kuarion.backend.service; - -import org.springframework.beans.factory.annotation.Autowired; - -import com.kuarion.backend.entities.UserStatistics; -import com.kuarion.backend.repositories.UserRepository; -import com.kuarion.backend.repositories.UserStatisticsRepository; - -public class UserStatisticsService { - - @Autowired - private UserRepository userRepository; - - @Autowired - private UserStatisticsRepository userStatisticsRepository; - - - public void saveUserStatistics(Long id, UserStatistics userStats) { - /*User user = - userRepository.findById(id).orElseThrow(() -> new RuntimeException("Usuario nao encontrado")); - - userStatisticsRepository.save(userStats); - - user.setUserStatistics(userStats); - userRepository.save(user); - */ - - } - -} From e288ccf59bcb60d6f112e9dcad7581f4f7c8f9ae Mon Sep 17 00:00:00 2001 From: Gustanol Date: Mon, 14 Apr 2025 21:01:46 -0300 Subject: [PATCH 16/42] fix: changing all User attibutes to 'nullable = false' --- .../java/com/kuarion/backend/config/SecurityConfig.java | 2 +- src/main/java/com/kuarion/backend/entities/User.java | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/kuarion/backend/config/SecurityConfig.java b/src/main/java/com/kuarion/backend/config/SecurityConfig.java index 706bed9..9114adb 100644 --- a/src/main/java/com/kuarion/backend/config/SecurityConfig.java +++ b/src/main/java/com/kuarion/backend/config/SecurityConfig.java @@ -32,7 +32,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws .authorizeHttpRequests(auth -> auth .requestMatchers(HttpMethod.GET, "/", "/login", "/index", "/api/chat").permitAll() .requestMatchers(HttpMethod.POST, "/authentication/**", "/api/chat").permitAll() - .requestMatchers(HttpMethod.GET, "/dashboard").authenticated() + .requestMatchers(HttpMethod.GET, "/dashboard/**").authenticated() .anyRequest().denyAll() ) .logout(logout -> logout diff --git a/src/main/java/com/kuarion/backend/entities/User.java b/src/main/java/com/kuarion/backend/entities/User.java index 5b305b6..9531e87 100644 --- a/src/main/java/com/kuarion/backend/entities/User.java +++ b/src/main/java/com/kuarion/backend/entities/User.java @@ -34,13 +34,13 @@ public class User implements UserDetails { @Setter @Column(nullable = false, unique = false) private String firstName; - @Setter @Column(nullable = true, unique = false) + @Setter @Column(nullable = false, unique = false) private String lastName; - @Setter @Column(nullable = true, unique = true) + @Setter @Column(nullable = false, unique = true) private String username; - @Setter @Column(nullable = true, unique = true) + @Setter @Column(nullable = false, unique = true) private String email; @Setter @Column(nullable = false, unique = false) @@ -62,8 +62,6 @@ public Collection getAuthorities() { ); } else if (this.role == role.USER) { return List.of(new SimpleGrantedAuthority("ROLE_USER")); - } else { - return List.of(new SimpleGrantedAuthority("ROLE_ENTERPRISE")); } } From 321ee170414635d3047434f40bacee9a10007a8f Mon Sep 17 00:00:00 2001 From: SD-W1972 Date: Mon, 14 Apr 2025 21:32:23 -0300 Subject: [PATCH 17/42] create basic logic to GeminiService --- .../backend/controller/GeminiController.java | 92 +++---------------- .../backend/model/ChatHistoryResponse.java | 5 + .../backend/service/GeminiService.java | 86 +++++++++++++++++ src/main/resources/application.properties | 2 +- 4 files changed, 107 insertions(+), 78 deletions(-) create mode 100644 src/main/java/com/kuarion/backend/model/ChatHistoryResponse.java create mode 100644 src/main/java/com/kuarion/backend/service/GeminiService.java diff --git a/src/main/java/com/kuarion/backend/controller/GeminiController.java b/src/main/java/com/kuarion/backend/controller/GeminiController.java index 82471cf..be3617e 100644 --- a/src/main/java/com/kuarion/backend/controller/GeminiController.java +++ b/src/main/java/com/kuarion/backend/controller/GeminiController.java @@ -1,94 +1,32 @@ package com.kuarion.backend.controller; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestClient; +import org.springframework.web.servlet.ModelAndView; import com.kuarion.backend.model.ChatRequest; -import com.kuarion.backend.model.GeminiResponse; +import com.kuarion.backend.model.ChatResponse; +import com.kuarion.backend.service.GeminiService; @RestController public class GeminiController { - private static final Logger log = LoggerFactory.getLogger(GeminiController.class); - - public record ChatResponse(String response) {} - - @Value("${spring.ai.openai.api-key}") - private String geminiApiKey; - - @Value("${gemini.api.model}") - private String geminiApiModel; - - @Value("${gemini.api.base-url}") - private String geminiApiUrl; - - - private final RestClient restClient; - - public GeminiController(RestClient.Builder builder) { - this.restClient = builder - .baseUrl("https://generativelanguage.googleapis.com") - .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) - .build(); + @Autowired + private GeminiService geminiService; + + @RequestMapping("/api/chat") + public ModelAndView test() { + ModelAndView mdv = new ModelAndView(); + mdv.setViewName("test"); + return mdv; } - - + @PostMapping("/api/chat") public ChatResponse askGemini(@RequestBody ChatRequest request) { - try { - String systemPrompt = "Você é um assistente especializado em energia solar. Siga as regras: Regras: responda de forma técnica mas acessível, com foco em São Paulo.\\n\\n\""; - - String requestBody = String.format(""" - { - "contents": [{ - "parts": [{ - "text": "%s" - }] - }], - "system_instruction": { - "parts": [{ - "text": "%s" - }] - } - } - """, - request.message().replace("\"", "\\\""), - systemPrompt.replace("\"", "\\\"") - ); - - // URI atualizada para a versão mais recente da API - GeminiResponse response = restClient.post() - .uri("/v1beta/models/{model}:generateContent?key={key}", - geminiApiModel, geminiApiKey) - .body(requestBody) - .retrieve() - .body(GeminiResponse.class); - - - if (response != null && - !response.candidates().isEmpty() && - !response.candidates().get(0).content().parts().isEmpty()) { - String textResponse = response.candidates().get(0).content().parts().get(0).text(); - return new ChatResponse(textResponse); - } - - return new ChatResponse("Não foi possível obter uma resposta do Gemini."); - - } catch (HttpClientErrorException e) { - log.error("Erro na chamada à API do Gemini: {}", e.getResponseBodyAsString()); - return new ChatResponse("Erro ao comunicar com o Gemini: " + e.getMessage()); - } catch (Exception e) { - log.error("Erro inesperado", e); - return new ChatResponse("Erro interno no servidor"); - } + return geminiService.askGemini(request); } -} +} \ No newline at end of file diff --git a/src/main/java/com/kuarion/backend/model/ChatHistoryResponse.java b/src/main/java/com/kuarion/backend/model/ChatHistoryResponse.java new file mode 100644 index 0000000..4aded3b --- /dev/null +++ b/src/main/java/com/kuarion/backend/model/ChatHistoryResponse.java @@ -0,0 +1,5 @@ +package com.kuarion.backend.model; + +public record ChatHistoryResponse() { + +} diff --git a/src/main/java/com/kuarion/backend/service/GeminiService.java b/src/main/java/com/kuarion/backend/service/GeminiService.java new file mode 100644 index 0000000..e6d1c61 --- /dev/null +++ b/src/main/java/com/kuarion/backend/service/GeminiService.java @@ -0,0 +1,86 @@ +package com.kuarion.backend.service; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Service; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestClient; + +import com.kuarion.backend.model.ChatRequest; +import com.kuarion.backend.model.GeminiResponse; +import com.kuarion.backend.model.ChatResponse; + +@Service +public class GeminiService { + private static final Logger log = LoggerFactory.getLogger(GeminiService.class); // Corrigido para GeminiService + + @Value("${spring.ai.openai.api-key}") + private String geminiApiKey; + + @Value("${gemini.api.model}") + private String geminiApiModel; + + @Value("${gemini.api.base-url}") + private String geminiApiUrl; + + private final RestClient restClient; + + public GeminiService(RestClient.Builder builder) { + this.restClient = builder + .baseUrl("https://generativelanguage.googleapis.com") + .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .build(); + } + + public ChatResponse askGemini(ChatRequest request) { + try { + String systemPrompt = "Você é um assistente especializado em energia solar. Siga as regras: Regras: responda de forma técnica mas acessível, com foco em São Paulo.\n\n"; + + String requestBody = String.format(""" + { + "contents": [{ + "parts": [{ + "text": "%s" + }] + }], + "system_instruction": { + "parts": [{ + "text": "%s" + }] + } + } + """, + request.message().replace("\"", "\\\""), + systemPrompt.replace("\"", "\\\"") + ); + + GeminiResponse response = restClient.post() + .uri("/v1beta/models/{model}:generateContent?key={key}", + geminiApiModel, geminiApiKey) + .body(requestBody) + .retrieve() + .body(GeminiResponse.class); + + if (response != null && + !response.candidates().isEmpty() && + !response.candidates().get(0).content().parts().isEmpty()) { + String textResponse = response.candidates().get(0).content().parts().get(0).text(); + return new ChatResponse(textResponse); + } + + return new ChatResponse("Não foi possível obter uma resposta do Gemini."); + + } catch (HttpClientErrorException e) { + log.error("Erro na chamada à API do Gemini: {}", e.getResponseBodyAsString()); + return new ChatResponse("Erro ao comunicar com o Gemini: " + e.getMessage()); + } catch (Exception e) { + log.error("Erro inesperado", e); + return new ChatResponse("Erro interno no servidor"); + } + } + + +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index c92e6d6..516d120 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -24,7 +24,7 @@ spring.jpa.open-in-view=false cors.origins=${CORS_ORIGINS:http://localhost:5173,http://localhost:3000} # Server port -server.port:8081 +server.port:8080 spring.config.import=application-local.properties gemini.api.base-url=https://generativelanguage.googleapis.com From afc0853041f7a8a53f7d523767230d32c83dfee5 Mon Sep 17 00:00:00 2001 From: SD-W1972 Date: Mon, 14 Apr 2025 21:37:04 -0300 Subject: [PATCH 18/42] creating records to later configure get endpoints --- src/main/java/com/kuarion/backend/model/ChatExchange.java | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/main/java/com/kuarion/backend/model/ChatExchange.java diff --git a/src/main/java/com/kuarion/backend/model/ChatExchange.java b/src/main/java/com/kuarion/backend/model/ChatExchange.java new file mode 100644 index 0000000..b80a31d --- /dev/null +++ b/src/main/java/com/kuarion/backend/model/ChatExchange.java @@ -0,0 +1,7 @@ +package com.kuarion.backend.model; + +import java.time.LocalDateTime; + +public record ChatExchange(String userMessage, String botResponse, LocalDateTime timestamp) { + +} From 29cd672194aee97cd313751e4a592a978307e56c Mon Sep 17 00:00:00 2001 From: SD-W1972 Date: Mon, 14 Apr 2025 21:46:51 -0300 Subject: [PATCH 19/42] implemented logic to get endpoints and chatbot message history --- .../backend/controller/GeminiController.java | 51 ++++++++++++------- .../backend/model/ChatHistoryResponse.java | 4 +- .../kuarion/backend/model/ChatResponse.java | 4 +- .../kuarion/backend/service/ChatService.java | 50 ++++++++++++++++++ .../backend/service/GeminiService.java | 30 ++++------- 5 files changed, 100 insertions(+), 39 deletions(-) create mode 100644 src/main/java/com/kuarion/backend/service/ChatService.java diff --git a/src/main/java/com/kuarion/backend/controller/GeminiController.java b/src/main/java/com/kuarion/backend/controller/GeminiController.java index be3617e..bfa82df 100644 --- a/src/main/java/com/kuarion/backend/controller/GeminiController.java +++ b/src/main/java/com/kuarion/backend/controller/GeminiController.java @@ -1,32 +1,47 @@ package com.kuarion.backend.controller; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.client.RestClient; -import org.springframework.web.servlet.ModelAndView; +import com.kuarion.backend.model.ChatHistoryResponse; import com.kuarion.backend.model.ChatRequest; import com.kuarion.backend.model.ChatResponse; -import com.kuarion.backend.service.GeminiService; +import com.kuarion.backend.service.ChatService; @RestController public class GeminiController { + + private final ChatService chatService; + + + @Autowired + public GeminiController(ChatService chatService) { + super(); + this.chatService = chatService; + } + + + @PostMapping("/api/chat") + public ResponseEntity sendMessage(@RequestBody ChatRequest request) { + ChatResponse response = chatService.processUserMessage(request); + return ResponseEntity.ok(response); + } - @Autowired - private GeminiService geminiService; - - @RequestMapping("/api/chat") - public ModelAndView test() { - ModelAndView mdv = new ModelAndView(); - mdv.setViewName("test"); - return mdv; - } - - @PostMapping("/api/chat") - public ChatResponse askGemini(@RequestBody ChatRequest request) { - return geminiService.askGemini(request); - } + @GetMapping("/history") + public ResponseEntity getChatHistory() { + ChatHistoryResponse history = chatService.getChatHistory(); + return ResponseEntity.ok(history); + } + + @DeleteMapping("/history") + public ResponseEntity clearChatHistory() { + chatService.clearChatHistory(); + return ResponseEntity.noContent().build(); + } + } \ No newline at end of file diff --git a/src/main/java/com/kuarion/backend/model/ChatHistoryResponse.java b/src/main/java/com/kuarion/backend/model/ChatHistoryResponse.java index 4aded3b..c07c8d7 100644 --- a/src/main/java/com/kuarion/backend/model/ChatHistoryResponse.java +++ b/src/main/java/com/kuarion/backend/model/ChatHistoryResponse.java @@ -1,5 +1,7 @@ package com.kuarion.backend.model; -public record ChatHistoryResponse() { +import java.util.List; + +public record ChatHistoryResponse(List exchanges) { } diff --git a/src/main/java/com/kuarion/backend/model/ChatResponse.java b/src/main/java/com/kuarion/backend/model/ChatResponse.java index 0870db9..8b801d1 100644 --- a/src/main/java/com/kuarion/backend/model/ChatResponse.java +++ b/src/main/java/com/kuarion/backend/model/ChatResponse.java @@ -1,5 +1,7 @@ package com.kuarion.backend.model; -public record ChatResponse(String response) { +import java.time.LocalDateTime; + +public record ChatResponse(String response, LocalDateTime timestamp) { } diff --git a/src/main/java/com/kuarion/backend/service/ChatService.java b/src/main/java/com/kuarion/backend/service/ChatService.java new file mode 100644 index 0000000..c10465a --- /dev/null +++ b/src/main/java/com/kuarion/backend/service/ChatService.java @@ -0,0 +1,50 @@ +package com.kuarion.backend.service; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +import org.springframework.stereotype.Service; + +import com.kuarion.backend.model.ChatExchange; +import com.kuarion.backend.model.ChatHistoryResponse; +import com.kuarion.backend.model.ChatRequest; +import com.kuarion.backend.model.ChatResponse; + +@Service +public class ChatService { + + private final GeminiService geminiService; + private final List chatHistory = new ArrayList<>(); + + public ChatService(GeminiService geminiService) { + super(); + this.geminiService = geminiService; + } + + public ChatResponse processUserMessage(ChatRequest request) { + // using geminiService to process and get it's answer + String botResponse = geminiService.getGeminiResponse(request.message()); + + // Registering the exchange between request - response + ChatExchange exchange = new ChatExchange( + request.message(), + botResponse, + LocalDateTime.now() + ); + + // Add to history + chatHistory.add(exchange); + + // return answer - back to first step + return new ChatResponse(botResponse, LocalDateTime.now()); + } + + public ChatHistoryResponse getChatHistory() { + return new ChatHistoryResponse(List.copyOf(chatHistory)); + } + + public void clearChatHistory() { + chatHistory.clear(); + } +} diff --git a/src/main/java/com/kuarion/backend/service/GeminiService.java b/src/main/java/com/kuarion/backend/service/GeminiService.java index e6d1c61..890d58b 100644 --- a/src/main/java/com/kuarion/backend/service/GeminiService.java +++ b/src/main/java/com/kuarion/backend/service/GeminiService.java @@ -35,10 +35,10 @@ public GeminiService(RestClient.Builder builder) { .build(); } - public ChatResponse askGemini(ChatRequest request) { + public String getGeminiResponse(String userMessage) { try { - String systemPrompt = "Você é um assistente especializado em energia solar. Siga as regras: Regras: responda de forma técnica mas acessível, com foco em São Paulo.\n\n"; - + String systemPrompt = "Você é um assistente especializado em energia solar..."; + String requestBody = String.format(""" { "contents": [{ @@ -53,10 +53,10 @@ public ChatResponse askGemini(ChatRequest request) { } } """, - request.message().replace("\"", "\\\""), + userMessage.replace("\"", "\\\""), systemPrompt.replace("\"", "\\\"") ); - + GeminiResponse response = restClient.post() .uri("/v1beta/models/{model}:generateContent?key={key}", geminiApiModel, geminiApiKey) @@ -64,23 +64,15 @@ public ChatResponse askGemini(ChatRequest request) { .retrieve() .body(GeminiResponse.class); - if (response != null && - !response.candidates().isEmpty() && - !response.candidates().get(0).content().parts().isEmpty()) { - String textResponse = response.candidates().get(0).content().parts().get(0).text(); - return new ChatResponse(textResponse); + if (response != null && !response.candidates().isEmpty()) { + return response.candidates().get(0).content().parts().get(0).text(); } + return "Não foi possível obter uma resposta do Gemini."; - return new ChatResponse("Não foi possível obter uma resposta do Gemini."); - - } catch (HttpClientErrorException e) { - log.error("Erro na chamada à API do Gemini: {}", e.getResponseBodyAsString()); - return new ChatResponse("Erro ao comunicar com o Gemini: " + e.getMessage()); } catch (Exception e) { - log.error("Erro inesperado", e); - return new ChatResponse("Erro interno no servidor"); + log.error("Erro ao chamar API do Gemini", e); + return "Desculpe, estou com problemas técnicos. Tente novamente mais tarde."; } - } - + } } \ No newline at end of file From b41131ebe6c2de431eac55cd7a6ecbb215ce2b8a Mon Sep 17 00:00:00 2001 From: SD-W1972 Date: Mon, 14 Apr 2025 21:51:58 -0300 Subject: [PATCH 20/42] updated security config to allow the new endpoints --- .../java/com/kuarion/backend/config/SecurityConfig.java | 5 +++-- .../com/kuarion/backend/controller/GeminiController.java | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/kuarion/backend/config/SecurityConfig.java b/src/main/java/com/kuarion/backend/config/SecurityConfig.java index e552d5f..ec3e977 100644 --- a/src/main/java/com/kuarion/backend/config/SecurityConfig.java +++ b/src/main/java/com/kuarion/backend/config/SecurityConfig.java @@ -30,8 +30,9 @@ public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws // authentication based in token: stateless security policy .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth - .requestMatchers(HttpMethod.GET, "/", "/login", "/index", "/api/chat").permitAll() - .requestMatchers(HttpMethod.POST, "/authentication/**", "/api/chat").permitAll() + .requestMatchers(HttpMethod.GET, "/", "/login", "/index", "/api/chat/message", "api/chat/history").permitAll() + .requestMatchers(HttpMethod.POST, "/authentication/**", "/api/chat/message").permitAll() + .requestMatchers(HttpMethod.DELETE, "/api/chat/history/delete").permitAll() .requestMatchers(HttpMethod.GET, "/dashboard").authenticated() .anyRequest().denyAll() ) diff --git a/src/main/java/com/kuarion/backend/controller/GeminiController.java b/src/main/java/com/kuarion/backend/controller/GeminiController.java index bfa82df..7b17f81 100644 --- a/src/main/java/com/kuarion/backend/controller/GeminiController.java +++ b/src/main/java/com/kuarion/backend/controller/GeminiController.java @@ -26,19 +26,19 @@ public GeminiController(ChatService chatService) { } - @PostMapping("/api/chat") + @PostMapping("/api/chat/message") public ResponseEntity sendMessage(@RequestBody ChatRequest request) { ChatResponse response = chatService.processUserMessage(request); return ResponseEntity.ok(response); } - @GetMapping("/history") + @GetMapping("/api/chat/history") public ResponseEntity getChatHistory() { ChatHistoryResponse history = chatService.getChatHistory(); return ResponseEntity.ok(history); } - @DeleteMapping("/history") + @DeleteMapping("/api/chat/history") public ResponseEntity clearChatHistory() { chatService.clearChatHistory(); return ResponseEntity.noContent().build(); From 9c7ca7ab7d8c02e2528c61a908a6528894469f3d Mon Sep 17 00:00:00 2001 From: SD-W1972 Date: Mon, 14 Apr 2025 23:44:59 -0300 Subject: [PATCH 21/42] creating entities to user survey mechanism --- .../backend/config/SecurityConfig.java | 18 ++-- .../com/kuarion/backend/entities/Answer.java | 72 +++++++++++++++ .../kuarion/backend/entities/Question.java | 50 +++++++++++ .../backend/entities/SurveyAnswers.java | 78 ++++++++++++++++ .../backend/entities/UserStatistics.java | 88 ------------------- .../repositories/SurveyAnswersRepository.java | 9 ++ .../kuarion/backend/roles/QuestionType.java | 28 ++++++ 7 files changed, 248 insertions(+), 95 deletions(-) create mode 100644 src/main/java/com/kuarion/backend/entities/Answer.java create mode 100644 src/main/java/com/kuarion/backend/entities/Question.java create mode 100644 src/main/java/com/kuarion/backend/entities/SurveyAnswers.java delete mode 100644 src/main/java/com/kuarion/backend/entities/UserStatistics.java create mode 100644 src/main/java/com/kuarion/backend/repositories/SurveyAnswersRepository.java create mode 100644 src/main/java/com/kuarion/backend/roles/QuestionType.java diff --git a/src/main/java/com/kuarion/backend/config/SecurityConfig.java b/src/main/java/com/kuarion/backend/config/SecurityConfig.java index ec3e977..bf7276c 100644 --- a/src/main/java/com/kuarion/backend/config/SecurityConfig.java +++ b/src/main/java/com/kuarion/backend/config/SecurityConfig.java @@ -1,17 +1,19 @@ package com.kuarion.backend.config; import org.springframework.context.annotation.Bean; -import org.springframework.http.HttpMethod; import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.web.SecurityFilterChain; +import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration @EnableWebSecurity @@ -30,7 +32,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws // authentication based in token: stateless security policy .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth - .requestMatchers(HttpMethod.GET, "/", "/login", "/index", "/api/chat/message", "api/chat/history").permitAll() + .requestMatchers(HttpMethod.GET, "/", "/login", "/index", "/api/chat/message", "/api/chat/history").permitAll() .requestMatchers(HttpMethod.POST, "/authentication/**", "/api/chat/message").permitAll() .requestMatchers(HttpMethod.DELETE, "/api/chat/history/delete").permitAll() .requestMatchers(HttpMethod.GET, "/dashboard").authenticated() @@ -49,6 +51,8 @@ public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws .build(); } + + @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { return authenticationConfiguration.getAuthenticationManager(); diff --git a/src/main/java/com/kuarion/backend/entities/Answer.java b/src/main/java/com/kuarion/backend/entities/Answer.java new file mode 100644 index 0000000..774ffe4 --- /dev/null +++ b/src/main/java/com/kuarion/backend/entities/Answer.java @@ -0,0 +1,72 @@ +package com.kuarion.backend.entities; + +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; + +public class Answer { + + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + + @ManyToOne + @JoinColumn(name = "reponse_id") + private SurveyAnswers response; + + @ManyToOne + private Question question; + + private String answer; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public SurveyAnswers getResponse() { + return response; + } + + public void setResponse(SurveyAnswers response) { + this.response = response; + } + + public Question getQuestion() { + return question; + } + + public void setQuestion(Question question) { + this.question = question; + } + + public String getAnswer() { + return answer; + } + + public void setAnswer(String answer) { + this.answer = answer; + } + + public Answer(Long id, SurveyAnswers response, Question question, String answer) { + super(); + this.id = id; + this.response = response; + this.question = question; + this.answer = answer; + } + + public Answer() { + super(); + // TODO Auto-generated constructor stub + } + + +} diff --git a/src/main/java/com/kuarion/backend/entities/Question.java b/src/main/java/com/kuarion/backend/entities/Question.java new file mode 100644 index 0000000..a14b702 --- /dev/null +++ b/src/main/java/com/kuarion/backend/entities/Question.java @@ -0,0 +1,50 @@ +package com.kuarion.backend.entities; + +import com.kuarion.backend.roles.QuestionType; + +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; + +public class Question { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String text; + private QuestionType type; + + + public Long getId() { + return id; + } + public void setId(Long id) { + this.id = id; + } + public String getText() { + return text; + } + public void setText(String text) { + this.text = text; + } + public QuestionType getType() { + return type; + } + public void setType(QuestionType type) { + this.type = type; + } + public Question(Long id, String text, QuestionType type) { + super(); + this.id = id; + this.text = text; + this.type = type; + } + public Question() { + super(); + // TODO Auto-generated constructor stub + } + + + +} diff --git a/src/main/java/com/kuarion/backend/entities/SurveyAnswers.java b/src/main/java/com/kuarion/backend/entities/SurveyAnswers.java new file mode 100644 index 0000000..e7ddd55 --- /dev/null +++ b/src/main/java/com/kuarion/backend/entities/SurveyAnswers.java @@ -0,0 +1,78 @@ +package com.kuarion.backend.entities; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToMany; +import jakarta.persistence.OneToOne; + +@Entity +public class SurveyAnswers { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @OneToOne + @JoinColumn(name = "user_id") + private User user; + + @OneToMany(mappedBy = "response", cascade = CascadeType.ALL) + private List answers = new ArrayList<>(); + + private LocalDateTime responseData; + + public SurveyAnswers() { + super(); + } + + public SurveyAnswers(Long id, User user, List answers, LocalDateTime responseData) { + super(); + this.id = id; + this.user = user; + this.answers = answers; + this.responseData = responseData; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public User getUser() { + return user; + } + + public void setUser(User user) { + this.user = user; + } + + public List getAnswers() { + return answers; + } + + public void setAnswers(List answers) { + this.answers = answers; + } + + public LocalDateTime getResponseData() { + return responseData; + } + + public void setResponseData(LocalDateTime responseData) { + this.responseData = responseData; + } + + + +} \ No newline at end of file diff --git a/src/main/java/com/kuarion/backend/entities/UserStatistics.java b/src/main/java/com/kuarion/backend/entities/UserStatistics.java deleted file mode 100644 index 0b081f9..0000000 --- a/src/main/java/com/kuarion/backend/entities/UserStatistics.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.kuarion.backend.entities; - -import jakarta.persistence.*; - -@Entity -public class UserStatistics { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - /* - @OneToOne - @JoinColumn(name = "user_id") // Esta é a coluna de FK na tabela user_statistics - private User user; - */ - private String dado1; - private String dado2; - private String dado3; - private String dado4; - private String dado5; - - - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getDado1() { - return dado1; - } - - public void setDado1(String dado1) { - this.dado1 = dado1; - } - - public String getDado2() { - return dado2; - } - - public void setDado2(String dado2) { - this.dado2 = dado2; - } - - public String getDado3() { - return dado3; - } - - public void setDado3(String dado3) { - this.dado3 = dado3; - } - - public String getDado4() { - return dado4; - } - - public void setDado4(String dado4) { - this.dado4 = dado4; - } - - public String getDado5() { - return dado5; - } - - public void setDado5(String dado5) { - this.dado5 = dado5; - } - - public UserStatistics() { - super(); - } - - /* - public User getUser() { - return user; - } - - public void setUser(User user) { - this.user = user; - } */ - //aaaaaaaaaaaa - - -} \ No newline at end of file diff --git a/src/main/java/com/kuarion/backend/repositories/SurveyAnswersRepository.java b/src/main/java/com/kuarion/backend/repositories/SurveyAnswersRepository.java new file mode 100644 index 0000000..290194c --- /dev/null +++ b/src/main/java/com/kuarion/backend/repositories/SurveyAnswersRepository.java @@ -0,0 +1,9 @@ +package com.kuarion.backend.repositories; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.kuarion.backend.entities.SurveyAnswers; + +public interface SurveyAnswersRepository extends JpaRepository { + +} diff --git a/src/main/java/com/kuarion/backend/roles/QuestionType.java b/src/main/java/com/kuarion/backend/roles/QuestionType.java new file mode 100644 index 0000000..57c6366 --- /dev/null +++ b/src/main/java/com/kuarion/backend/roles/QuestionType.java @@ -0,0 +1,28 @@ +package com.kuarion.backend.roles; + +public enum QuestionType { + MULTIPLE_CHOICE("QUESTION_MULTIPLE_CHOICE"), + TEXT("QUESTION_TEXT"); + + private final String type; + + QuestionType(String type) { + this.type = type; + } + + // require method from GrantedAuthority. It returns the string "type" + + public String getType() { + return this.type; + } + + // method to get a role (constant enum) from a string + public static QuestionType fromString(String type) { + for (QuestionType questionType : QuestionType.values()) { + if (questionType.type.equalsIgnoreCase(type)) { + return questionType; + } + } + throw new IllegalArgumentException("Invalid question type: " + type); + } +} From bad0b578989a66f883728fd8287d3ddb299f23fd Mon Sep 17 00:00:00 2001 From: SD-W1972 Date: Tue, 15 Apr 2025 00:27:47 -0300 Subject: [PATCH 22/42] creating service and repositories to interact with survey data --- .../repositories/AnswerRepository.java | 12 +++ .../repositories/QuestionRepository.java | 9 +++ .../repositories/SurveyAnswersRepository.java | 6 +- .../backend/service/SurveyService.java | 81 +++++++++++++++++++ 4 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/kuarion/backend/repositories/AnswerRepository.java create mode 100644 src/main/java/com/kuarion/backend/repositories/QuestionRepository.java create mode 100644 src/main/java/com/kuarion/backend/service/SurveyService.java diff --git a/src/main/java/com/kuarion/backend/repositories/AnswerRepository.java b/src/main/java/com/kuarion/backend/repositories/AnswerRepository.java new file mode 100644 index 0000000..83a50cb --- /dev/null +++ b/src/main/java/com/kuarion/backend/repositories/AnswerRepository.java @@ -0,0 +1,12 @@ +package com.kuarion.backend.repositories; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.kuarion.backend.entities.Answer; +import com.kuarion.backend.entities.Question; + +public interface AnswerRepository extends JpaRepository{ + List findByQuestion(Question question); +} diff --git a/src/main/java/com/kuarion/backend/repositories/QuestionRepository.java b/src/main/java/com/kuarion/backend/repositories/QuestionRepository.java new file mode 100644 index 0000000..1148c9e --- /dev/null +++ b/src/main/java/com/kuarion/backend/repositories/QuestionRepository.java @@ -0,0 +1,9 @@ +package com.kuarion.backend.repositories; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.kuarion.backend.entities.Question; + +public interface QuestionRepository extends JpaRepository{ + +} diff --git a/src/main/java/com/kuarion/backend/repositories/SurveyAnswersRepository.java b/src/main/java/com/kuarion/backend/repositories/SurveyAnswersRepository.java index 290194c..5db79e6 100644 --- a/src/main/java/com/kuarion/backend/repositories/SurveyAnswersRepository.java +++ b/src/main/java/com/kuarion/backend/repositories/SurveyAnswersRepository.java @@ -1,9 +1,13 @@ package com.kuarion.backend.repositories; +import java.util.Optional; + import org.springframework.data.jpa.repository.JpaRepository; import com.kuarion.backend.entities.SurveyAnswers; +import com.kuarion.backend.entities.User; public interface SurveyAnswersRepository extends JpaRepository { - + boolean existsByUser(User user); + Optional findByUser(User user); } diff --git a/src/main/java/com/kuarion/backend/service/SurveyService.java b/src/main/java/com/kuarion/backend/service/SurveyService.java new file mode 100644 index 0000000..4fde161 --- /dev/null +++ b/src/main/java/com/kuarion/backend/service/SurveyService.java @@ -0,0 +1,81 @@ +package com.kuarion.backend.service; + +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.springframework.beans.factory.annotation.Autowired; + +import com.kuarion.backend.entities.Answer; +import com.kuarion.backend.entities.Question; +import com.kuarion.backend.entities.SurveyAnswers; +import com.kuarion.backend.entities.User; +import com.kuarion.backend.repositories.AnswerRepository; +import com.kuarion.backend.repositories.QuestionRepository; +import com.kuarion.backend.repositories.SurveyAnswersRepository; +import com.kuarion.backend.repositories.UserRepository; + +public class SurveyService { + + @Autowired + private SurveyAnswersRepository surveyRepository; + + @Autowired + private UserRepository userRepository; + + @Autowired + private QuestionRepository questionRepository; + + @Autowired + private AnswerRepository answerRepository; + + public Boolean hasResponded(Long userId) { + User user = userRepository.findById(userId).orElseThrow(() -> new RuntimeException("Usuario não encontrado!")); + return surveyRepository.existsByUser(user); + } + + public SurveyAnswers submitSurveyAnswer(Long userId, Map answers) { + if(hasResponded(userId)) { + throw new IllegalStateException("User has already responded to the questionnaire"); + } + + User user = userRepository.findById(userId).orElseThrow(() -> new RuntimeException("Usuario não encontrado !")); + + SurveyAnswers surveyAnswers = new SurveyAnswers(); + surveyAnswers.setUser(user); + surveyAnswers.setResponseData(LocalDateTime.now()); + surveyAnswers = surveyRepository.save(surveyAnswers); + + for(Map.Entry entry : answers.entrySet()) { + Question question = questionRepository.findById(entry.getKey()) + .orElseThrow(() -> new RuntimeException("Questão não encontrada!")); + + Answer answer = new Answer(); + answer.setResponse(surveyAnswers); + answer.setQuestion(question); + answer.setAnswer(entry.getValue()); + surveyAnswers.getAnswers().add(answer); + } + + return surveyRepository.save(surveyAnswers); + } + + public Map> getQuestionStatistics(){ + List questions = questionRepository.findAll(); + Map> statistics = new HashMap<>(); + + for(Question question : questions) { + List answers = answerRepository.findByQuestion(question); + Map countMap = answers.stream() + .collect(Collectors.groupingBy(Answer::getAnswer, Collectors.counting())); + + statistics.put(question, countMap); + } + + return statistics; + } + + +} From 2b323b3ce39a30dbd1aa26b29d146ba294d6eed4 Mon Sep 17 00:00:00 2001 From: SD-W1972 Date: Tue, 15 Apr 2025 00:47:06 -0300 Subject: [PATCH 23/42] created endpoints on survey controller, bug fix on every Entity --- .../backend/controller/SurveyController.java | 51 +++++++++++++++++++ .../com/kuarion/backend/entities/Answer.java | 2 + .../kuarion/backend/entities/Question.java | 2 + .../com/kuarion/backend/entities/User.java | 21 +++++++- .../backend/service/SurveyService.java | 2 + 5 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/kuarion/backend/controller/SurveyController.java diff --git a/src/main/java/com/kuarion/backend/controller/SurveyController.java b/src/main/java/com/kuarion/backend/controller/SurveyController.java new file mode 100644 index 0000000..c2c60a2 --- /dev/null +++ b/src/main/java/com/kuarion/backend/controller/SurveyController.java @@ -0,0 +1,51 @@ +package com.kuarion.backend.controller; + +import java.util.Collections; +import java.util.Map; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import com.kuarion.backend.entities.Question; +import com.kuarion.backend.entities.SurveyAnswers; +import com.kuarion.backend.service.SurveyService; + +@RestController +public class SurveyController { + + @Autowired + private SurveyService surveyService; + + + @GetMapping("/status/{userId}") + public ResponseEntity checkResponseStatus(@PathVariable Long userId){ + boolean hasResponded = surveyService.hasResponded(userId); + return ResponseEntity.ok(Collections.singletonMap("hasResponded", hasResponded)); + + } + + @PostMapping("/submit/{userId}") + public ResponseEntity submitQuestionnaire( + @PathVariable Long userId, + @RequestBody Map answers) { + + try { + SurveyAnswers response = surveyService.submitSurveyAnswer(userId, answers); + return ResponseEntity.ok(response); + } catch (IllegalStateException e) { + return ResponseEntity.badRequest().body(e.getMessage()); + } + } + + @GetMapping("/statistics") + public ResponseEntity getStatistics() { + Map> statistics = surveyService.getQuestionStatistics(); + return ResponseEntity.ok(statistics); + } + +} diff --git a/src/main/java/com/kuarion/backend/entities/Answer.java b/src/main/java/com/kuarion/backend/entities/Answer.java index 774ffe4..390bab9 100644 --- a/src/main/java/com/kuarion/backend/entities/Answer.java +++ b/src/main/java/com/kuarion/backend/entities/Answer.java @@ -1,11 +1,13 @@ package com.kuarion.backend.entities; +import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; +@Entity public class Answer { diff --git a/src/main/java/com/kuarion/backend/entities/Question.java b/src/main/java/com/kuarion/backend/entities/Question.java index a14b702..ebb797a 100644 --- a/src/main/java/com/kuarion/backend/entities/Question.java +++ b/src/main/java/com/kuarion/backend/entities/Question.java @@ -2,10 +2,12 @@ import com.kuarion.backend.roles.QuestionType; +import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +@Entity public class Question { @Id diff --git a/src/main/java/com/kuarion/backend/entities/User.java b/src/main/java/com/kuarion/backend/entities/User.java index 1a2c738..1446b2a 100644 --- a/src/main/java/com/kuarion/backend/entities/User.java +++ b/src/main/java/com/kuarion/backend/entities/User.java @@ -9,6 +9,7 @@ import com.kuarion.backend.roles.Roles; +import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; @@ -16,6 +17,7 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import jakarta.persistence.OneToOne; import jakarta.persistence.Table; import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; @@ -48,7 +50,24 @@ public class User implements UserDetails { @Setter @Enumerated(EnumType.STRING) private Roles role; // user role - // required methods from UserDetails interface + @OneToOne(mappedBy = "user", cascade = CascadeType.ALL) + private SurveyAnswers surveyAnswers; + + + + public SurveyAnswers getSurveyAnswers() { + return surveyAnswers; +} + +public void setSurveyAnswers(SurveyAnswers surveyAnswers) { + this.surveyAnswers = surveyAnswers; +} + +public Long getId() { + return id; +} + +// required methods from UserDetails interface @Override public Collection getAuthorities() { diff --git a/src/main/java/com/kuarion/backend/service/SurveyService.java b/src/main/java/com/kuarion/backend/service/SurveyService.java index 4fde161..8abe708 100644 --- a/src/main/java/com/kuarion/backend/service/SurveyService.java +++ b/src/main/java/com/kuarion/backend/service/SurveyService.java @@ -7,6 +7,7 @@ import java.util.stream.Collectors; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; import com.kuarion.backend.entities.Answer; import com.kuarion.backend.entities.Question; @@ -17,6 +18,7 @@ import com.kuarion.backend.repositories.SurveyAnswersRepository; import com.kuarion.backend.repositories.UserRepository; +@Service public class SurveyService { @Autowired From 3edb9b54a9d09b43ee3a4b1fb249c57b1030cc47 Mon Sep 17 00:00:00 2001 From: Rafael Medeiros Date: Tue, 15 Apr 2025 01:06:19 -0300 Subject: [PATCH 24/42] feat: started the front-end and defined some routes for testing in the back-end --- .../statics/kuarion-front-end/.gitignore | 24 + .../statics/kuarion-front-end/README.md | 12 + .../kuarion-front-end/eslint.config.js | 33 + .../statics/kuarion-front-end/index.html | 13 + .../kuarion-front-end/package-lock.json | 2814 +++++++++++++++++ .../statics/kuarion-front-end/package.json | 28 + .../kuarion-front-end/public/Kuarion.svg | 34 + .../statics/kuarion-front-end/src/App.css | 0 .../statics/kuarion-front-end/src/App.jsx | 15 + .../statics/kuarion-front-end/src/Router.jsx | 15 + .../src/assets/kuarion-logo.png | Bin 0 -> 19769 bytes .../statics/kuarion-front-end/src/index.css | 0 .../statics/kuarion-front-end/src/main.jsx | 10 + .../src/pages/Index/Index.jsx | 7 + .../src/pages/Login/Login.jsx | 7 + .../src/pages/NotFound/NotFound.jsx | 7 + .../statics/kuarion-front-end/vite.config.js | 10 + 17 files changed, 3029 insertions(+) create mode 100644 src/main/resources/statics/kuarion-front-end/.gitignore create mode 100644 src/main/resources/statics/kuarion-front-end/README.md create mode 100644 src/main/resources/statics/kuarion-front-end/eslint.config.js create mode 100644 src/main/resources/statics/kuarion-front-end/index.html create mode 100644 src/main/resources/statics/kuarion-front-end/package-lock.json create mode 100644 src/main/resources/statics/kuarion-front-end/package.json create mode 100644 src/main/resources/statics/kuarion-front-end/public/Kuarion.svg create mode 100644 src/main/resources/statics/kuarion-front-end/src/App.css create mode 100644 src/main/resources/statics/kuarion-front-end/src/App.jsx create mode 100644 src/main/resources/statics/kuarion-front-end/src/Router.jsx create mode 100644 src/main/resources/statics/kuarion-front-end/src/assets/kuarion-logo.png create mode 100644 src/main/resources/statics/kuarion-front-end/src/index.css create mode 100644 src/main/resources/statics/kuarion-front-end/src/main.jsx create mode 100644 src/main/resources/statics/kuarion-front-end/src/pages/Index/Index.jsx create mode 100644 src/main/resources/statics/kuarion-front-end/src/pages/Login/Login.jsx create mode 100644 src/main/resources/statics/kuarion-front-end/src/pages/NotFound/NotFound.jsx create mode 100644 src/main/resources/statics/kuarion-front-end/vite.config.js diff --git a/src/main/resources/statics/kuarion-front-end/.gitignore b/src/main/resources/statics/kuarion-front-end/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/src/main/resources/statics/kuarion-front-end/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/src/main/resources/statics/kuarion-front-end/README.md b/src/main/resources/statics/kuarion-front-end/README.md new file mode 100644 index 0000000..fd3b758 --- /dev/null +++ b/src/main/resources/statics/kuarion-front-end/README.md @@ -0,0 +1,12 @@ +# React + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend using TypeScript and enable type-aware lint rules. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project. diff --git a/src/main/resources/statics/kuarion-front-end/eslint.config.js b/src/main/resources/statics/kuarion-front-end/eslint.config.js new file mode 100644 index 0000000..ec2b712 --- /dev/null +++ b/src/main/resources/statics/kuarion-front-end/eslint.config.js @@ -0,0 +1,33 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' + +export default [ + { ignores: ['dist'] }, + { + files: ['**/*.{js,jsx}'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + parserOptions: { + ecmaVersion: 'latest', + ecmaFeatures: { jsx: true }, + sourceType: 'module', + }, + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...js.configs.recommended.rules, + ...reactHooks.configs.recommended.rules, + 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }], + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, + }, +] diff --git a/src/main/resources/statics/kuarion-front-end/index.html b/src/main/resources/statics/kuarion-front-end/index.html new file mode 100644 index 0000000..f79f765 --- /dev/null +++ b/src/main/resources/statics/kuarion-front-end/index.html @@ -0,0 +1,13 @@ + + + + + + + Kuarion + + +
+ + + diff --git a/src/main/resources/statics/kuarion-front-end/package-lock.json b/src/main/resources/statics/kuarion-front-end/package-lock.json new file mode 100644 index 0000000..1828be5 --- /dev/null +++ b/src/main/resources/statics/kuarion-front-end/package-lock.json @@ -0,0 +1,2814 @@ +{ + "name": "kuarion-front-end", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "kuarion-front-end", + "version": "0.0.0", + "dependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-router-dom": "^7.5.0" + }, + "devDependencies": { + "@eslint/js": "^9.21.0", + "@types/react": "^19.0.10", + "@types/react-dom": "^19.0.4", + "@vitejs/plugin-react": "^4.3.4", + "eslint": "^9.21.0", + "eslint-plugin-react-hooks": "^5.1.0", + "eslint-plugin-react-refresh": "^0.4.19", + "globals": "^15.15.0", + "vite": "^6.2.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.26.8", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz", + "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", + "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.10", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.10", + "@babel/parser": "^7.26.10", + "@babel/template": "^7.26.9", + "@babel/traverse": "^7.26.10", + "@babel/types": "^7.26.10", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz", + "integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.27.0", + "@babel/types": "^7.27.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.0.tgz", + "integrity": "sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.26.8", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", + "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz", + "integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.0", + "@babel/types": "^7.27.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", + "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.9.tgz", + "integrity": "sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.25.9.tgz", + "integrity": "sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz", + "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/parser": "^7.27.0", + "@babel/types": "^7.27.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz", + "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.27.0", + "@babel/parser": "^7.27.0", + "@babel/template": "^7.27.0", + "@babel/types": "^7.27.0", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", + "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz", + "integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.2.tgz", + "integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.2.tgz", + "integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.2.tgz", + "integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.2.tgz", + "integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.2.tgz", + "integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.2.tgz", + "integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.2.tgz", + "integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.2.tgz", + "integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.2.tgz", + "integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.2.tgz", + "integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.2.tgz", + "integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.2.tgz", + "integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.2.tgz", + "integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.2.tgz", + "integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.2.tgz", + "integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.2.tgz", + "integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.2.tgz", + "integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.2.tgz", + "integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.2.tgz", + "integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.2.tgz", + "integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.2.tgz", + "integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.2.tgz", + "integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.2.tgz", + "integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.2.tgz", + "integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.6.0.tgz", + "integrity": "sha512-WhCn7Z7TauhBtmzhvKpoQs0Wwb/kBcy4CwpuI0/eEIr2Lx2auxmulAzLr91wVZJaz47iUZdkXOK7WlAfxGKCnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", + "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.1.tgz", + "integrity": "sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz", + "integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.24.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.24.0.tgz", + "integrity": "sha512-uIY/y3z0uvOGX8cp1C2fiC4+ZmBhp6yZWkojtHL1YEMnRt1Y63HB9TM17proGEmeG7HeUY+UP36F0aknKYTpYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz", + "integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.13.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz", + "integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz", + "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.0.tgz", + "integrity": "sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.0.tgz", + "integrity": "sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.0.tgz", + "integrity": "sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.0.tgz", + "integrity": "sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.0.tgz", + "integrity": "sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.0.tgz", + "integrity": "sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.0.tgz", + "integrity": "sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.0.tgz", + "integrity": "sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.0.tgz", + "integrity": "sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.0.tgz", + "integrity": "sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.0.tgz", + "integrity": "sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.0.tgz", + "integrity": "sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.0.tgz", + "integrity": "sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.0.tgz", + "integrity": "sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.0.tgz", + "integrity": "sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.0.tgz", + "integrity": "sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.0.tgz", + "integrity": "sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.0.tgz", + "integrity": "sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.0.tgz", + "integrity": "sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.0.tgz", + "integrity": "sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", + "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.1.2", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.2.tgz", + "integrity": "sha512-oxLPMytKchWGbnQM9O7D67uPa9paTNxO7jVoNMXgkkErULBPhPARCfkKL9ytcIJJRGjbsVwW4ugJzyFFvm/Tiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.1.2", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.2.tgz", + "integrity": "sha512-XGJkWF41Qq305SKWEILa1O8vzhb3aOo3ogBlSmiqNko/WmRb6QIaweuZCXjKygVDXpzXb5wyxKTSOsmkuqj+Qw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.0.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz", + "integrity": "sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.26.0", + "@babel/plugin-transform-react-jsx-self": "^7.25.9", + "@babel/plugin-transform-react-jsx-source": "^7.25.9", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.14.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" + } + }, + "node_modules/acorn": { + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/browserslist": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", + "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001713", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001713.tgz", + "integrity": "sha512-wCIWIg+A4Xr7NfhTuHdX+/FKh3+Op3LBbSp2N5Pfx6T/LhdQy3GTyoTg48BReaW/MyMNZAkTadsBtai3ldWK0Q==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.137", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.137.tgz", + "integrity": "sha512-/QSJaU2JyIuTbbABAo/crOs+SuAZLS+fVVS10PVrIT9hrRkmZl8Hb0xPSkKRUUWHQtYzXHpQUW3Dy5hwMzGZkA==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz", + "integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.2", + "@esbuild/android-arm": "0.25.2", + "@esbuild/android-arm64": "0.25.2", + "@esbuild/android-x64": "0.25.2", + "@esbuild/darwin-arm64": "0.25.2", + "@esbuild/darwin-x64": "0.25.2", + "@esbuild/freebsd-arm64": "0.25.2", + "@esbuild/freebsd-x64": "0.25.2", + "@esbuild/linux-arm": "0.25.2", + "@esbuild/linux-arm64": "0.25.2", + "@esbuild/linux-ia32": "0.25.2", + "@esbuild/linux-loong64": "0.25.2", + "@esbuild/linux-mips64el": "0.25.2", + "@esbuild/linux-ppc64": "0.25.2", + "@esbuild/linux-riscv64": "0.25.2", + "@esbuild/linux-s390x": "0.25.2", + "@esbuild/linux-x64": "0.25.2", + "@esbuild/netbsd-arm64": "0.25.2", + "@esbuild/netbsd-x64": "0.25.2", + "@esbuild/openbsd-arm64": "0.25.2", + "@esbuild/openbsd-x64": "0.25.2", + "@esbuild/sunos-x64": "0.25.2", + "@esbuild/win32-arm64": "0.25.2", + "@esbuild/win32-ia32": "0.25.2", + "@esbuild/win32-x64": "0.25.2" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.24.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.24.0.tgz", + "integrity": "sha512-eh/jxIEJyZrvbWRe4XuVclLPDYSYYYgLy5zXGGxD6j8zjSAxFEzI2fL/8xNq6O2yKqVt+eF2YhV+hxjV6UKXwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.20.0", + "@eslint/config-helpers": "^0.2.0", + "@eslint/core": "^0.12.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.24.0", + "@eslint/plugin-kit": "^0.2.7", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.3.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", + "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.19.tgz", + "integrity": "sha512-eyy8pcr/YxSYjBoqIFSrlbn9i/xvxUFa8CjzAYo9cFjgGXqq1hyjihcpZvxRLalpaWmueWR81xn7vuKmAFijDQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=8.40" + } + }, + "node_modules/eslint-scope": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", + "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/react": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", + "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", + "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.26.0" + }, + "peerDependencies": { + "react": "^19.1.0" + } + }, + "node_modules/react-refresh": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.5.0.tgz", + "integrity": "sha512-estOHrRlDMKdlQa6Mj32gIks4J+AxNsYoE0DbTTxiMy2mPzZuWSDU+N85/r1IlNR7kGfznF3VCUlvc5IUO+B9g==", + "license": "MIT", + "dependencies": { + "@types/cookie": "^0.6.0", + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0", + "turbo-stream": "2.4.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.5.0.tgz", + "integrity": "sha512-fFhGFCULy4vIseTtH5PNcY/VvDJK5gvOWcwJVHQp8JQcWVr85ENhJ3UpuF/zP1tQOIFYNRJHzXtyhU1Bdgw0RA==", + "license": "MIT", + "dependencies": { + "react-router": "7.5.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/rollup": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.0.tgz", + "integrity": "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.7" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.40.0", + "@rollup/rollup-android-arm64": "4.40.0", + "@rollup/rollup-darwin-arm64": "4.40.0", + "@rollup/rollup-darwin-x64": "4.40.0", + "@rollup/rollup-freebsd-arm64": "4.40.0", + "@rollup/rollup-freebsd-x64": "4.40.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.40.0", + "@rollup/rollup-linux-arm-musleabihf": "4.40.0", + "@rollup/rollup-linux-arm64-gnu": "4.40.0", + "@rollup/rollup-linux-arm64-musl": "4.40.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.40.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.40.0", + "@rollup/rollup-linux-riscv64-gnu": "4.40.0", + "@rollup/rollup-linux-riscv64-musl": "4.40.0", + "@rollup/rollup-linux-s390x-gnu": "4.40.0", + "@rollup/rollup-linux-x64-gnu": "4.40.0", + "@rollup/rollup-linux-x64-musl": "4.40.0", + "@rollup/rollup-win32-arm64-msvc": "4.40.0", + "@rollup/rollup-win32-ia32-msvc": "4.40.0", + "@rollup/rollup-win32-x64-msvc": "4.40.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", + "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/turbo-stream": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz", + "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==", + "license": "ISC" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vite": { + "version": "6.2.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.6.tgz", + "integrity": "sha512-9xpjNl3kR4rVDZgPNdTL0/c6ao4km69a/2ihNQbcANz8RuCOK3hQBmLSJf3bRKVQjVMda+YvizNE8AwvogcPbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "postcss": "^8.5.3", + "rollup": "^4.30.1" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/src/main/resources/statics/kuarion-front-end/package.json b/src/main/resources/statics/kuarion-front-end/package.json new file mode 100644 index 0000000..d19fe4f --- /dev/null +++ b/src/main/resources/statics/kuarion-front-end/package.json @@ -0,0 +1,28 @@ +{ + "name": "kuarion-front-end", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-router-dom": "^7.5.0" + }, + "devDependencies": { + "@eslint/js": "^9.21.0", + "@types/react": "^19.0.10", + "@types/react-dom": "^19.0.4", + "@vitejs/plugin-react": "^4.3.4", + "eslint": "^9.21.0", + "eslint-plugin-react-hooks": "^5.1.0", + "eslint-plugin-react-refresh": "^0.4.19", + "globals": "^15.15.0", + "vite": "^6.2.0" + } +} diff --git a/src/main/resources/statics/kuarion-front-end/public/Kuarion.svg b/src/main/resources/statics/kuarion-front-end/public/Kuarion.svg new file mode 100644 index 0000000..2dda422 --- /dev/null +++ b/src/main/resources/statics/kuarion-front-end/public/Kuarion.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/statics/kuarion-front-end/src/App.css b/src/main/resources/statics/kuarion-front-end/src/App.css new file mode 100644 index 0000000..e69de29 diff --git a/src/main/resources/statics/kuarion-front-end/src/App.jsx b/src/main/resources/statics/kuarion-front-end/src/App.jsx new file mode 100644 index 0000000..83c46b0 --- /dev/null +++ b/src/main/resources/statics/kuarion-front-end/src/App.jsx @@ -0,0 +1,15 @@ +import { BrowserRouter } from 'react-router-dom'; + +import './App.css'; + +import { Router } from './Router'; + +function App() { + return ( + + + + ); +} + +export default App; diff --git a/src/main/resources/statics/kuarion-front-end/src/Router.jsx b/src/main/resources/statics/kuarion-front-end/src/Router.jsx new file mode 100644 index 0000000..4ac86d4 --- /dev/null +++ b/src/main/resources/statics/kuarion-front-end/src/Router.jsx @@ -0,0 +1,15 @@ +import { Routes, Route } from 'react-router-dom'; + +import { Index } from './pages/Index/Index'; +import { Login } from './pages/Login/Login'; +import { NotFound } from './pages/NotFound/NotFound'; + +export function Router() { + return ( + + } /> + } /> + } /> + + ); +} \ No newline at end of file diff --git a/src/main/resources/statics/kuarion-front-end/src/assets/kuarion-logo.png b/src/main/resources/statics/kuarion-front-end/src/assets/kuarion-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..2dae43862a7650134ccde2ce5699b84848ec6b76 GIT binary patch literal 19769 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
^3#n{4XfX7u@s zbDK>=sp%pVSkpI<#o<Ec`!pUAljf^F)kn9tqI zZfP7(=R^$=anXxgzyWf>ZTfdXSk9FJ^IEQ_>D>JY?x^QqjoFXI@4Lkw_i+^}2v!;! z(G3cW0ZyT07nNN$k`j-%8FePmNe0&~Q!s@TSmCd;y(1jv_U$}Dtfva*Wz+F6$3bXd z0yL8%+eVsK5=@`s(stT81&V<=1MJ4MMF-?YP5;YHy~3i)cNS6ObH>e*U88L)u>!O9 zE9P|aUuF)-NY_VkArX_+Q-v}~uRi51eSv9Y|7dC7h_AADFqGOXz!0uN3N|5U`_{ac zN+KmT+l_8!G-jDn-~*Qrl2;#`oh-IdIV}+3HU>zgZj$a{M_>+XjV-X9;=naQzkpY5 zd0}t$HpWeu9Q>scCGGlsBqI1z_+#}|9Z(E3mu38JWmXJ(qq8J%IeGKiUo@bg!dQ(R zmS;fWrl2(7CZ;ImiB>NwHdYSr$%|rF;}q3$r8|4waqAf3&<@FXa4tM6mE15BOedA! zc0fKV^pC;4_5dpfaWxvBhch*IwW9K!+1-q1)l?8Z7*<@~xLFO`9*8|rQC T{*OI3^LbxpejVjCVzeje`3gN7oQ-!v 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/statics/kuarion-front-end/src/index.css b/src/main/resources/statics/kuarion-front-end/src/index.css new file mode 100644 index 0000000..e69de29 diff --git a/src/main/resources/statics/kuarion-front-end/src/main.jsx b/src/main/resources/statics/kuarion-front-end/src/main.jsx new file mode 100644 index 0000000..568b6b5 --- /dev/null +++ b/src/main/resources/statics/kuarion-front-end/src/main.jsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import './index.css'; +import App from './App.jsx'; + +createRoot(document.getElementById('root')).render( + + + , +) diff --git a/src/main/resources/statics/kuarion-front-end/src/pages/Index/Index.jsx b/src/main/resources/statics/kuarion-front-end/src/pages/Index/Index.jsx new file mode 100644 index 0000000..495a3e0 --- /dev/null +++ b/src/main/resources/statics/kuarion-front-end/src/pages/Index/Index.jsx @@ -0,0 +1,7 @@ +function Index() { + return ( +

Kuarion melhor projeto do Hackathon

+ ); +} + +export { Index }; \ No newline at end of file diff --git a/src/main/resources/statics/kuarion-front-end/src/pages/Login/Login.jsx b/src/main/resources/statics/kuarion-front-end/src/pages/Login/Login.jsx new file mode 100644 index 0000000..2df04b9 --- /dev/null +++ b/src/main/resources/statics/kuarion-front-end/src/pages/Login/Login.jsx @@ -0,0 +1,7 @@ +function Login() { + return ( +

aqui o login será feito

+ ); +} + +export { Login }; \ No newline at end of file diff --git a/src/main/resources/statics/kuarion-front-end/src/pages/NotFound/NotFound.jsx b/src/main/resources/statics/kuarion-front-end/src/pages/NotFound/NotFound.jsx new file mode 100644 index 0000000..f540c10 --- /dev/null +++ b/src/main/resources/statics/kuarion-front-end/src/pages/NotFound/NotFound.jsx @@ -0,0 +1,7 @@ +function NotFound() { + return ( +

Error page not found

+ ); +} + +export { NotFound }; \ No newline at end of file diff --git a/src/main/resources/statics/kuarion-front-end/vite.config.js b/src/main/resources/statics/kuarion-front-end/vite.config.js new file mode 100644 index 0000000..ce3ddda --- /dev/null +++ b/src/main/resources/statics/kuarion-front-end/vite.config.js @@ -0,0 +1,10 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], + server: { + port: 5000, + }, +}) From 3fa527f10ea75517f7de860853eb2ce5eb7cb01c Mon Sep 17 00:00:00 2001 From: SD-W1972 Date: Tue, 15 Apr 2025 01:18:44 -0300 Subject: [PATCH 25/42] provisory endpoint configuration --- .../com/kuarion/backend/config/SecurityConfig.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/kuarion/backend/config/SecurityConfig.java b/src/main/java/com/kuarion/backend/config/SecurityConfig.java index bf7276c..abdcaec 100644 --- a/src/main/java/com/kuarion/backend/config/SecurityConfig.java +++ b/src/main/java/com/kuarion/backend/config/SecurityConfig.java @@ -24,7 +24,7 @@ public SecurityConfig(TokenFilter tokenFilter) { this.tokenFilter = tokenFilter; } - @Bean + @Bean public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception { return httpSecurity .csrf(csrf -> csrf.disable()) @@ -32,10 +32,14 @@ public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws // authentication based in token: stateless security policy .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth - .requestMatchers(HttpMethod.GET, "/", "/login", "/index", "/api/chat/message", "/api/chat/history").permitAll() - .requestMatchers(HttpMethod.POST, "/authentication/**", "/api/chat/message").permitAll() + .requestMatchers(HttpMethod.GET, "/", "/login", "/index", "/api/chat/message","/status/**" ,"/statistics" , "/api/chat/history").permitAll() + .requestMatchers(HttpMethod.POST, "/authentication/**", "/api/chat/message", "/submit/**").permitAll() .requestMatchers(HttpMethod.DELETE, "/api/chat/history/delete").permitAll() + .requestMatchers(HttpMethod.GET, "/dashboard").authenticated() + + .requestMatchers(HttpMethod.GET, "/statistics").permitAll() + .anyRequest().denyAll() ) .logout(logout -> logout @@ -45,8 +49,6 @@ public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws .deleteCookies("jwtToken") .permitAll() ) - // the tokenFilter will intercept all protected routes before UsernamePasswordAuthenticationFilter - // the tokenFilter will intercept all protected routes before UsernamePasswordAuthenticationFilter .addFilterBefore(tokenFilter, UsernamePasswordAuthenticationFilter.class) .build(); } From 68db5cc47cdf4307936addf30850108295fa6b8d Mon Sep 17 00:00:00 2001 From: SD-W1972 Date: Tue, 15 Apr 2025 02:38:35 -0300 Subject: [PATCH 26/42] implemented User Survey --- .../components/QuestionInitializer.java | 29 ++++ .../backend/config/SecurityConfig.java | 2 +- .../backend/controller/SurveyController.java | 34 +++-- .../kuarion/backend/entities/Question.java | 20 +-- .../repositories/QuestionRepository.java | 4 +- .../backend/service/SurveyService.java | 128 +++++++++--------- src/main/resources/application.properties | 4 +- 7 files changed, 132 insertions(+), 89 deletions(-) create mode 100644 src/main/java/com/kuarion/backend/components/QuestionInitializer.java diff --git a/src/main/java/com/kuarion/backend/components/QuestionInitializer.java b/src/main/java/com/kuarion/backend/components/QuestionInitializer.java new file mode 100644 index 0000000..f6142ec --- /dev/null +++ b/src/main/java/com/kuarion/backend/components/QuestionInitializer.java @@ -0,0 +1,29 @@ +package com.kuarion.backend.components; + +import com.kuarion.backend.entities.Question; +import com.kuarion.backend.repositories.QuestionRepository; +import com.kuarion.backend.roles.QuestionType; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +public class QuestionInitializer implements CommandLineRunner { + + @Autowired + private QuestionRepository questionRepository; + + @Override + public void run(String... args) { + if(questionRepository.count() == 0) { + List defaultQuestions = List.of( + new Question("Oi como voce tá", QuestionType.TEXT), + new Question("Quantos cômodos tem na sua casa", QuestionType.MULTIPLE_CHOICE), + new Question("Quanto voce gasta por mes com energia eletrica", QuestionType.MULTIPLE_CHOICE) + ); + questionRepository.saveAll(defaultQuestions); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/kuarion/backend/config/SecurityConfig.java b/src/main/java/com/kuarion/backend/config/SecurityConfig.java index abdcaec..2ecf511 100644 --- a/src/main/java/com/kuarion/backend/config/SecurityConfig.java +++ b/src/main/java/com/kuarion/backend/config/SecurityConfig.java @@ -32,7 +32,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws // authentication based in token: stateless security policy .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth - .requestMatchers(HttpMethod.GET, "/", "/login", "/index", "/api/chat/message","/status/**" ,"/statistics" , "/api/chat/history").permitAll() + .requestMatchers(HttpMethod.GET, "/", "/login", "/index", "/api/chat/message","/status/**" ,"/statistics" , "/api/chat/history", "/questions").permitAll() .requestMatchers(HttpMethod.POST, "/authentication/**", "/api/chat/message", "/submit/**").permitAll() .requestMatchers(HttpMethod.DELETE, "/api/chat/history/delete").permitAll() diff --git a/src/main/java/com/kuarion/backend/controller/SurveyController.java b/src/main/java/com/kuarion/backend/controller/SurveyController.java index c2c60a2..92da7a9 100644 --- a/src/main/java/com/kuarion/backend/controller/SurveyController.java +++ b/src/main/java/com/kuarion/backend/controller/SurveyController.java @@ -1,6 +1,7 @@ package com.kuarion.backend.controller; import java.util.Collections; +import java.util.List; import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; @@ -21,31 +22,34 @@ public class SurveyController { @Autowired private SurveyService surveyService; - @GetMapping("/status/{userId}") public ResponseEntity checkResponseStatus(@PathVariable Long userId){ boolean hasResponded = surveyService.hasResponded(userId); return ResponseEntity.ok(Collections.singletonMap("hasResponded", hasResponded)); } + @PostMapping("/submit/{userId}") + public ResponseEntity submitQuestionnaire( + @PathVariable Long userId, + @RequestBody Map answers) { // Usava Long (ID da questão) como chave + try { + surveyService.submitSurveyAnswer(userId, answers); + return ResponseEntity.ok().build(); + } catch (Exception e) { + return ResponseEntity.badRequest().body(e.getMessage()); + } + } - @PostMapping("/submit/{userId}") - public ResponseEntity submitQuestionnaire( - @PathVariable Long userId, - @RequestBody Map answers) { - - try { - SurveyAnswers response = surveyService.submitSurveyAnswer(userId, answers); - return ResponseEntity.ok(response); - } catch (IllegalStateException e) { - return ResponseEntity.badRequest().body(e.getMessage()); - } - } - - @GetMapping("/statistics") + + @GetMapping("/statistics") public ResponseEntity getStatistics() { Map> statistics = surveyService.getQuestionStatistics(); return ResponseEntity.ok(statistics); } + + @GetMapping("/questions") + public List getQuestions() { + return surveyService.getAllQuestions(); + } } diff --git a/src/main/java/com/kuarion/backend/entities/Question.java b/src/main/java/com/kuarion/backend/entities/Question.java index ebb797a..c74251a 100644 --- a/src/main/java/com/kuarion/backend/entities/Question.java +++ b/src/main/java/com/kuarion/backend/entities/Question.java @@ -2,7 +2,10 @@ import com.kuarion.backend.roles.QuestionType; +import jakarta.persistence.Column; import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; @@ -14,16 +17,17 @@ public class Question { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + private String text; + + private QuestionType type; public Long getId() { return id; } - public void setId(Long id) { - this.id = id; - } + public String getText() { return text; } @@ -36,17 +40,17 @@ public QuestionType getType() { public void setType(QuestionType type) { this.type = type; } - public Question(Long id, String text, QuestionType type) { - super(); - this.id = id; + + public Question( String text, QuestionType type) { this.text = text; this.type = type; } + + public Question() { - super(); - // TODO Auto-generated constructor stub } + } diff --git a/src/main/java/com/kuarion/backend/repositories/QuestionRepository.java b/src/main/java/com/kuarion/backend/repositories/QuestionRepository.java index 1148c9e..db3658c 100644 --- a/src/main/java/com/kuarion/backend/repositories/QuestionRepository.java +++ b/src/main/java/com/kuarion/backend/repositories/QuestionRepository.java @@ -1,9 +1,11 @@ package com.kuarion.backend.repositories; +import java.util.Optional; + import org.springframework.data.jpa.repository.JpaRepository; import com.kuarion.backend.entities.Question; public interface QuestionRepository extends JpaRepository{ - + } diff --git a/src/main/java/com/kuarion/backend/service/SurveyService.java b/src/main/java/com/kuarion/backend/service/SurveyService.java index 8abe708..86ef24c 100644 --- a/src/main/java/com/kuarion/backend/service/SurveyService.java +++ b/src/main/java/com/kuarion/backend/service/SurveyService.java @@ -1,9 +1,8 @@ package com.kuarion.backend.service; -import java.time.LocalDateTime; -import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.HashMap; import java.util.stream.Collectors; import org.springframework.beans.factory.annotation.Autowired; @@ -20,64 +19,67 @@ @Service public class SurveyService { - - @Autowired - private SurveyAnswersRepository surveyRepository; - - @Autowired - private UserRepository userRepository; - - @Autowired - private QuestionRepository questionRepository; - - @Autowired - private AnswerRepository answerRepository; - - public Boolean hasResponded(Long userId) { - User user = userRepository.findById(userId).orElseThrow(() -> new RuntimeException("Usuario não encontrado!")); - return surveyRepository.existsByUser(user); - } - - public SurveyAnswers submitSurveyAnswer(Long userId, Map answers) { - if(hasResponded(userId)) { - throw new IllegalStateException("User has already responded to the questionnaire"); - } - - User user = userRepository.findById(userId).orElseThrow(() -> new RuntimeException("Usuario não encontrado !")); - - SurveyAnswers surveyAnswers = new SurveyAnswers(); - surveyAnswers.setUser(user); - surveyAnswers.setResponseData(LocalDateTime.now()); - surveyAnswers = surveyRepository.save(surveyAnswers); - - for(Map.Entry entry : answers.entrySet()) { - Question question = questionRepository.findById(entry.getKey()) - .orElseThrow(() -> new RuntimeException("Questão não encontrada!")); - - Answer answer = new Answer(); - answer.setResponse(surveyAnswers); - answer.setQuestion(question); - answer.setAnswer(entry.getValue()); - surveyAnswers.getAnswers().add(answer); - } - - return surveyRepository.save(surveyAnswers); - } - - public Map> getQuestionStatistics(){ - List questions = questionRepository.findAll(); - Map> statistics = new HashMap<>(); - - for(Question question : questions) { - List answers = answerRepository.findByQuestion(question); - Map countMap = answers.stream() - .collect(Collectors.groupingBy(Answer::getAnswer, Collectors.counting())); - - statistics.put(question, countMap); - } - - return statistics; - } - - -} + + @Autowired + private SurveyAnswersRepository surveyRepository; + + @Autowired + private UserRepository userRepository; + + @Autowired + private QuestionRepository questionRepository; + + @Autowired + private AnswerRepository answerRepository; + + public Boolean hasResponded(Long userId) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new RuntimeException("Usuário não encontrado!")); + return surveyRepository.existsByUser(user); + } + + public SurveyAnswers submitSurveyAnswer(Long userId, Map answers) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new RuntimeException("Usuário não encontrado")); + + if (hasResponded(userId)) { + throw new IllegalStateException("Usuário já respondeu ao questionário"); + } + + SurveyAnswers surveyAnswers = new SurveyAnswers(); + surveyAnswers.setUser(user); + + for (Map.Entry entry : answers.entrySet()) { + Question question = questionRepository.findById(entry.getKey()) + .orElseThrow(() -> new RuntimeException("Questão não encontrada com ID: " + entry.getKey())); + + Answer answer = new Answer(); + answer.setQuestion(question); + answer.setAnswer(entry.getValue()); + answer.setResponse(surveyAnswers); + + surveyAnswers.getAnswers().add(answer); + } + + return surveyRepository.save(surveyAnswers); + } + + public Map> getQuestionStatistics() { + List questions = questionRepository.findAll(); + Map> statistics = new HashMap<>(); + + for (Question question : questions) { + List answers = answerRepository.findByQuestion(question); + Map countMap = answers.stream() + .collect(Collectors.groupingBy(Answer::getAnswer, Collectors.counting())); + + statistics.put(question, countMap); + } + + return statistics; + } + + public List getAllQuestions() { + return questionRepository.findAll(); + } +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 516d120..a05dd49 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -28,4 +28,6 @@ server.port:8080 spring.config.import=application-local.properties gemini.api.base-url=https://generativelanguage.googleapis.com -gemini.api.model=gemini-1.5-pro-latest \ No newline at end of file +gemini.api.model=gemini-1.5-pro-latest + + From 5f6c35bec255b53197c51e82d592d515ba4e1c8e Mon Sep 17 00:00:00 2001 From: SD-W1972 Date: Tue, 15 Apr 2025 11:21:34 -0300 Subject: [PATCH 27/42] implemented GET endpoint to specific question statistics --- .../kuarion/backend/config/SecurityConfig.java | 2 +- .../backend/controller/SurveyController.java | 8 ++++++-- .../kuarion/backend/service/SurveyService.java | 16 ++++++++++++++++ 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/kuarion/backend/config/SecurityConfig.java b/src/main/java/com/kuarion/backend/config/SecurityConfig.java index 2ecf511..1434f07 100644 --- a/src/main/java/com/kuarion/backend/config/SecurityConfig.java +++ b/src/main/java/com/kuarion/backend/config/SecurityConfig.java @@ -32,7 +32,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws // authentication based in token: stateless security policy .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth - .requestMatchers(HttpMethod.GET, "/", "/login", "/index", "/api/chat/message","/status/**" ,"/statistics" , "/api/chat/history", "/questions").permitAll() + .requestMatchers(HttpMethod.GET, "/", "/login", "/index", "/api/chat/message","/status/**" ,"/statistics" , "/api/chat/history", "/questions", "/statistics/**").permitAll() .requestMatchers(HttpMethod.POST, "/authentication/**", "/api/chat/message", "/submit/**").permitAll() .requestMatchers(HttpMethod.DELETE, "/api/chat/history/delete").permitAll() diff --git a/src/main/java/com/kuarion/backend/controller/SurveyController.java b/src/main/java/com/kuarion/backend/controller/SurveyController.java index 92da7a9..1489cad 100644 --- a/src/main/java/com/kuarion/backend/controller/SurveyController.java +++ b/src/main/java/com/kuarion/backend/controller/SurveyController.java @@ -13,7 +13,6 @@ import org.springframework.web.bind.annotation.RestController; import com.kuarion.backend.entities.Question; -import com.kuarion.backend.entities.SurveyAnswers; import com.kuarion.backend.service.SurveyService; @RestController @@ -46,7 +45,12 @@ public ResponseEntity getStatistics() { Map> statistics = surveyService.getQuestionStatistics(); return ResponseEntity.ok(statistics); } - + + @GetMapping("/statistics/{questionId}") + public ResponseEntity> getQuestionStatistics(@PathVariable Long questionId) { + Map statistics = surveyService.getSingleQuestionStatistics(questionId); + return ResponseEntity.ok(statistics); + } @GetMapping("/questions") public List getQuestions() { diff --git a/src/main/java/com/kuarion/backend/service/SurveyService.java b/src/main/java/com/kuarion/backend/service/SurveyService.java index 86ef24c..b971c70 100644 --- a/src/main/java/com/kuarion/backend/service/SurveyService.java +++ b/src/main/java/com/kuarion/backend/service/SurveyService.java @@ -79,6 +79,22 @@ public Map> getQuestionStatistics() { return statistics; } + + public Map getSingleQuestionStatistics(Long questionId) { + Question question = questionRepository.findById(questionId) + .orElseThrow(() -> new RuntimeException("Questão não encontrada, tente com outro ID, por favor !")); + + List answers = answerRepository.findByQuestion(question); + + Map statistics = answers.stream() + .collect(Collectors.groupingBy( + Answer::getAnswer, + Collectors.counting() + )); + + return statistics; + } + public List getAllQuestions() { return questionRepository.findAll(); } From 09314c42dcb6151467739bdd608af525d9ffc12e Mon Sep 17 00:00:00 2001 From: SD-W1972 Date: Tue, 15 Apr 2025 20:37:06 -0300 Subject: [PATCH 28/42] websocket implementation to allow real time chatbot history interactivity --- pom.xml | 5 ++ .../backend/config/WebSocketConfig.java | 24 +++++++ .../backend/controller/GeminiController.java | 63 +++++++++++-------- .../backend/entities/ChatExchangeEntity.java | 62 ++++++++++++++++++ .../repositories/ChatExchangeRepository.java | 11 ++++ 5 files changed, 138 insertions(+), 27 deletions(-) create mode 100644 src/main/java/com/kuarion/backend/config/WebSocketConfig.java create mode 100644 src/main/java/com/kuarion/backend/entities/ChatExchangeEntity.java create mode 100644 src/main/java/com/kuarion/backend/repositories/ChatExchangeRepository.java diff --git a/pom.xml b/pom.xml index bf99449..b937595 100644 --- a/pom.xml +++ b/pom.xml @@ -48,6 +48,11 @@ spring-ai-openai-spring-boot-starter + + + org.springframework.boot + spring-boot-starter-websocket + org.springframework.ai spring-ai-bom diff --git a/src/main/java/com/kuarion/backend/config/WebSocketConfig.java b/src/main/java/com/kuarion/backend/config/WebSocketConfig.java new file mode 100644 index 0000000..f5c534d --- /dev/null +++ b/src/main/java/com/kuarion/backend/config/WebSocketConfig.java @@ -0,0 +1,24 @@ +package com.kuarion.backend.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.messaging.simp.config.MessageBrokerRegistry; +import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; +import org.springframework.web.socket.config.annotation.StompEndpointRegistry; +import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; + +@Configuration +@EnableWebSocketMessageBroker +public class WebSocketConfig implements WebSocketMessageBrokerConfigurer{ + + @Override + public void configureMessageBroker(MessageBrokerRegistry config) { + config.enableSimpleBroker("/topic"); + config.setApplicationDestinationPrefixes("/app"); + } + + @Override + public void registerStompEndpoints(StompEndpointRegistry registry) { + registry.addEndpoint("/ws-chat").withSockJS(); + } + +} diff --git a/src/main/java/com/kuarion/backend/controller/GeminiController.java b/src/main/java/com/kuarion/backend/controller/GeminiController.java index 7b17f81..55579b6 100644 --- a/src/main/java/com/kuarion/backend/controller/GeminiController.java +++ b/src/main/java/com/kuarion/backend/controller/GeminiController.java @@ -1,7 +1,10 @@ package com.kuarion.backend.controller; +import java.util.List; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; +import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; @@ -15,33 +18,39 @@ @RestController public class GeminiController { - - private final ChatService chatService; - - - @Autowired - public GeminiController(ChatService chatService) { - super(); - this.chatService = chatService; - } - - - @PostMapping("/api/chat/message") - public ResponseEntity sendMessage(@RequestBody ChatRequest request) { - ChatResponse response = chatService.processUserMessage(request); - return ResponseEntity.ok(response); - } + + private final ChatService chatService; + private final SimpMessagingTemplate messagingTemplate; + + @Autowired + public GeminiController(ChatService chatService, SimpMessagingTemplate messagingTemplate) { + this.chatService = chatService; + this.messagingTemplate = messagingTemplate; + } + + @PostMapping("/api/chat/message") + public ResponseEntity sendMessage(@RequestBody ChatRequest request) { + ChatResponse response = chatService.processUserMessage(request); + + // Envia a atualização do histórico para todos os clientes conectados + messagingTemplate.convertAndSend("/topic/chat-updates", chatService.getChatHistory()); + + return ResponseEntity.ok(response); + } - @GetMapping("/api/chat/history") - public ResponseEntity getChatHistory() { - ChatHistoryResponse history = chatService.getChatHistory(); - return ResponseEntity.ok(history); - } + @GetMapping("/api/chat/history") + public ResponseEntity getChatHistory() { + ChatHistoryResponse history = chatService.getChatHistory(); + return ResponseEntity.ok(history); + } - @DeleteMapping("/api/chat/history") - public ResponseEntity clearChatHistory() { - chatService.clearChatHistory(); - return ResponseEntity.noContent().build(); - } - + @DeleteMapping("/api/chat/history") + public ResponseEntity clearChatHistory() { + chatService.clearChatHistory(); + + // Notifica os clientes que o histórico foi limpo + messagingTemplate.convertAndSend("/topic/chat-updates", new ChatHistoryResponse(List.of())); + + return ResponseEntity.noContent().build(); + } } \ No newline at end of file diff --git a/src/main/java/com/kuarion/backend/entities/ChatExchangeEntity.java b/src/main/java/com/kuarion/backend/entities/ChatExchangeEntity.java new file mode 100644 index 0000000..ff355d6 --- /dev/null +++ b/src/main/java/com/kuarion/backend/entities/ChatExchangeEntity.java @@ -0,0 +1,62 @@ +package com.kuarion.backend.entities; + +import java.time.LocalDateTime; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; + +@Entity +public class ChatExchangeEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private String userMessage; + + @Column(nullable = false) + private String botResponse; + + @Column(nullable = false) + private LocalDateTime timestamp; + + public String getUserMessage() { + return userMessage; + } + + public void setUserMessage(String userMessage) { + this.userMessage = userMessage; + } + + public String getBotResponse() { + return botResponse; + } + + public void setBotResponse(String botResponse) { + this.botResponse = botResponse; + } + + public LocalDateTime getTimestamp() { + return timestamp; + } + + public void setTimestamp(LocalDateTime timestamp) { + this.timestamp = timestamp; + } + + public Long getId() { + return id; + } + + public ChatExchangeEntity(String userMessage, String botResponse, LocalDateTime timestamp) { + this.userMessage = userMessage; + this.botResponse = botResponse; + this.timestamp = timestamp; + } + + public ChatExchangeEntity() { + } +} diff --git a/src/main/java/com/kuarion/backend/repositories/ChatExchangeRepository.java b/src/main/java/com/kuarion/backend/repositories/ChatExchangeRepository.java new file mode 100644 index 0000000..5971937 --- /dev/null +++ b/src/main/java/com/kuarion/backend/repositories/ChatExchangeRepository.java @@ -0,0 +1,11 @@ +package com.kuarion.backend.repositories; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.kuarion.backend.entities.ChatExchangeEntity; + +public interface ChatExchangeRepository extends JpaRepository{ + List findAllByOrderByTimestampAsc(); +} From 7f7056e71021d4ed4ec5c012e0e724392b182a58 Mon Sep 17 00:00:00 2001 From: SD-W1972 Date: Tue, 15 Apr 2025 21:05:46 -0300 Subject: [PATCH 29/42] add logger to gemini controller --- .../kuarion/backend/controller/GeminiController.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/kuarion/backend/controller/GeminiController.java b/src/main/java/com/kuarion/backend/controller/GeminiController.java index 55579b6..10deb32 100644 --- a/src/main/java/com/kuarion/backend/controller/GeminiController.java +++ b/src/main/java/com/kuarion/backend/controller/GeminiController.java @@ -2,6 +2,8 @@ import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.messaging.simp.SimpMessagingTemplate; @@ -21,7 +23,9 @@ public class GeminiController { private final ChatService chatService; private final SimpMessagingTemplate messagingTemplate; + private static final Logger logger = LoggerFactory.getLogger(GeminiController.class); + @Autowired public GeminiController(ChatService chatService, SimpMessagingTemplate messagingTemplate) { this.chatService = chatService; @@ -30,9 +34,10 @@ public GeminiController(ChatService chatService, SimpMessagingTemplate messaging @PostMapping("/api/chat/message") public ResponseEntity sendMessage(@RequestBody ChatRequest request) { - ChatResponse response = chatService.processUserMessage(request); - // Envia a atualização do histórico para todos os clientes conectados + logger.info("Nova mensagem recebida: {}", request.message()); + ChatResponse response = chatService.processUserMessage(request); + logger.info("Enviando atualização para Websocket"); messagingTemplate.convertAndSend("/topic/chat-updates", chatService.getChatHistory()); return ResponseEntity.ok(response); @@ -48,7 +53,6 @@ public ResponseEntity getChatHistory() { public ResponseEntity clearChatHistory() { chatService.clearChatHistory(); - // Notifica os clientes que o histórico foi limpo messagingTemplate.convertAndSend("/topic/chat-updates", new ChatHistoryResponse(List.of())); return ResponseEntity.noContent().build(); From 495c4e6ca5c34ed31494428ffb66bd2d71be64ba Mon Sep 17 00:00:00 2001 From: SD-W1972 Date: Wed, 16 Apr 2025 10:43:36 -0300 Subject: [PATCH 30/42] implemented manual InMemoryChatMemory instead of using Web Socket --- .../backend/config/WebSocketConfig.java | 24 ------------ .../backend/controller/GeminiController.java | 37 +++++++++++-------- .../backend/entities/ChatExchangeEntity.java | 2 +- .../kuarion/backend/service/ChatService.java | 6 +-- .../backend/service/GeminiService.java | 13 ++++--- .../kuarion/backend/service/TokenService.java | 2 +- 6 files changed, 34 insertions(+), 50 deletions(-) delete mode 100644 src/main/java/com/kuarion/backend/config/WebSocketConfig.java diff --git a/src/main/java/com/kuarion/backend/config/WebSocketConfig.java b/src/main/java/com/kuarion/backend/config/WebSocketConfig.java deleted file mode 100644 index f5c534d..0000000 --- a/src/main/java/com/kuarion/backend/config/WebSocketConfig.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.kuarion.backend.config; - -import org.springframework.context.annotation.Configuration; -import org.springframework.messaging.simp.config.MessageBrokerRegistry; -import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; -import org.springframework.web.socket.config.annotation.StompEndpointRegistry; -import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; - -@Configuration -@EnableWebSocketMessageBroker -public class WebSocketConfig implements WebSocketMessageBrokerConfigurer{ - - @Override - public void configureMessageBroker(MessageBrokerRegistry config) { - config.enableSimpleBroker("/topic"); - config.setApplicationDestinationPrefixes("/app"); - } - - @Override - public void registerStompEndpoints(StompEndpointRegistry registry) { - registry.addEndpoint("/ws-chat").withSockJS(); - } - -} diff --git a/src/main/java/com/kuarion/backend/controller/GeminiController.java b/src/main/java/com/kuarion/backend/controller/GeminiController.java index 10deb32..37dd989 100644 --- a/src/main/java/com/kuarion/backend/controller/GeminiController.java +++ b/src/main/java/com/kuarion/backend/controller/GeminiController.java @@ -1,18 +1,14 @@ package com.kuarion.backend.controller; -import java.util.List; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; -import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; +import com.kuarion.backend.model.ChatExchange; import com.kuarion.backend.model.ChatHistoryResponse; import com.kuarion.backend.model.ChatRequest; import com.kuarion.backend.model.ChatResponse; @@ -22,23 +18,31 @@ public class GeminiController { private final ChatService chatService; - private final SimpMessagingTemplate messagingTemplate; - private static final Logger logger = LoggerFactory.getLogger(GeminiController.class); - + private StringBuilder sb = new StringBuilder(); @Autowired - public GeminiController(ChatService chatService, SimpMessagingTemplate messagingTemplate) { + public GeminiController(ChatService chatService) { this.chatService = chatService; - this.messagingTemplate = messagingTemplate; } @PostMapping("/api/chat/message") public ResponseEntity sendMessage(@RequestBody ChatRequest request) { - - logger.info("Nova mensagem recebida: {}", request.message()); - ChatResponse response = chatService.processUserMessage(request); - logger.info("Enviando atualização para Websocket"); - messagingTemplate.convertAndSend("/topic/chat-updates", chatService.getChatHistory()); + String systemPrompt = ""; + ChatHistoryResponse chatResponse = chatService.getChatHistory(); + if(chatResponse != null && chatResponse.exchanges().size() <= 10) { + for(ChatExchange ce : chatResponse.exchanges()) { + systemPrompt = + sb.append("Essa NÃO é a primeira vez que o usuário te envia uma mensagem. " + + "Você deve levar as mensagens anteriores em consideração as mensagens anteriores que ele já tenha mandado" + + " a mensagem anterior do usuário foi" + ce.userMessage()) + .append("e a sua resposta foi" + ce.botResponse()) + .toString(); + } + }else { + systemPrompt = + sb.append("essa é a primeira mensagem que o usuário te envia uma mensagem !").toString(); + } + ChatResponse response = chatService.processUserMessage(request, systemPrompt); return ResponseEntity.ok(response); } @@ -53,8 +57,9 @@ public ResponseEntity getChatHistory() { public ResponseEntity clearChatHistory() { chatService.clearChatHistory(); - messagingTemplate.convertAndSend("/topic/chat-updates", new ChatHistoryResponse(List.of())); return ResponseEntity.noContent().build(); } + + } \ No newline at end of file diff --git a/src/main/java/com/kuarion/backend/entities/ChatExchangeEntity.java b/src/main/java/com/kuarion/backend/entities/ChatExchangeEntity.java index ff355d6..75e553c 100644 --- a/src/main/java/com/kuarion/backend/entities/ChatExchangeEntity.java +++ b/src/main/java/com/kuarion/backend/entities/ChatExchangeEntity.java @@ -14,7 +14,7 @@ public class ChatExchangeEntity { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column(nullable = false) + @Column(nullable = false, length = 2048) private String userMessage; @Column(nullable = false) diff --git a/src/main/java/com/kuarion/backend/service/ChatService.java b/src/main/java/com/kuarion/backend/service/ChatService.java index c10465a..4964721 100644 --- a/src/main/java/com/kuarion/backend/service/ChatService.java +++ b/src/main/java/com/kuarion/backend/service/ChatService.java @@ -22,9 +22,9 @@ public ChatService(GeminiService geminiService) { this.geminiService = geminiService; } - public ChatResponse processUserMessage(ChatRequest request) { + public ChatResponse processUserMessage(ChatRequest request, String systemPrompt) { // using geminiService to process and get it's answer - String botResponse = geminiService.getGeminiResponse(request.message()); + String botResponse = geminiService.getGeminiResponse(request.message(), systemPrompt); // Registering the exchange between request - response ChatExchange exchange = new ChatExchange( @@ -47,4 +47,4 @@ public ChatHistoryResponse getChatHistory() { public void clearChatHistory() { chatHistory.clear(); } -} +} \ No newline at end of file diff --git a/src/main/java/com/kuarion/backend/service/GeminiService.java b/src/main/java/com/kuarion/backend/service/GeminiService.java index 890d58b..7dd53fa 100644 --- a/src/main/java/com/kuarion/backend/service/GeminiService.java +++ b/src/main/java/com/kuarion/backend/service/GeminiService.java @@ -6,12 +6,9 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.stereotype.Service; -import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestClient; -import com.kuarion.backend.model.ChatRequest; import com.kuarion.backend.model.GeminiResponse; -import com.kuarion.backend.model.ChatResponse; @Service public class GeminiService { @@ -28,16 +25,22 @@ public class GeminiService { private final RestClient restClient; + + public GeminiService(RestClient.Builder builder) { this.restClient = builder .baseUrl("https://generativelanguage.googleapis.com") .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .build(); + + + } - public String getGeminiResponse(String userMessage) { + public String getGeminiResponse(String userMessage, String systemPrompt) { try { - String systemPrompt = "Você é um assistente especializado em energia solar..."; + String requestBody = String.format(""" { diff --git a/src/main/java/com/kuarion/backend/service/TokenService.java b/src/main/java/com/kuarion/backend/service/TokenService.java index f602a25..3642af3 100644 --- a/src/main/java/com/kuarion/backend/service/TokenService.java +++ b/src/main/java/com/kuarion/backend/service/TokenService.java @@ -57,4 +57,4 @@ private Instant expirationTime() { // the JWT token has 5 hours of duration return LocalDateTime.now().plusHours(5).toInstant(ZoneOffset.of("-03:00")); } -} +} \ No newline at end of file From d2294c51804e2261e46a86b53a038fee522ce06c Mon Sep 17 00:00:00 2001 From: SD-W1972 Date: Thu, 17 Apr 2025 22:50:56 -0300 Subject: [PATCH 31/42] predefined a set of answers to each question --- .../backend/components/AnswerInitializer.java | 52 +++++++++++++++++++ .../backend/controller/SurveyController.java | 9 ++++ .../com/kuarion/backend/entities/Answer.java | 10 +--- .../com/kuarion/backend/entities/User.java | 2 + .../repositories/QuestionRepository.java | 3 +- .../backend/service/SurveyService.java | 16 +++++- 6 files changed, 81 insertions(+), 11 deletions(-) create mode 100644 src/main/java/com/kuarion/backend/components/AnswerInitializer.java diff --git a/src/main/java/com/kuarion/backend/components/AnswerInitializer.java b/src/main/java/com/kuarion/backend/components/AnswerInitializer.java new file mode 100644 index 0000000..c8e363b --- /dev/null +++ b/src/main/java/com/kuarion/backend/components/AnswerInitializer.java @@ -0,0 +1,52 @@ +package com.kuarion.backend.components; + +import java.util.Arrays; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; + +import com.kuarion.backend.entities.Answer; +import com.kuarion.backend.entities.Question; +import com.kuarion.backend.repositories.AnswerRepository; +import com.kuarion.backend.repositories.QuestionRepository; +import com.kuarion.backend.roles.QuestionType; + +@Component +public class AnswerInitializer implements CommandLineRunner{ + + @Autowired + private AnswerRepository answerRepository; + + @Autowired + private QuestionRepository questionRepository; + + + + @Override + public void run(String... args) { + List questions = questionRepository.findAll(); + for(Question question : questions) { + if(question.getType() == QuestionType.MULTIPLE_CHOICE) { + List answers = Arrays.asList( + createDefaultAnswer(question, "OPÇÃO 1"), + createDefaultAnswer(question, "OPÇÃO 2"), + createDefaultAnswer(question, "OPÇÃO 3"), + createDefaultAnswer(question, "OPÇÃO 4") + ); + answerRepository.saveAll(answers); + } + } + + } + + + private Answer createDefaultAnswer(Question question, String text) { + Answer answer = new Answer(); + answer.setQuestion(question); + answer.setAnswer(text); + answer.setResponse(null); + return answer; + } +} diff --git a/src/main/java/com/kuarion/backend/controller/SurveyController.java b/src/main/java/com/kuarion/backend/controller/SurveyController.java index 1489cad..7a67638 100644 --- a/src/main/java/com/kuarion/backend/controller/SurveyController.java +++ b/src/main/java/com/kuarion/backend/controller/SurveyController.java @@ -13,6 +13,7 @@ import org.springframework.web.bind.annotation.RestController; import com.kuarion.backend.entities.Question; +import com.kuarion.backend.entities.SurveyAnswers; import com.kuarion.backend.service.SurveyService; @RestController @@ -56,4 +57,12 @@ public ResponseEntity> getQuestionStatistics(@PathVariable Lon public List getQuestions() { return surveyService.getAllQuestions(); } + + /* + @GetMapping("/statistics/user/{userId}") + public ResponseEntity getUserStatistics(@PathVariable Long userId){ + SurveyAnswers surveyAnswers = surveyService.getUserSurvey(userId); + return ResponseEntity.ok(surveyAnswers); + }*/ + } diff --git a/src/main/java/com/kuarion/backend/entities/Answer.java b/src/main/java/com/kuarion/backend/entities/Answer.java index 390bab9..9ae7805 100644 --- a/src/main/java/com/kuarion/backend/entities/Answer.java +++ b/src/main/java/com/kuarion/backend/entities/Answer.java @@ -29,10 +29,6 @@ public Long getId() { return id; } - public void setId(Long id) { - this.id = id; - } - public SurveyAnswers getResponse() { return response; } @@ -57,17 +53,13 @@ public void setAnswer(String answer) { this.answer = answer; } - public Answer(Long id, SurveyAnswers response, Question question, String answer) { - super(); - this.id = id; + public Answer(SurveyAnswers response, Question question, String answer) { this.response = response; this.question = question; this.answer = answer; } public Answer() { - super(); - // TODO Auto-generated constructor stub } diff --git a/src/main/java/com/kuarion/backend/entities/User.java b/src/main/java/com/kuarion/backend/entities/User.java index 1446b2a..7086017 100644 --- a/src/main/java/com/kuarion/backend/entities/User.java +++ b/src/main/java/com/kuarion/backend/entities/User.java @@ -17,6 +17,7 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; import jakarta.persistence.OneToOne; import jakarta.persistence.Table; import lombok.AllArgsConstructor; @@ -51,6 +52,7 @@ public class User implements UserDetails { private Roles role; // user role @OneToOne(mappedBy = "user", cascade = CascadeType.ALL) + @JoinColumn(name = "response_id", nullable = true) private SurveyAnswers surveyAnswers; diff --git a/src/main/java/com/kuarion/backend/repositories/QuestionRepository.java b/src/main/java/com/kuarion/backend/repositories/QuestionRepository.java index db3658c..d17ceb8 100644 --- a/src/main/java/com/kuarion/backend/repositories/QuestionRepository.java +++ b/src/main/java/com/kuarion/backend/repositories/QuestionRepository.java @@ -5,7 +5,8 @@ import org.springframework.data.jpa.repository.JpaRepository; import com.kuarion.backend.entities.Question; +import com.kuarion.backend.roles.QuestionType; public interface QuestionRepository extends JpaRepository{ - + Optional findByType(QuestionType questionType); } diff --git a/src/main/java/com/kuarion/backend/service/SurveyService.java b/src/main/java/com/kuarion/backend/service/SurveyService.java index b971c70..4d90272 100644 --- a/src/main/java/com/kuarion/backend/service/SurveyService.java +++ b/src/main/java/com/kuarion/backend/service/SurveyService.java @@ -1,8 +1,8 @@ package com.kuarion.backend.service; +import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.HashMap; import java.util.stream.Collectors; import org.springframework.beans.factory.annotation.Autowired; @@ -98,4 +98,18 @@ public Map getSingleQuestionStatistics(Long questionId) { public List getAllQuestions() { return questionRepository.findAll(); } + + /* + public SurveyAnswers getUserSurvey(Long id) { + User user = userRepository.findById(id) + .orElseThrow(() -> new RuntimeException("Usuário não encontrado !")); + + + SurveyAnswers sa = user.getSurveyAnswers(); + + + }*/ + + + } \ No newline at end of file From b085035c29f0a5fde49da6ed81bd76b7839545a2 Mon Sep 17 00:00:00 2001 From: SD-W1972 Date: Thu, 17 Apr 2025 23:00:21 -0300 Subject: [PATCH 32/42] bug fix --- .../kuarion/backend/components/AnswerInitializer.java | 10 +++++++--- .../kuarion/backend/repositories/AnswerRepository.java | 2 ++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/kuarion/backend/components/AnswerInitializer.java b/src/main/java/com/kuarion/backend/components/AnswerInitializer.java index c8e363b..76a252d 100644 --- a/src/main/java/com/kuarion/backend/components/AnswerInitializer.java +++ b/src/main/java/com/kuarion/backend/components/AnswerInitializer.java @@ -27,8 +27,10 @@ public class AnswerInitializer implements CommandLineRunner{ @Override public void run(String... args) { List questions = questionRepository.findAll(); + + for(Question question : questions) { - if(question.getType() == QuestionType.MULTIPLE_CHOICE) { + if(question.getType() == QuestionType.MULTIPLE_CHOICE && hasDefaultAnswers(questions) != true) { List answers = Arrays.asList( createDefaultAnswer(question, "OPÇÃO 1"), createDefaultAnswer(question, "OPÇÃO 2"), @@ -38,10 +40,12 @@ public void run(String... args) { answerRepository.saveAll(answers); } } - + } - + private boolean hasDefaultAnswers(List questions) { + return answerRepository.existsByQuestionInAndResponseIsNull(questions); + } private Answer createDefaultAnswer(Question question, String text) { Answer answer = new Answer(); answer.setQuestion(question); diff --git a/src/main/java/com/kuarion/backend/repositories/AnswerRepository.java b/src/main/java/com/kuarion/backend/repositories/AnswerRepository.java index 83a50cb..32d21de 100644 --- a/src/main/java/com/kuarion/backend/repositories/AnswerRepository.java +++ b/src/main/java/com/kuarion/backend/repositories/AnswerRepository.java @@ -9,4 +9,6 @@ public interface AnswerRepository extends JpaRepository{ List findByQuestion(Question question); + boolean existsByQuestionAndAnswerAndResponseIsNull(Question question, String answer); + boolean existsByQuestionInAndResponseIsNull(List questions); } From 061c408742ae4f5e7b6d9a9a02d120be8cab300d Mon Sep 17 00:00:00 2001 From: n the k <118210824+N-The-K-3403@users.noreply.github.com> Date: Fri, 18 Apr 2025 13:06:12 -0300 Subject: [PATCH 33/42] add react base and forum --- .gitignore | 1 + node_modules/.package-lock.json | 97 + node_modules/cookie/LICENSE | 24 + node_modules/cookie/README.md | 248 + node_modules/cookie/dist/index.d.ts | 114 + node_modules/cookie/dist/index.js | 239 + node_modules/cookie/dist/index.js.map | 1 + node_modules/cookie/package.json | 42 + node_modules/react-dom/LICENSE | 21 + node_modules/react-dom/README.md | 60 + .../cjs/react-dom-client.development.js | 24990 +++++++++++++++ .../cjs/react-dom-client.production.js | 15393 ++++++++++ .../cjs/react-dom-profiling.development.js | 25377 ++++++++++++++++ .../cjs/react-dom-profiling.profiling.js | 16218 ++++++++++ ...t-dom-server-legacy.browser.development.js | 9035 ++++++ ...ct-dom-server-legacy.browser.production.js | 5892 ++++ ...eact-dom-server-legacy.node.development.js | 9035 ++++++ ...react-dom-server-legacy.node.production.js | 5972 ++++ .../react-dom-server.browser.development.js | 9424 ++++++ .../react-dom-server.browser.production.js | 6384 ++++ .../cjs/react-dom-server.bun.development.js | 8739 ++++++ .../cjs/react-dom-server.bun.production.js | 5967 ++++ .../cjs/react-dom-server.edge.development.js | 9443 ++++++ .../cjs/react-dom-server.edge.production.js | 6477 ++++ .../cjs/react-dom-server.node.development.js | 9317 ++++++ .../cjs/react-dom-server.node.production.js | 6372 ++++ .../cjs/react-dom-test-utils.development.js | 24 + .../cjs/react-dom-test-utils.production.js | 21 + .../react-dom/cjs/react-dom.development.js | 424 + .../react-dom/cjs/react-dom.production.js | 210 + .../cjs/react-dom.react-server.development.js | 340 + .../cjs/react-dom.react-server.production.js | 152 + node_modules/react-dom/client.js | 38 + node_modules/react-dom/client.react-server.js | 5 + node_modules/react-dom/index.js | 38 + node_modules/react-dom/package.json | 117 + node_modules/react-dom/profiling.js | 38 + .../react-dom/profiling.react-server.js | 5 + .../react-dom/react-dom.react-server.js | 7 + node_modules/react-dom/server.browser.js | 18 + node_modules/react-dom/server.bun.js | 19 + node_modules/react-dom/server.edge.js | 19 + node_modules/react-dom/server.js | 3 + node_modules/react-dom/server.node.js | 18 + node_modules/react-dom/server.react-server.js | 5 + node_modules/react-dom/static.browser.js | 12 + node_modules/react-dom/static.edge.js | 12 + node_modules/react-dom/static.js | 3 + node_modules/react-dom/static.node.js | 12 + node_modules/react-dom/static.react-server.js | 5 + node_modules/react-dom/test-utils.js | 7 + node_modules/react-router-dom/LICENSE.md | 23 + node_modules/react-router-dom/README.md | 6 + .../react-router-dom/dist/index.d.mts | 2 + node_modules/react-router-dom/dist/index.d.ts | 2 + node_modules/react-router-dom/dist/index.js | 45 + node_modules/react-router-dom/dist/index.mjs | 18 + node_modules/react-router-dom/package.json | 82 + node_modules/react-router/CHANGELOG.md | 1568 + node_modules/react-router/LICENSE.md | 23 + node_modules/react-router/README.md | 7 + .../dist/development/chunk-LSOULM7L.mjs | 10716 +++++++ .../dist/development/data-CQbyyGzl.d.mts | 11 + .../dist/development/data-CQbyyGzl.d.ts | 11 + .../dist/development/dom-export.d.mts | 23 + .../dist/development/dom-export.d.ts | 23 + .../dist/development/dom-export.js | 6279 ++++ .../dist/development/dom-export.mjs | 246 + .../development/fog-of-war-CyHis97d.d.mts | 1691 + .../dist/development/fog-of-war-D4x86-Xc.d.ts | 1691 + .../react-router/dist/development/index.d.mts | 845 + .../react-router/dist/development/index.d.ts | 845 + .../react-router/dist/development/index.js | 10865 +++++++ .../react-router/dist/development/index.mjs | 244 + .../development/lib/types/route-module.d.mts | 209 + .../development/lib/types/route-module.d.ts | 209 + .../development/lib/types/route-module.js | 28 + .../development/lib/types/route-module.mjs | 10 + .../development/route-data-OcOrqK13.d.mts | 1739 ++ .../dist/development/route-data-OcOrqK13.d.ts | 1739 ++ .../dist/production/chunk-SAWFLE7G.mjs | 10716 +++++++ .../dist/production/data-CQbyyGzl.d.mts | 11 + .../dist/production/data-CQbyyGzl.d.ts | 11 + .../dist/production/dom-export.d.mts | 23 + .../dist/production/dom-export.d.ts | 23 + .../dist/production/dom-export.js | 6279 ++++ .../dist/production/dom-export.mjs | 246 + .../dist/production/fog-of-war-CyHis97d.d.mts | 1691 + .../dist/production/fog-of-war-D4x86-Xc.d.ts | 1691 + .../react-router/dist/production/index.d.mts | 845 + .../react-router/dist/production/index.d.ts | 845 + .../react-router/dist/production/index.js | 10865 +++++++ .../react-router/dist/production/index.mjs | 244 + .../production/lib/types/route-module.d.mts | 209 + .../production/lib/types/route-module.d.ts | 209 + .../dist/production/lib/types/route-module.js | 28 + .../production/lib/types/route-module.mjs | 10 + .../dist/production/route-data-OcOrqK13.d.mts | 1739 ++ .../dist/production/route-data-OcOrqK13.d.ts | 1739 ++ node_modules/react-router/package.json | 115 + node_modules/react/LICENSE | 21 + node_modules/react/README.md | 37 + .../cjs/react-compiler-runtime.development.js | 24 + .../cjs/react-compiler-runtime.production.js | 16 + .../cjs/react-compiler-runtime.profiling.js | 16 + .../cjs/react-jsx-dev-runtime.development.js | 349 + .../cjs/react-jsx-dev-runtime.production.js | 14 + .../cjs/react-jsx-dev-runtime.profiling.js | 14 + ...sx-dev-runtime.react-server.development.js | 385 + ...jsx-dev-runtime.react-server.production.js | 40 + .../cjs/react-jsx-runtime.development.js | 358 + .../react/cjs/react-jsx-runtime.production.js | 34 + .../react/cjs/react-jsx-runtime.profiling.js | 34 + ...ct-jsx-runtime.react-server.development.js | 385 + ...act-jsx-runtime.react-server.production.js | 40 + node_modules/react/cjs/react.development.js | 1242 + node_modules/react/cjs/react.production.js | 546 + .../cjs/react.react-server.development.js | 815 + .../cjs/react.react-server.production.js | 429 + node_modules/react/compiler-runtime.js | 14 + node_modules/react/index.js | 7 + node_modules/react/jsx-dev-runtime.js | 7 + .../react/jsx-dev-runtime.react-server.js | 7 + node_modules/react/jsx-runtime.js | 7 + .../react/jsx-runtime.react-server.js | 7 + node_modules/react/package.json | 51 + node_modules/react/react.react-server.js | 7 + node_modules/scheduler/LICENSE | 21 + node_modules/scheduler/README.md | 9 + .../scheduler-unstable_mock.development.js | 414 + .../cjs/scheduler-unstable_mock.production.js | 406 + ...cheduler-unstable_post_task.development.js | 150 + ...scheduler-unstable_post_task.production.js | 140 + .../scheduler/cjs/scheduler.development.js | 364 + .../cjs/scheduler.native.development.js | 350 + .../cjs/scheduler.native.production.js | 330 + .../scheduler/cjs/scheduler.production.js | 340 + node_modules/scheduler/index.js | 7 + node_modules/scheduler/index.native.js | 7 + node_modules/scheduler/package.json | 27 + node_modules/scheduler/unstable_mock.js | 7 + node_modules/scheduler/unstable_post_task.js | 7 + node_modules/set-cookie-parser/LICENSE | 21 + node_modules/set-cookie-parser/README.md | 202 + .../set-cookie-parser/lib/set-cookie.js | 224 + node_modules/set-cookie-parser/package.json | 45 + node_modules/turbo-stream/LICENSE | 7 + node_modules/turbo-stream/README.md | 31 + node_modules/turbo-stream/dist/flatten.d.ts | 2 + node_modules/turbo-stream/dist/flatten.js | 203 + .../turbo-stream/dist/turbo-stream.d.ts | 13 + .../turbo-stream/dist/turbo-stream.js | 207 + .../turbo-stream/dist/turbo-stream.mjs | 673 + node_modules/turbo-stream/dist/unflatten.d.ts | 2 + node_modules/turbo-stream/dist/unflatten.js | 243 + node_modules/turbo-stream/dist/utils.d.ts | 44 + node_modules/turbo-stream/dist/utils.js | 55 + node_modules/turbo-stream/package.json | 50 + package-lock.json | 102 + package.json | 5 + .../kuarion/backend/config/CorsConfig.java | 20 + .../backend/config/SecurityConfig.java | 92 +- .../controller/AuthenticationController.java | 47 +- .../backend/controller/CommentController.java | 33 + .../backend/controller/GeminiController.java | 72 +- .../backend/controller/PostController.java | 68 + .../kuarion/backend/dtos/PostCommentDTO.java | 28 + .../com/kuarion/backend/entities/Comment.java | 23 + .../com/kuarion/backend/entities/Post.java | 22 + .../errors/ResourceNotFoundException.java | 8 + .../repositories/CommentRepository.java | 12 + .../backend/repositories/PostRepository.java | 9 + .../backend/service/CommentService.java | 33 + .../kuarion/backend/service/PostService.java | 62 + .../kuarion-front-end/.gitignore | 0 .../kuarion-front-end/README.md | 0 .../kuarion-front-end/eslint.config.js | 0 .../kuarion-front-end/index.html | 0 .../kuarion-front-end/package-lock.json | 292 + .../kuarion-front-end/package.json | 3 + .../kuarion-front-end/public/Kuarion.svg | 0 .../kuarion-front-end/src/App.css | 0 .../static/kuarion-front-end/src/App.jsx | 24 + .../kuarion-front-end/src/Router.jsx | 0 .../src/assets/kuarion-logo.png | Bin .../kuarion-front-end/src/index.css | 0 .../kuarion-front-end/src/main.jsx | 0 .../kuarion-front-end/src/pages/Chat.jsx | 79 + .../kuarion-front-end/src/pages/Forum.jsx | 127 + .../src/pages/ForumDetail.jsx | 101 + .../kuarion-front-end/src/pages}/Index.jsx | 0 .../kuarion-front-end/src/pages/Login.jsx | 79 + .../kuarion-front-end/src/pages}/NotFound.jsx | 0 .../kuarion-front-end/src/pages/Register.jsx | 114 + .../kuarion-front-end/vite.config.js | 0 .../statics/kuarion-front-end/src/App.jsx | 15 - .../src/pages/Login/Login.jsx | 7 - 197 files changed, 264049 insertions(+), 108 deletions(-) create mode 100644 node_modules/.package-lock.json create mode 100644 node_modules/cookie/LICENSE create mode 100644 node_modules/cookie/README.md create mode 100644 node_modules/cookie/dist/index.d.ts create mode 100644 node_modules/cookie/dist/index.js create mode 100644 node_modules/cookie/dist/index.js.map create mode 100644 node_modules/cookie/package.json create mode 100644 node_modules/react-dom/LICENSE create mode 100644 node_modules/react-dom/README.md create mode 100644 node_modules/react-dom/cjs/react-dom-client.development.js create mode 100644 node_modules/react-dom/cjs/react-dom-client.production.js create mode 100644 node_modules/react-dom/cjs/react-dom-profiling.development.js create mode 100644 node_modules/react-dom/cjs/react-dom-profiling.profiling.js create mode 100644 node_modules/react-dom/cjs/react-dom-server-legacy.browser.development.js create mode 100644 node_modules/react-dom/cjs/react-dom-server-legacy.browser.production.js create mode 100644 node_modules/react-dom/cjs/react-dom-server-legacy.node.development.js create mode 100644 node_modules/react-dom/cjs/react-dom-server-legacy.node.production.js create mode 100644 node_modules/react-dom/cjs/react-dom-server.browser.development.js create mode 100644 node_modules/react-dom/cjs/react-dom-server.browser.production.js create mode 100644 node_modules/react-dom/cjs/react-dom-server.bun.development.js create mode 100644 node_modules/react-dom/cjs/react-dom-server.bun.production.js create mode 100644 node_modules/react-dom/cjs/react-dom-server.edge.development.js create mode 100644 node_modules/react-dom/cjs/react-dom-server.edge.production.js create mode 100644 node_modules/react-dom/cjs/react-dom-server.node.development.js create mode 100644 node_modules/react-dom/cjs/react-dom-server.node.production.js create mode 100644 node_modules/react-dom/cjs/react-dom-test-utils.development.js create mode 100644 node_modules/react-dom/cjs/react-dom-test-utils.production.js create mode 100644 node_modules/react-dom/cjs/react-dom.development.js create mode 100644 node_modules/react-dom/cjs/react-dom.production.js create mode 100644 node_modules/react-dom/cjs/react-dom.react-server.development.js create mode 100644 node_modules/react-dom/cjs/react-dom.react-server.production.js create mode 100644 node_modules/react-dom/client.js create mode 100644 node_modules/react-dom/client.react-server.js create mode 100644 node_modules/react-dom/index.js create mode 100644 node_modules/react-dom/package.json create mode 100644 node_modules/react-dom/profiling.js create mode 100644 node_modules/react-dom/profiling.react-server.js create mode 100644 node_modules/react-dom/react-dom.react-server.js create mode 100644 node_modules/react-dom/server.browser.js create mode 100644 node_modules/react-dom/server.bun.js create mode 100644 node_modules/react-dom/server.edge.js create mode 100644 node_modules/react-dom/server.js create mode 100644 node_modules/react-dom/server.node.js create mode 100644 node_modules/react-dom/server.react-server.js create mode 100644 node_modules/react-dom/static.browser.js create mode 100644 node_modules/react-dom/static.edge.js create mode 100644 node_modules/react-dom/static.js create mode 100644 node_modules/react-dom/static.node.js create mode 100644 node_modules/react-dom/static.react-server.js create mode 100644 node_modules/react-dom/test-utils.js create mode 100644 node_modules/react-router-dom/LICENSE.md create mode 100644 node_modules/react-router-dom/README.md create mode 100644 node_modules/react-router-dom/dist/index.d.mts create mode 100644 node_modules/react-router-dom/dist/index.d.ts create mode 100644 node_modules/react-router-dom/dist/index.js create mode 100644 node_modules/react-router-dom/dist/index.mjs create mode 100644 node_modules/react-router-dom/package.json create mode 100644 node_modules/react-router/CHANGELOG.md create mode 100644 node_modules/react-router/LICENSE.md create mode 100644 node_modules/react-router/README.md create mode 100644 node_modules/react-router/dist/development/chunk-LSOULM7L.mjs create mode 100644 node_modules/react-router/dist/development/data-CQbyyGzl.d.mts create mode 100644 node_modules/react-router/dist/development/data-CQbyyGzl.d.ts create mode 100644 node_modules/react-router/dist/development/dom-export.d.mts create mode 100644 node_modules/react-router/dist/development/dom-export.d.ts create mode 100644 node_modules/react-router/dist/development/dom-export.js create mode 100644 node_modules/react-router/dist/development/dom-export.mjs create mode 100644 node_modules/react-router/dist/development/fog-of-war-CyHis97d.d.mts create mode 100644 node_modules/react-router/dist/development/fog-of-war-D4x86-Xc.d.ts create mode 100644 node_modules/react-router/dist/development/index.d.mts create mode 100644 node_modules/react-router/dist/development/index.d.ts create mode 100644 node_modules/react-router/dist/development/index.js create mode 100644 node_modules/react-router/dist/development/index.mjs create mode 100644 node_modules/react-router/dist/development/lib/types/route-module.d.mts create mode 100644 node_modules/react-router/dist/development/lib/types/route-module.d.ts create mode 100644 node_modules/react-router/dist/development/lib/types/route-module.js create mode 100644 node_modules/react-router/dist/development/lib/types/route-module.mjs create mode 100644 node_modules/react-router/dist/development/route-data-OcOrqK13.d.mts create mode 100644 node_modules/react-router/dist/development/route-data-OcOrqK13.d.ts create mode 100644 node_modules/react-router/dist/production/chunk-SAWFLE7G.mjs create mode 100644 node_modules/react-router/dist/production/data-CQbyyGzl.d.mts create mode 100644 node_modules/react-router/dist/production/data-CQbyyGzl.d.ts create mode 100644 node_modules/react-router/dist/production/dom-export.d.mts create mode 100644 node_modules/react-router/dist/production/dom-export.d.ts create mode 100644 node_modules/react-router/dist/production/dom-export.js create mode 100644 node_modules/react-router/dist/production/dom-export.mjs create mode 100644 node_modules/react-router/dist/production/fog-of-war-CyHis97d.d.mts create mode 100644 node_modules/react-router/dist/production/fog-of-war-D4x86-Xc.d.ts create mode 100644 node_modules/react-router/dist/production/index.d.mts create mode 100644 node_modules/react-router/dist/production/index.d.ts create mode 100644 node_modules/react-router/dist/production/index.js create mode 100644 node_modules/react-router/dist/production/index.mjs create mode 100644 node_modules/react-router/dist/production/lib/types/route-module.d.mts create mode 100644 node_modules/react-router/dist/production/lib/types/route-module.d.ts create mode 100644 node_modules/react-router/dist/production/lib/types/route-module.js create mode 100644 node_modules/react-router/dist/production/lib/types/route-module.mjs create mode 100644 node_modules/react-router/dist/production/route-data-OcOrqK13.d.mts create mode 100644 node_modules/react-router/dist/production/route-data-OcOrqK13.d.ts create mode 100644 node_modules/react-router/package.json create mode 100644 node_modules/react/LICENSE create mode 100644 node_modules/react/README.md create mode 100644 node_modules/react/cjs/react-compiler-runtime.development.js create mode 100644 node_modules/react/cjs/react-compiler-runtime.production.js create mode 100644 node_modules/react/cjs/react-compiler-runtime.profiling.js create mode 100644 node_modules/react/cjs/react-jsx-dev-runtime.development.js create mode 100644 node_modules/react/cjs/react-jsx-dev-runtime.production.js create mode 100644 node_modules/react/cjs/react-jsx-dev-runtime.profiling.js create mode 100644 node_modules/react/cjs/react-jsx-dev-runtime.react-server.development.js create mode 100644 node_modules/react/cjs/react-jsx-dev-runtime.react-server.production.js create mode 100644 node_modules/react/cjs/react-jsx-runtime.development.js create mode 100644 node_modules/react/cjs/react-jsx-runtime.production.js create mode 100644 node_modules/react/cjs/react-jsx-runtime.profiling.js create mode 100644 node_modules/react/cjs/react-jsx-runtime.react-server.development.js create mode 100644 node_modules/react/cjs/react-jsx-runtime.react-server.production.js create mode 100644 node_modules/react/cjs/react.development.js create mode 100644 node_modules/react/cjs/react.production.js create mode 100644 node_modules/react/cjs/react.react-server.development.js create mode 100644 node_modules/react/cjs/react.react-server.production.js create mode 100644 node_modules/react/compiler-runtime.js create mode 100644 node_modules/react/index.js create mode 100644 node_modules/react/jsx-dev-runtime.js create mode 100644 node_modules/react/jsx-dev-runtime.react-server.js create mode 100644 node_modules/react/jsx-runtime.js create mode 100644 node_modules/react/jsx-runtime.react-server.js create mode 100644 node_modules/react/package.json create mode 100644 node_modules/react/react.react-server.js create mode 100644 node_modules/scheduler/LICENSE create mode 100644 node_modules/scheduler/README.md create mode 100644 node_modules/scheduler/cjs/scheduler-unstable_mock.development.js create mode 100644 node_modules/scheduler/cjs/scheduler-unstable_mock.production.js create mode 100644 node_modules/scheduler/cjs/scheduler-unstable_post_task.development.js create mode 100644 node_modules/scheduler/cjs/scheduler-unstable_post_task.production.js create mode 100644 node_modules/scheduler/cjs/scheduler.development.js create mode 100644 node_modules/scheduler/cjs/scheduler.native.development.js create mode 100644 node_modules/scheduler/cjs/scheduler.native.production.js create mode 100644 node_modules/scheduler/cjs/scheduler.production.js create mode 100644 node_modules/scheduler/index.js create mode 100644 node_modules/scheduler/index.native.js create mode 100644 node_modules/scheduler/package.json create mode 100644 node_modules/scheduler/unstable_mock.js create mode 100644 node_modules/scheduler/unstable_post_task.js create mode 100644 node_modules/set-cookie-parser/LICENSE create mode 100644 node_modules/set-cookie-parser/README.md create mode 100644 node_modules/set-cookie-parser/lib/set-cookie.js create mode 100644 node_modules/set-cookie-parser/package.json create mode 100644 node_modules/turbo-stream/LICENSE create mode 100644 node_modules/turbo-stream/README.md create mode 100644 node_modules/turbo-stream/dist/flatten.d.ts create mode 100644 node_modules/turbo-stream/dist/flatten.js create mode 100644 node_modules/turbo-stream/dist/turbo-stream.d.ts create mode 100644 node_modules/turbo-stream/dist/turbo-stream.js create mode 100644 node_modules/turbo-stream/dist/turbo-stream.mjs create mode 100644 node_modules/turbo-stream/dist/unflatten.d.ts create mode 100644 node_modules/turbo-stream/dist/unflatten.js create mode 100644 node_modules/turbo-stream/dist/utils.d.ts create mode 100644 node_modules/turbo-stream/dist/utils.js create mode 100644 node_modules/turbo-stream/package.json create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/main/java/com/kuarion/backend/config/CorsConfig.java create mode 100644 src/main/java/com/kuarion/backend/controller/CommentController.java create mode 100644 src/main/java/com/kuarion/backend/controller/PostController.java create mode 100644 src/main/java/com/kuarion/backend/dtos/PostCommentDTO.java create mode 100644 src/main/java/com/kuarion/backend/entities/Comment.java create mode 100644 src/main/java/com/kuarion/backend/entities/Post.java create mode 100644 src/main/java/com/kuarion/backend/errors/ResourceNotFoundException.java create mode 100644 src/main/java/com/kuarion/backend/repositories/CommentRepository.java create mode 100644 src/main/java/com/kuarion/backend/repositories/PostRepository.java create mode 100644 src/main/java/com/kuarion/backend/service/CommentService.java create mode 100644 src/main/java/com/kuarion/backend/service/PostService.java rename src/main/resources/{statics => static}/kuarion-front-end/.gitignore (100%) rename src/main/resources/{statics => static}/kuarion-front-end/README.md (100%) rename src/main/resources/{statics => static}/kuarion-front-end/eslint.config.js (100%) rename src/main/resources/{statics => static}/kuarion-front-end/index.html (100%) rename src/main/resources/{statics => static}/kuarion-front-end/package-lock.json (90%) rename src/main/resources/{statics => static}/kuarion-front-end/package.json (92%) rename src/main/resources/{statics => static}/kuarion-front-end/public/Kuarion.svg (100%) rename src/main/resources/{statics => static}/kuarion-front-end/src/App.css (100%) create mode 100644 src/main/resources/static/kuarion-front-end/src/App.jsx rename src/main/resources/{statics => static}/kuarion-front-end/src/Router.jsx (100%) rename src/main/resources/{statics => static}/kuarion-front-end/src/assets/kuarion-logo.png (100%) rename src/main/resources/{statics => static}/kuarion-front-end/src/index.css (100%) rename src/main/resources/{statics => static}/kuarion-front-end/src/main.jsx (100%) create mode 100644 src/main/resources/static/kuarion-front-end/src/pages/Chat.jsx create mode 100644 src/main/resources/static/kuarion-front-end/src/pages/Forum.jsx create mode 100644 src/main/resources/static/kuarion-front-end/src/pages/ForumDetail.jsx rename src/main/resources/{statics/kuarion-front-end/src/pages/Index => static/kuarion-front-end/src/pages}/Index.jsx (100%) create mode 100644 src/main/resources/static/kuarion-front-end/src/pages/Login.jsx rename src/main/resources/{statics/kuarion-front-end/src/pages/NotFound => static/kuarion-front-end/src/pages}/NotFound.jsx (100%) create mode 100644 src/main/resources/static/kuarion-front-end/src/pages/Register.jsx rename src/main/resources/{statics => static}/kuarion-front-end/vite.config.js (100%) delete mode 100644 src/main/resources/statics/kuarion-front-end/src/App.jsx delete mode 100644 src/main/resources/statics/kuarion-front-end/src/pages/Login/Login.jsx diff --git a/.gitignore b/.gitignore index cff808a..ec39ec6 100644 --- a/.gitignore +++ b/.gitignore @@ -36,5 +36,6 @@ build/ # Arquivos de configuração local src/main/resources/application-local.properties +src/main/resources/application-dev.properties *.secret.* /secrets/ \ No newline at end of file diff --git a/node_modules/.package-lock.json b/node_modules/.package-lock.json new file mode 100644 index 0000000..e8c3f8a --- /dev/null +++ b/node_modules/.package-lock.json @@ -0,0 +1,97 @@ +{ + "name": "site", + "lockfileVersion": 3, + "requires": true, + "packages": { + "node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/react": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", + "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", + "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", + "license": "MIT", + "peer": true, + "dependencies": { + "scheduler": "^0.26.0" + }, + "peerDependencies": { + "react": "^19.1.0" + } + }, + "node_modules/react-router": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.5.1.tgz", + "integrity": "sha512-/jjU3fcYNd2bwz9Q0xt5TwyiyoO8XjSEFXJY4O/lMAlkGTHWuHRAbR9Etik+lSDqMC7A7mz3UlXzgYT6Vl58sA==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0", + "turbo-stream": "2.4.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.5.1.tgz", + "integrity": "sha512-5DPSPc7ENrt2tlKPq0FtpG80ZbqA9aIKEyqX6hSNJDlol/tr6iqCK4crqdsusmOSSotq6zDsn0y3urX9TuTNmA==", + "license": "MIT", + "dependencies": { + "react-router": "7.5.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/scheduler": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", + "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "license": "MIT", + "peer": true + }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "license": "MIT" + }, + "node_modules/turbo-stream": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz", + "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==", + "license": "ISC" + } + } +} diff --git a/node_modules/cookie/LICENSE b/node_modules/cookie/LICENSE new file mode 100644 index 0000000..058b6b4 --- /dev/null +++ b/node_modules/cookie/LICENSE @@ -0,0 +1,24 @@ +(The MIT License) + +Copyright (c) 2012-2014 Roman Shtylman +Copyright (c) 2015 Douglas Christopher Wilson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/node_modules/cookie/README.md b/node_modules/cookie/README.md new file mode 100644 index 0000000..54e1cda --- /dev/null +++ b/node_modules/cookie/README.md @@ -0,0 +1,248 @@ +# cookie + +[![NPM Version][npm-version-image]][npm-url] +[![NPM Downloads][npm-downloads-image]][npm-url] +[![Build Status][ci-image]][ci-url] +[![Coverage Status][coverage-image]][coverage-url] + +Basic HTTP cookie parser and serializer for HTTP servers. + +## Installation + +```sh +$ npm install cookie +``` + +## API + +```js +const cookie = require("cookie"); +// import * as cookie from 'cookie'; +``` + +### cookie.parse(str, options) + +Parse a HTTP `Cookie` header string and returning an object of all cookie name-value pairs. +The `str` argument is the string representing a `Cookie` header value and `options` is an +optional object containing additional parsing options. + +```js +const cookies = cookie.parse("foo=bar; equation=E%3Dmc%5E2"); +// { foo: 'bar', equation: 'E=mc^2' } +``` + +#### Options + +`cookie.parse` accepts these properties in the options object. + +##### decode + +Specifies a function that will be used to decode a [cookie-value](https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.1). +Since the value of a cookie has a limited character set (and must be a simple string), this function can be used to decode +a previously-encoded cookie value into a JavaScript string. + +The default function is the global `decodeURIComponent`, wrapped in a `try..catch`. If an error +is thrown it will return the cookie's original value. If you provide your own encode/decode +scheme you must ensure errors are appropriately handled. + +### cookie.serialize(name, value, options) + +Serialize a cookie name-value pair into a `Set-Cookie` header string. The `name` argument is the +name for the cookie, the `value` argument is the value to set the cookie to, and the `options` +argument is an optional object containing additional serialization options. + +```js +const setCookie = cookie.serialize("foo", "bar"); +// foo=bar +``` + +#### Options + +`cookie.serialize` accepts these properties in the options object. + +##### encode + +Specifies a function that will be used to encode a [cookie-value](https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.1). +Since value of a cookie has a limited character set (and must be a simple string), this function can be used to encode +a value into a string suited for a cookie's value, and should mirror `decode` when parsing. + +The default function is the global `encodeURIComponent`. + +##### maxAge + +Specifies the `number` (in seconds) to be the value for the [`Max-Age` `Set-Cookie` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.2). + +The [cookie storage model specification](https://tools.ietf.org/html/rfc6265#section-5.3) states that if both `expires` and +`maxAge` are set, then `maxAge` takes precedence, but it is possible not all clients by obey this, +so if both are set, they should point to the same date and time. + +##### expires + +Specifies the `Date` object to be the value for the [`Expires` `Set-Cookie` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.1). +When no expiration is set clients consider this a "non-persistent cookie" and delete it the current session is over. + +The [cookie storage model specification](https://tools.ietf.org/html/rfc6265#section-5.3) states that if both `expires` and +`maxAge` are set, then `maxAge` takes precedence, but it is possible not all clients by obey this, +so if both are set, they should point to the same date and time. + +##### domain + +Specifies the value for the [`Domain` `Set-Cookie` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.3). +When no domain is set clients consider the cookie to apply to the current domain only. + +##### path + +Specifies the value for the [`Path` `Set-Cookie` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.4). +When no path is set, the path is considered the ["default path"](https://tools.ietf.org/html/rfc6265#section-5.1.4). + +##### httpOnly + +Enables the [`HttpOnly` `Set-Cookie` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.6). +When enabled, clients will not allow client-side JavaScript to see the cookie in `document.cookie`. + +##### secure + +Enables the [`Secure` `Set-Cookie` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.5). +When enabled, clients will only send the cookie back if the browser has a HTTPS connection. + +##### partitioned + +Enables the [`Partitioned` `Set-Cookie` attribute](https://tools.ietf.org/html/draft-cutler-httpbis-partitioned-cookies/). +When enabled, clients will only send the cookie back when the current domain _and_ top-level domain matches. + +This is an attribute that has not yet been fully standardized, and may change in the future. +This also means clients may ignore this attribute until they understand it. More information +about can be found in [the proposal](https://github.com/privacycg/CHIPS). + +##### priority + +Specifies the value for the [`Priority` `Set-Cookie` attribute](https://tools.ietf.org/html/draft-west-cookie-priority-00#section-4.1). + +- `'low'` will set the `Priority` attribute to `Low`. +- `'medium'` will set the `Priority` attribute to `Medium`, the default priority when not set. +- `'high'` will set the `Priority` attribute to `High`. + +More information about priority levels can be found in [the specification](https://tools.ietf.org/html/draft-west-cookie-priority-00#section-4.1). + +##### sameSite + +Specifies the value for the [`SameSite` `Set-Cookie` attribute](https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-09#section-5.4.7). + +- `true` will set the `SameSite` attribute to `Strict` for strict same site enforcement. +- `'lax'` will set the `SameSite` attribute to `Lax` for lax same site enforcement. +- `'none'` will set the `SameSite` attribute to `None` for an explicit cross-site cookie. +- `'strict'` will set the `SameSite` attribute to `Strict` for strict same site enforcement. + +More information about enforcement levels can be found in [the specification](https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-09#section-5.4.7). + +## Example + +The following example uses this module in conjunction with the Node.js core HTTP server +to prompt a user for their name and display it back on future visits. + +```js +var cookie = require("cookie"); +var escapeHtml = require("escape-html"); +var http = require("http"); +var url = require("url"); + +function onRequest(req, res) { + // Parse the query string + var query = url.parse(req.url, true, true).query; + + if (query && query.name) { + // Set a new cookie with the name + res.setHeader( + "Set-Cookie", + cookie.serialize("name", String(query.name), { + httpOnly: true, + maxAge: 60 * 60 * 24 * 7, // 1 week + }), + ); + + // Redirect back after setting cookie + res.statusCode = 302; + res.setHeader("Location", req.headers.referer || "/"); + res.end(); + return; + } + + // Parse the cookies on the request + var cookies = cookie.parse(req.headers.cookie || ""); + + // Get the visitor name set in the cookie + var name = cookies.name; + + res.setHeader("Content-Type", "text/html; charset=UTF-8"); + + if (name) { + res.write("

Welcome back, " + escapeHtml(name) + "!

"); + } else { + res.write("

Hello, new visitor!

"); + } + + res.write('
'); + res.write( + ' ', + ); + res.end("
"); +} + +http.createServer(onRequest).listen(3000); +``` + +## Testing + +```sh +npm test +``` + +## Benchmark + +```sh +npm run bench +``` + +``` + name hz min max mean p75 p99 p995 p999 rme samples + · simple 8,566,313.09 0.0000 0.3694 0.0001 0.0001 0.0002 0.0002 0.0003 ±0.64% 4283157 fastest + · decode 3,834,348.85 0.0001 0.2465 0.0003 0.0003 0.0003 0.0004 0.0006 ±0.38% 1917175 + · unquote 8,315,355.96 0.0000 0.3824 0.0001 0.0001 0.0002 0.0002 0.0003 ±0.72% 4157880 + · duplicates 1,944,765.97 0.0004 0.2959 0.0005 0.0005 0.0006 0.0006 0.0008 ±0.24% 972384 + · 10 cookies 675,345.67 0.0012 0.4328 0.0015 0.0015 0.0019 0.0020 0.0058 ±0.75% 337673 + · 100 cookies 61,040.71 0.0152 0.4092 0.0164 0.0160 0.0196 0.0228 0.2260 ±0.71% 30521 slowest + ✓ parse top-sites (15) 22945ms + name hz min max mean p75 p99 p995 p999 rme samples + · parse accounts.google.com 7,164,349.17 0.0000 0.0929 0.0001 0.0002 0.0002 0.0002 0.0003 ±0.09% 3582184 + · parse apple.com 7,817,686.84 0.0000 0.6048 0.0001 0.0001 0.0002 0.0002 0.0003 ±1.05% 3908844 + · parse cloudflare.com 7,189,841.70 0.0000 0.0390 0.0001 0.0002 0.0002 0.0002 0.0003 ±0.06% 3594921 + · parse docs.google.com 7,051,765.61 0.0000 0.0296 0.0001 0.0002 0.0002 0.0002 0.0003 ±0.06% 3525883 + · parse drive.google.com 7,349,104.77 0.0000 0.0368 0.0001 0.0001 0.0002 0.0002 0.0003 ±0.05% 3674553 + · parse en.wikipedia.org 1,929,909.49 0.0004 0.3598 0.0005 0.0005 0.0007 0.0007 0.0012 ±0.16% 964955 + · parse linkedin.com 2,225,658.01 0.0003 0.0595 0.0004 0.0005 0.0005 0.0005 0.0006 ±0.06% 1112830 + · parse maps.google.com 4,423,511.68 0.0001 0.0942 0.0002 0.0003 0.0003 0.0003 0.0005 ±0.08% 2211756 + · parse microsoft.com 3,387,601.88 0.0002 0.0725 0.0003 0.0003 0.0004 0.0004 0.0005 ±0.09% 1693801 + · parse play.google.com 7,375,980.86 0.0000 0.1994 0.0001 0.0001 0.0002 0.0002 0.0003 ±0.12% 3687991 + · parse support.google.com 4,912,267.94 0.0001 2.8958 0.0002 0.0002 0.0003 0.0003 0.0005 ±1.28% 2456134 + · parse www.google.com 3,443,035.87 0.0002 0.2783 0.0003 0.0003 0.0004 0.0004 0.0007 ±0.51% 1721518 + · parse youtu.be 1,910,492.87 0.0004 0.3490 0.0005 0.0005 0.0007 0.0007 0.0011 ±0.46% 955247 + · parse youtube.com 1,895,082.62 0.0004 0.7454 0.0005 0.0005 0.0006 0.0007 0.0013 ±0.64% 947542 slowest + · parse example.com 21,582,835.27 0.0000 0.1095 0.0000 0.0000 0.0001 0.0001 0.0001 ±0.13% 10791418 +``` + +## References + +- [RFC 6265: HTTP State Management Mechanism](https://tools.ietf.org/html/rfc6265) +- [Same-site Cookies](https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-09#section-5.4.7) + +## License + +[MIT](LICENSE) + +[ci-image]: https://img.shields.io/github/actions/workflow/status/jshttp/cookie/ci.yml +[ci-url]: https://github.com/jshttp/cookie/actions/workflows/ci.yml?query=branch%3Amaster +[coverage-image]: https://img.shields.io/codecov/c/github/jshttp/cookie/master +[coverage-url]: https://app.codecov.io/gh/jshttp/cookie +[npm-downloads-image]: https://img.shields.io/npm/dm/cookie +[npm-url]: https://npmjs.org/package/cookie +[npm-version-image]: https://img.shields.io/npm/v/cookie diff --git a/node_modules/cookie/dist/index.d.ts b/node_modules/cookie/dist/index.d.ts new file mode 100644 index 0000000..784b0db --- /dev/null +++ b/node_modules/cookie/dist/index.d.ts @@ -0,0 +1,114 @@ +/** + * Parse options. + */ +export interface ParseOptions { + /** + * Specifies a function that will be used to decode a [cookie-value](https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.1). + * Since the value of a cookie has a limited character set (and must be a simple string), this function can be used to decode + * a previously-encoded cookie value into a JavaScript string. + * + * The default function is the global `decodeURIComponent`, wrapped in a `try..catch`. If an error + * is thrown it will return the cookie's original value. If you provide your own encode/decode + * scheme you must ensure errors are appropriately handled. + * + * @default decode + */ + decode?: (str: string) => string | undefined; +} +/** + * Parse a cookie header. + * + * Parse the given cookie header string into an object + * The object has the various cookies as keys(names) => values + */ +export declare function parse(str: string, options?: ParseOptions): Record; +/** + * Serialize options. + */ +export interface SerializeOptions { + /** + * Specifies a function that will be used to encode a [cookie-value](https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.1). + * Since value of a cookie has a limited character set (and must be a simple string), this function can be used to encode + * a value into a string suited for a cookie's value, and should mirror `decode` when parsing. + * + * @default encodeURIComponent + */ + encode?: (str: string) => string; + /** + * Specifies the `number` (in seconds) to be the value for the [`Max-Age` `Set-Cookie` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.2). + * + * The [cookie storage model specification](https://tools.ietf.org/html/rfc6265#section-5.3) states that if both `expires` and + * `maxAge` are set, then `maxAge` takes precedence, but it is possible not all clients by obey this, + * so if both are set, they should point to the same date and time. + */ + maxAge?: number; + /** + * Specifies the `Date` object to be the value for the [`Expires` `Set-Cookie` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.1). + * When no expiration is set clients consider this a "non-persistent cookie" and delete it the current session is over. + * + * The [cookie storage model specification](https://tools.ietf.org/html/rfc6265#section-5.3) states that if both `expires` and + * `maxAge` are set, then `maxAge` takes precedence, but it is possible not all clients by obey this, + * so if both are set, they should point to the same date and time. + */ + expires?: Date; + /** + * Specifies the value for the [`Domain` `Set-Cookie` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.3). + * When no domain is set clients consider the cookie to apply to the current domain only. + */ + domain?: string; + /** + * Specifies the value for the [`Path` `Set-Cookie` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.4). + * When no path is set, the path is considered the ["default path"](https://tools.ietf.org/html/rfc6265#section-5.1.4). + */ + path?: string; + /** + * Enables the [`HttpOnly` `Set-Cookie` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.6). + * When enabled, clients will not allow client-side JavaScript to see the cookie in `document.cookie`. + */ + httpOnly?: boolean; + /** + * Enables the [`Secure` `Set-Cookie` attribute](https://tools.ietf.org/html/rfc6265#section-5.2.5). + * When enabled, clients will only send the cookie back if the browser has a HTTPS connection. + */ + secure?: boolean; + /** + * Enables the [`Partitioned` `Set-Cookie` attribute](https://tools.ietf.org/html/draft-cutler-httpbis-partitioned-cookies/). + * When enabled, clients will only send the cookie back when the current domain _and_ top-level domain matches. + * + * This is an attribute that has not yet been fully standardized, and may change in the future. + * This also means clients may ignore this attribute until they understand it. More information + * about can be found in [the proposal](https://github.com/privacycg/CHIPS). + */ + partitioned?: boolean; + /** + * Specifies the value for the [`Priority` `Set-Cookie` attribute](https://tools.ietf.org/html/draft-west-cookie-priority-00#section-4.1). + * + * - `'low'` will set the `Priority` attribute to `Low`. + * - `'medium'` will set the `Priority` attribute to `Medium`, the default priority when not set. + * - `'high'` will set the `Priority` attribute to `High`. + * + * More information about priority levels can be found in [the specification](https://tools.ietf.org/html/draft-west-cookie-priority-00#section-4.1). + */ + priority?: "low" | "medium" | "high"; + /** + * Specifies the value for the [`SameSite` `Set-Cookie` attribute](https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-09#section-5.4.7). + * + * - `true` will set the `SameSite` attribute to `Strict` for strict same site enforcement. + * - `'lax'` will set the `SameSite` attribute to `Lax` for lax same site enforcement. + * - `'none'` will set the `SameSite` attribute to `None` for an explicit cross-site cookie. + * - `'strict'` will set the `SameSite` attribute to `Strict` for strict same site enforcement. + * + * More information about enforcement levels can be found in [the specification](https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-09#section-5.4.7). + */ + sameSite?: boolean | "lax" | "strict" | "none"; +} +/** + * Serialize data into a cookie header. + * + * Serialize a name value pair into a cookie string suitable for + * http headers. An optional options object specifies cookie parameters. + * + * serialize('foo', 'bar', { httpOnly: true }) + * => "foo=bar; httpOnly" + */ +export declare function serialize(name: string, val: string, options?: SerializeOptions): string; diff --git a/node_modules/cookie/dist/index.js b/node_modules/cookie/dist/index.js new file mode 100644 index 0000000..423acb4 --- /dev/null +++ b/node_modules/cookie/dist/index.js @@ -0,0 +1,239 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.parse = parse; +exports.serialize = serialize; +/** + * RegExp to match cookie-name in RFC 6265 sec 4.1.1 + * This refers out to the obsoleted definition of token in RFC 2616 sec 2.2 + * which has been replaced by the token definition in RFC 7230 appendix B. + * + * cookie-name = token + * token = 1*tchar + * tchar = "!" / "#" / "$" / "%" / "&" / "'" / + * "*" / "+" / "-" / "." / "^" / "_" / + * "`" / "|" / "~" / DIGIT / ALPHA + * + * Note: Allowing more characters - https://github.com/jshttp/cookie/issues/191 + * Allow same range as cookie value, except `=`, which delimits end of name. + */ +const cookieNameRegExp = /^[\u0021-\u003A\u003C\u003E-\u007E]+$/; +/** + * RegExp to match cookie-value in RFC 6265 sec 4.1.1 + * + * cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE ) + * cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E + * ; US-ASCII characters excluding CTLs, + * ; whitespace DQUOTE, comma, semicolon, + * ; and backslash + * + * Allowing more characters: https://github.com/jshttp/cookie/issues/191 + * Comma, backslash, and DQUOTE are not part of the parsing algorithm. + */ +const cookieValueRegExp = /^[\u0021-\u003A\u003C-\u007E]*$/; +/** + * RegExp to match domain-value in RFC 6265 sec 4.1.1 + * + * domain-value = + * ; defined in [RFC1034], Section 3.5, as + * ; enhanced by [RFC1123], Section 2.1 + * =