From 8f553a7b1b23e0f9a591789f211f4c61b1f322ea Mon Sep 17 00:00:00 2001 From: Matithieu Date: Sun, 28 Dec 2025 11:35:46 +0100 Subject: [PATCH 1/2] refactor: move utils to common.utils --- .../spring/app/company/CompanyController.java | 11 +++--- .../spring/app/company/CompanyService.java | 2 +- .../spring/app/stripe/CustomerUtil.java | 2 +- .../spring/app/stripe/PaymentController.java | 6 ++-- .../spring/app/user/UserController.java | 15 +++----- .../example/spring/app/user/UserService.java | 2 +- .../UserCompanyStatusController.java | 4 +-- .../spring/app/webhook/WebHookController.java | 4 +-- .../{ => common}/utils/HeadersUtil.java | 16 +-------- .../example/spring/common/utils/JwtUtil.java | 35 +++++++++++++++++++ .../spring/{ => common}/utils/LogUtil.java | 6 +--- .../core/userQuota/UserQuotaAspect.java | 4 +-- .../core/userQuota/UserQuotaService.java | 2 +- .../security/JwtAuthenticationFilter.java | 11 +++--- 14 files changed, 65 insertions(+), 55 deletions(-) rename src/main/java/com/example/spring/{ => common}/utils/HeadersUtil.java (66%) create mode 100644 src/main/java/com/example/spring/common/utils/JwtUtil.java rename src/main/java/com/example/spring/{ => common}/utils/LogUtil.java (90%) diff --git a/src/main/java/com/example/spring/app/company/CompanyController.java b/src/main/java/com/example/spring/app/company/CompanyController.java index 6ffde36..b41c91e 100644 --- a/src/main/java/com/example/spring/app/company/CompanyController.java +++ b/src/main/java/com/example/spring/app/company/CompanyController.java @@ -16,8 +16,7 @@ import java.util.List; import java.util.stream.Collectors; -import static com.example.spring.utils.HeadersUtil.parseUserIdFromHeader; - +import static com.example.spring.common.utils.JwtUtil.extractUserIdFromToken; @CrossOrigin @RestController @@ -33,7 +32,7 @@ public class CompanyController { // Example: http://localhost:8080/api/v1/company/get-by-id/123 @GetMapping("/get-by-id/{id}") public CompanyDtoWithStatusDTO getCompanyById(@PathVariable("id") Integer id) { - String userId = parseUserIdFromHeader(); + String userId = extractUserIdFromToken(); CompanyDTO companyDto = companyService.getCompanyById(id).toCompanyDTO(); UserCompanyStatusModel userCompanyStatus = userCompanyStatusService .getOneUserCompanyStatusByUserIdAndCompanyId(userId, id); @@ -46,7 +45,7 @@ public CompanyDtoWithStatusDTO getCompanyById(@PathVariable("id") Integer id) { public Page getCompaniesSeenByUser(@RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size) { Pageable pageable = PageRequest.of(page, size); - String userId = parseUserIdFromHeader(); + String userId = extractUserIdFromToken(); Page companies = companyService.getCompaniesSeenByUser(userId, pageable); List userCompanyStatuses = userCompanyStatusService @@ -71,7 +70,7 @@ public Page searchCompaniesByName(@RequestParam("companyName") S @PostMapping("/filter-by-parameters") public Page getCompaniesByFilters( @RequestBody(required = false) CompanyFilterRequest filterRequest) { - String userId = parseUserIdFromHeader(); + String userId = extractUserIdFromToken(); Pageable pageable = PageRequest.of(filterRequest.getPage(), filterRequest.getSize()); Page companies = companyService.findCompaniesByFilters( @@ -101,7 +100,7 @@ public Page getCompaniesByFilters( public Page getRandomUnseenCompanies(@RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size) { Pageable pageable = PageRequest.of(page, size); - String userId = parseUserIdFromHeader(); + String userId = extractUserIdFromToken(); Page companies = companyService.findRandomUnseenCompanies(userId, pageable); List userCompanyStatuses = userCompanyStatusService .getMultipleUserCompanyStatusByUserIdAndCompanyIds(userId, companies.getContent() diff --git a/src/main/java/com/example/spring/app/company/CompanyService.java b/src/main/java/com/example/spring/app/company/CompanyService.java index 55482ca..d280b42 100644 --- a/src/main/java/com/example/spring/app/company/CompanyService.java +++ b/src/main/java/com/example/spring/app/company/CompanyService.java @@ -4,7 +4,7 @@ import com.example.spring.app.company.dto.NumberOfEmployeeFilterDTO; import com.example.spring.app.company.objects.ContactDTO; import com.example.spring.app.company.objects.SocialMediaDTO; -import com.example.spring.utils.LogUtil; +import com.example.spring.common.utils.LogUtil; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; diff --git a/src/main/java/com/example/spring/app/stripe/CustomerUtil.java b/src/main/java/com/example/spring/app/stripe/CustomerUtil.java index 102c5f9..072178a 100644 --- a/src/main/java/com/example/spring/app/stripe/CustomerUtil.java +++ b/src/main/java/com/example/spring/app/stripe/CustomerUtil.java @@ -1,7 +1,7 @@ package com.example.spring.app.stripe; import com.example.spring.app.user.UserDTO; -import com.example.spring.utils.LogUtil; +import com.example.spring.common.utils.LogUtil; import com.stripe.exception.StripeException; import com.stripe.model.Customer; import com.stripe.model.CustomerSearchResult; diff --git a/src/main/java/com/example/spring/app/stripe/PaymentController.java b/src/main/java/com/example/spring/app/stripe/PaymentController.java index b3693db..c8b15ce 100644 --- a/src/main/java/com/example/spring/app/stripe/PaymentController.java +++ b/src/main/java/com/example/spring/app/stripe/PaymentController.java @@ -2,7 +2,7 @@ import com.example.spring.app.user.UserDTO; import com.example.spring.core.keycloakClient.UserResource; -import com.example.spring.utils.LogUtil; +import com.example.spring.common.utils.LogUtil; import com.stripe.Stripe; import com.stripe.exception.StripeException; import com.stripe.model.Customer; @@ -18,7 +18,7 @@ import java.util.Map; import java.util.Objects; -import static com.example.spring.utils.HeadersUtil.parseUserIdFromHeader; +import static com.example.spring.common.utils.JwtUtil.extractUserIdFromToken; // https://kinsta.com/blog/stripe-java-api/ @@ -44,7 +44,7 @@ public ResponseEntity newSubscriptionWithTrial(@RequestHeader("X-priceId Stripe.apiKey = STRIPE_API_KEY; String clientBaseURL = "https://" + HOSTNAME + "/ui"; - String userId = parseUserIdFromHeader(); + String userId = extractUserIdFromToken(); // Find the user record from the database UserDTO user = userResource.getUserById(userId); diff --git a/src/main/java/com/example/spring/app/user/UserController.java b/src/main/java/com/example/spring/app/user/UserController.java index d0fb891..9ae8b61 100644 --- a/src/main/java/com/example/spring/app/user/UserController.java +++ b/src/main/java/com/example/spring/app/user/UserController.java @@ -5,7 +5,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; -import static com.example.spring.utils.HeadersUtil.parseUserIdFromHeader; +import static com.example.spring.common.utils.JwtUtil.extractUserIdFromToken; @CrossOrigin @RestController @@ -18,20 +18,20 @@ public class UserController { @GetMapping("/user") public UserDTO getUser() { - String userId = parseUserIdFromHeader(); + String userId = extractUserIdFromToken(); return userResource.getUserById(userId); } @PostMapping("/completeOnboarding") public Response completeOnboarding() { - String userId = parseUserIdFromHeader(); + String userId = extractUserIdFromToken(); userResource.completeOnboarding(userId); return Response.ok().build(); } @PutMapping("/update-user") public Response updateUser(@RequestParam UserDTO user) { - String id = parseUserIdFromHeader(); + String id = extractUserIdFromToken(); UserDTO existingUser = userResource.getUserById(id); if (existingUser != null) { @@ -46,11 +46,4 @@ public Response updateUser(@RequestParam UserDTO user) { return Response.status(Response.Status.BAD_REQUEST).build(); } - - /* - @GetMapping("/register") - public String registerEndpoint() { - return userResource.returnRegistrationEndpoint(); - } - */ } diff --git a/src/main/java/com/example/spring/app/user/UserService.java b/src/main/java/com/example/spring/app/user/UserService.java index e19a4f2..8fc4427 100644 --- a/src/main/java/com/example/spring/app/user/UserService.java +++ b/src/main/java/com/example/spring/app/user/UserService.java @@ -3,7 +3,7 @@ import com.example.spring.core.keycloakClient.UserResource; import com.example.spring.core.userQuota.UserQuotaModel; import com.example.spring.core.userQuota.UserQuotaRepository; -import com.example.spring.utils.LogUtil; +import com.example.spring.common.utils.LogUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled; diff --git a/src/main/java/com/example/spring/app/userCompanyStatus/UserCompanyStatusController.java b/src/main/java/com/example/spring/app/userCompanyStatus/UserCompanyStatusController.java index 60e6ad2..e95c8d4 100644 --- a/src/main/java/com/example/spring/app/userCompanyStatus/UserCompanyStatusController.java +++ b/src/main/java/com/example/spring/app/userCompanyStatus/UserCompanyStatusController.java @@ -8,7 +8,7 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import static com.example.spring.utils.HeadersUtil.parseUserIdFromHeader; +import static com.example.spring.common.utils.JwtUtil.extractUserIdFromToken; @RestController @RequestMapping("/v1/companies-status") @@ -20,7 +20,7 @@ public class UserCompanyStatusController { @PostMapping("/update-status") public ResponseEntity updateStatus(@RequestParam Integer companyId, @RequestParam Status status) { - String userId = parseUserIdFromHeader(); + String userId = extractUserIdFromToken(); UserCompanyStatusModel updated = userCompanyStatusService.updateCompanyStatus(userId, companyId, status); if (updated == null) { diff --git a/src/main/java/com/example/spring/app/webhook/WebHookController.java b/src/main/java/com/example/spring/app/webhook/WebHookController.java index 18d4e98..02c41e2 100644 --- a/src/main/java/com/example/spring/app/webhook/WebHookController.java +++ b/src/main/java/com/example/spring/app/webhook/WebHookController.java @@ -5,7 +5,7 @@ import com.example.spring.core.keycloakClient.RoleResource; import com.example.spring.core.keycloakClient.UserResource; import com.example.spring.core.userQuota.UserQuotaService; -import com.example.spring.utils.LogUtil; +import com.example.spring.common.utils.LogUtil; import com.google.gson.JsonSyntaxException; import com.stripe.exception.SignatureVerificationException; import com.stripe.exception.StripeException; @@ -81,7 +81,7 @@ private void handleChargeSucceeded(Event event) { private void handleSubscriptionCreated(Event event) throws StripeException { EventDataObjectDeserializer dataObjectDeserializer = event.getDataObjectDeserializer(); - StripeObject stripeObject = null; + StripeObject stripeObject; if (dataObjectDeserializer.getObject().isPresent()) { stripeObject = dataObjectDeserializer.getObject().get(); Subscription subscription = (Subscription) stripeObject; diff --git a/src/main/java/com/example/spring/utils/HeadersUtil.java b/src/main/java/com/example/spring/common/utils/HeadersUtil.java similarity index 66% rename from src/main/java/com/example/spring/utils/HeadersUtil.java rename to src/main/java/com/example/spring/common/utils/HeadersUtil.java index 2171491..86fdadb 100644 --- a/src/main/java/com/example/spring/utils/HeadersUtil.java +++ b/src/main/java/com/example/spring/common/utils/HeadersUtil.java @@ -1,4 +1,4 @@ -package com.example.spring.utils; +package com.example.spring.common.utils; import jakarta.servlet.http.HttpServletRequest; import org.springframework.web.context.request.RequestContextHolder; @@ -11,20 +11,6 @@ public class HeadersUtil { // Find the corresponding header of nginx: proxy_set_header X-User $user; - public static String parseEmailFromHeader() { - ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); - assert sra != null; - HttpServletRequest request = sra.getRequest(); - return request.getHeader("X-Auth-Request-Email"); - } - - public static String parseUserIdFromHeader() { - ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); - assert sra != null; - HttpServletRequest request = sra.getRequest(); - return request.getHeader("X-Auth-Request-User"); - } - public static String parseTokenFromHeader() { ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); assert sra != null; diff --git a/src/main/java/com/example/spring/common/utils/JwtUtil.java b/src/main/java/com/example/spring/common/utils/JwtUtil.java new file mode 100644 index 0000000..cf38907 --- /dev/null +++ b/src/main/java/com/example/spring/common/utils/JwtUtil.java @@ -0,0 +1,35 @@ +package com.example.spring.common.utils; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.util.Base64; + +import static com.example.spring.common.utils.HeadersUtil.parseTokenFromHeader; + +public class JwtUtil { + public static String[] splitToken(String token) { + return token.split("\\."); + } + + public static String decodePayload(String token) { + String[] parts = splitToken(token); + return new String(Base64.getDecoder().decode(parts[1])); + } + + public static String extractUserIdFromToken() { + String token = parseTokenFromHeader(); + String payload = decodePayload(token); + + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode jsonNode; + try { + jsonNode = objectMapper.readTree(payload); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + JsonNode node = jsonNode.path("sub"); + return node.asText(); + } +} diff --git a/src/main/java/com/example/spring/utils/LogUtil.java b/src/main/java/com/example/spring/common/utils/LogUtil.java similarity index 90% rename from src/main/java/com/example/spring/utils/LogUtil.java rename to src/main/java/com/example/spring/common/utils/LogUtil.java index 0a1d0a4..1874226 100644 --- a/src/main/java/com/example/spring/utils/LogUtil.java +++ b/src/main/java/com/example/spring/common/utils/LogUtil.java @@ -1,4 +1,4 @@ -package com.example.spring.utils; +package com.example.spring.common.utils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -23,10 +23,6 @@ public static void error(String message, Throwable throwable) { logger.error(message, throwable); } - public static void debug(String message) { - logger.debug(message); - } - private static String formatContext(Map context) { return context.entrySet().stream() .map(entry -> entry.getKey() + "=" + entry.getValue()) diff --git a/src/main/java/com/example/spring/core/userQuota/UserQuotaAspect.java b/src/main/java/com/example/spring/core/userQuota/UserQuotaAspect.java index d81db96..0bb2b30 100644 --- a/src/main/java/com/example/spring/core/userQuota/UserQuotaAspect.java +++ b/src/main/java/com/example/spring/core/userQuota/UserQuotaAspect.java @@ -17,7 +17,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; -import static com.example.spring.utils.HeadersUtil.parseUserIdFromHeader; +import static com.example.spring.common.utils.JwtUtil.extractUserIdFromToken; @Aspect @Component @@ -68,7 +68,7 @@ private void cleanupUnusedLocks() { @Around("allMethodsExceptExcluded()") public Object checkQuota(ProceedingJoinPoint joinPoint) throws Throwable { - String userId = parseUserIdFromHeader(); + String userId = extractUserIdFromToken(); // Get or create lock for this specific user ReentrantLock userLock = userLocks.computeIfAbsent(userId, k -> new ReentrantLock()); diff --git a/src/main/java/com/example/spring/core/userQuota/UserQuotaService.java b/src/main/java/com/example/spring/core/userQuota/UserQuotaService.java index e0391d5..d2372a1 100644 --- a/src/main/java/com/example/spring/core/userQuota/UserQuotaService.java +++ b/src/main/java/com/example/spring/core/userQuota/UserQuotaService.java @@ -2,7 +2,7 @@ import com.example.spring.core.appSettings.AppSettings; import com.example.spring.core.appSettings.AppSettingsRepository; -import com.example.spring.utils.LogUtil; +import com.example.spring.common.utils.LogUtil; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import jakarta.annotation.PostConstruct; diff --git a/src/main/java/com/example/spring/security/JwtAuthenticationFilter.java b/src/main/java/com/example/spring/security/JwtAuthenticationFilter.java index e3cc14f..c081c48 100644 --- a/src/main/java/com/example/spring/security/JwtAuthenticationFilter.java +++ b/src/main/java/com/example/spring/security/JwtAuthenticationFilter.java @@ -18,11 +18,12 @@ import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; -import java.util.Base64; import java.util.List; import java.util.stream.Collectors; -import static com.example.spring.utils.HeadersUtil.parseTokenFromHeader; +import static com.example.spring.common.utils.HeadersUtil.parseTokenFromHeader; +import static com.example.spring.common.utils.JwtUtil.decodePayload; +import static com.example.spring.common.utils.JwtUtil.splitToken; public class JwtAuthenticationFilter extends OncePerRequestFilter { @@ -31,11 +32,11 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse throws ServletException, IOException { String token = parseTokenFromHeader(); - if (token != null && StringUtils.hasText(token)) { + if (StringUtils.hasText(token)) { // Decode the JWT token - String[] parts = token.split("\\."); + String[] parts = splitToken(token); if (parts.length == 3) { - String payload = new String(Base64.getDecoder().decode(parts[1])); + String payload = decodePayload(token); // Extract roles from the decoded payload List roles = getRolesFromToken(payload); From d1be9783c52e8c82b97af617bde28eab7edc02c0 Mon Sep 17 00:00:00 2001 From: Matithieu Date: Sun, 28 Dec 2025 11:36:55 +0100 Subject: [PATCH 2/2] feat: introduce streamed ai --- .../example/spring/app/llm/LLMAnswerDTO.java | 10 +++++++++ .../example/spring/app/llm/LLMController.java | 21 ++++++++++++++++--- 2 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/example/spring/app/llm/LLMAnswerDTO.java diff --git a/src/main/java/com/example/spring/app/llm/LLMAnswerDTO.java b/src/main/java/com/example/spring/app/llm/LLMAnswerDTO.java new file mode 100644 index 0000000..45f72c3 --- /dev/null +++ b/src/main/java/com/example/spring/app/llm/LLMAnswerDTO.java @@ -0,0 +1,10 @@ +package com.example.spring.app.llm; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class LLMAnswerDTO { + private String answer; +} diff --git a/src/main/java/com/example/spring/app/llm/LLMController.java b/src/main/java/com/example/spring/app/llm/LLMController.java index 939cbd1..d3ffd19 100644 --- a/src/main/java/com/example/spring/app/llm/LLMController.java +++ b/src/main/java/com/example/spring/app/llm/LLMController.java @@ -1,7 +1,10 @@ package com.example.spring.app.llm; import org.springframework.ai.chat.client.ChatClient; +import org.springframework.ai.chat.model.ChatResponse; +import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; +import reactor.core.publisher.Flux; @CrossOrigin @RestController @@ -14,11 +17,23 @@ public LLMController(ChatClient.Builder chatClientBuilder) { this.chatClient = chatClientBuilder.build(); } - @GetMapping("/ai") - String generation(String userInput) { - return this.chatClient.prompt() + @GetMapping("/ask-ai") + LLMAnswerDTO generation(String userInput) { + LLMAnswerDTO llmAnswerDTO = new LLMAnswerDTO(); + String response = this.chatClient.prompt() .user(userInput) .call() .content(); + + llmAnswerDTO.setAnswer(response); + return llmAnswerDTO; + } + + @GetMapping(value = "/stream-ai", produces = MediaType.TEXT_EVENT_STREAM_VALUE) + public Flux streamGeneration(@RequestParam String userInput) { + return chatClient.prompt() + .user(userInput) + .stream() + .chatResponse(); } }