-
Notifications
You must be signed in to change notification settings - Fork 0
Dev #6
Changes from all commits
c2d44b9
25ad199
ff65981
3e42f36
4483a11
a2318ce
ea83337
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,82 @@ | ||||||
| package com.techtorque.admin_service.config; | ||||||
|
|
||||||
| import io.jsonwebtoken.Claims; | ||||||
| import io.jsonwebtoken.Jwts; | ||||||
| import io.jsonwebtoken.security.Keys; | ||||||
| import org.springframework.beans.factory.annotation.Value; | ||||||
| import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; | ||||||
| import org.springframework.security.core.authority.SimpleGrantedAuthority; | ||||||
| import org.springframework.security.core.context.SecurityContextHolder; | ||||||
| import org.springframework.stereotype.Component; | ||||||
| import org.springframework.web.filter.OncePerRequestFilter; | ||||||
|
|
||||||
| import jakarta.servlet.FilterChain; | ||||||
| import jakarta.servlet.ServletException; | ||||||
| import jakarta.servlet.http.HttpServletRequest; | ||||||
| import jakarta.servlet.http.HttpServletResponse; | ||||||
| import javax.crypto.SecretKey; | ||||||
| import java.io.IOException; | ||||||
| import java.nio.charset.StandardCharsets; | ||||||
| import java.util.Arrays; | ||||||
| import java.util.Collections; | ||||||
| import java.util.List; | ||||||
| import java.util.stream.Collectors; | ||||||
|
|
||||||
| @Component | ||||||
| public class JwtAuthenticationFilter extends OncePerRequestFilter { | ||||||
|
|
||||||
| @Value("${jwt.secret:mysecretkey}") | ||||||
|
||||||
| @Value("${jwt.secret:mysecretkey}") | |
| @Value("${jwt.secret}") |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,7 +5,12 @@ | |
| import org.springframework.context.annotation.Configuration; | ||
| import org.springframework.http.HttpHeaders; | ||
| import org.springframework.http.MediaType; | ||
| import org.springframework.web.context.request.RequestContextHolder; | ||
| import org.springframework.web.context.request.ServletRequestAttributes; | ||
| import org.springframework.web.reactive.function.client.WebClient; | ||
| import org.springframework.web.reactive.function.client.ExchangeFilterFunction; | ||
| import reactor.core.publisher.Mono; | ||
| import jakarta.servlet.http.HttpServletRequest; | ||
|
|
||
| /** | ||
| * Configuration for WebClient to communicate with other microservices | ||
|
|
@@ -31,12 +36,35 @@ public class WebClientConfig { | |
| @Value("${services.vehicle.url:http://localhost:8082}") | ||
| private String vehicleServiceUrl; | ||
|
|
||
| /** | ||
| * Exchange filter function to propagate JWT token from incoming request to outgoing WebClient calls | ||
| */ | ||
| private ExchangeFilterFunction jwtTokenPropagationFilter() { | ||
| return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> { | ||
| ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); | ||
| if (attributes != null) { | ||
| HttpServletRequest request = attributes.getRequest(); | ||
| String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION); | ||
| if (authHeader != null && authHeader.startsWith("Bearer ")) { | ||
| return Mono.just( | ||
| org.springframework.web.reactive.function.client.ClientRequest | ||
| .from(clientRequest) | ||
| .header(HttpHeaders.AUTHORIZATION, authHeader) | ||
| .build() | ||
| ); | ||
| } | ||
| } | ||
| return Mono.just(clientRequest); | ||
|
Comment on lines
+44
to
+57
|
||
| }); | ||
| } | ||
|
|
||
| @Bean(name = "authServiceWebClient") | ||
| public WebClient authServiceWebClient(WebClient.Builder webClientBuilder) { | ||
| return webClientBuilder | ||
| .baseUrl(authServiceUrl) | ||
| .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) | ||
| .defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE) | ||
| .filter(jwtTokenPropagationFilter()) | ||
| .build(); | ||
| } | ||
|
|
||
|
|
@@ -46,6 +74,7 @@ public WebClient paymentServiceWebClient(WebClient.Builder webClientBuilder) { | |
| .baseUrl(paymentServiceUrl) | ||
| .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) | ||
| .defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE) | ||
| .filter(jwtTokenPropagationFilter()) | ||
| .build(); | ||
| } | ||
|
|
||
|
|
@@ -55,6 +84,7 @@ public WebClient appointmentServiceWebClient(WebClient.Builder webClientBuilder) | |
| .baseUrl(appointmentServiceUrl) | ||
| .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) | ||
| .defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE) | ||
| .filter(jwtTokenPropagationFilter()) | ||
| .build(); | ||
| } | ||
|
|
||
|
|
@@ -64,6 +94,7 @@ public WebClient projectServiceWebClient(WebClient.Builder webClientBuilder) { | |
| .baseUrl(projectServiceUrl) | ||
| .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) | ||
| .defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE) | ||
| .filter(jwtTokenPropagationFilter()) | ||
| .build(); | ||
| } | ||
|
|
||
|
|
@@ -73,6 +104,7 @@ public WebClient timeLoggingServiceWebClient(WebClient.Builder webClientBuilder) | |
| .baseUrl(timeLoggingServiceUrl) | ||
| .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) | ||
| .defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE) | ||
| .filter(jwtTokenPropagationFilter()) | ||
| .build(); | ||
| } | ||
|
|
||
|
|
@@ -82,6 +114,7 @@ public WebClient vehicleServiceWebClient(WebClient.Builder webClientBuilder) { | |
| .baseUrl(vehicleServiceUrl) | ||
| .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) | ||
| .defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE) | ||
| .filter(jwtTokenPropagationFilter()) | ||
| .build(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,43 @@ | ||||||||||||||
| package com.techtorque.admin_service.controller; | ||||||||||||||
|
|
||||||||||||||
| import com.techtorque.admin_service.dto.response.ServiceTypeResponse; | ||||||||||||||
| import com.techtorque.admin_service.service.AdminServiceConfigService; | ||||||||||||||
| import io.swagger.v3.oas.annotations.Operation; | ||||||||||||||
| import io.swagger.v3.oas.annotations.tags.Tag; | ||||||||||||||
| import lombok.RequiredArgsConstructor; | ||||||||||||||
| import lombok.extern.slf4j.Slf4j; | ||||||||||||||
| import org.springframework.http.ResponseEntity; | ||||||||||||||
| import org.springframework.web.bind.annotation.*; | ||||||||||||||
|
|
||||||||||||||
| import java.util.List; | ||||||||||||||
|
|
||||||||||||||
| /** | ||||||||||||||
| * Public controller for service types - accessible by all authenticated users and other microservices | ||||||||||||||
| * This allows customers and other services to read active service types without admin privileges | ||||||||||||||
| */ | ||||||||||||||
| @RestController | ||||||||||||||
| @RequestMapping("/public/service-types") | ||||||||||||||
| @RequiredArgsConstructor | ||||||||||||||
| @Slf4j | ||||||||||||||
| @Tag(name = "Public Service Types", description = "Public API for accessing service types") | ||||||||||||||
| public class PublicServiceTypeController { | ||||||||||||||
|
|
||||||||||||||
| private final AdminServiceConfigService adminServiceConfigService; | ||||||||||||||
|
|
||||||||||||||
| @GetMapping | ||||||||||||||
| @Operation(summary = "Get all active service types", description = "Retrieve all active service types available to customers") | ||||||||||||||
| public ResponseEntity<List<ServiceTypeResponse>> getActiveServiceTypes() { | ||||||||||||||
| log.info("Public request: Fetching all active service types"); | ||||||||||||||
| List<ServiceTypeResponse> serviceTypes = adminServiceConfigService.getAllServiceTypes(true); // activeOnly = true | ||||||||||||||
| log.info("Retrieved {} active service types for public access", serviceTypes.size()); | ||||||||||||||
| return ResponseEntity.ok(serviceTypes); | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| @GetMapping("/{id}") | ||||||||||||||
| @Operation(summary = "Get service type by ID", description = "Retrieve a specific service type by its ID") | ||||||||||||||
| public ResponseEntity<ServiceTypeResponse> getServiceTypeById(@PathVariable String id) { | ||||||||||||||
| log.info("Public request: Fetching service type with ID: {}", id); | ||||||||||||||
| ServiceTypeResponse serviceType = adminServiceConfigService.getServiceTypeById(id); | ||||||||||||||
|
||||||||||||||
| ServiceTypeResponse serviceType = adminServiceConfigService.getServiceTypeById(id); | |
| ServiceTypeResponse serviceType = adminServiceConfigService.getServiceTypeById(id); | |
| if (serviceType == null || (serviceType.getActive() != null && !serviceType.getActive())) { | |
| log.warn("Service type with ID {} not found or not active for public access", id); | |
| return ResponseEntity.notFound().build(); | |
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,11 +24,24 @@ public class UpdateUserRequest { | |
| @Pattern(regexp = "ADMIN|EMPLOYEE|CUSTOMER", message = "Role must be ADMIN, EMPLOYEE, or CUSTOMER") | ||
| private String role; | ||
|
|
||
| private List<String> roles; | ||
|
||
|
|
||
| private Boolean active; | ||
|
|
||
| // Alternative field name for activation status (frontend compatibility) | ||
| private Boolean enabled; | ||
|
|
||
| @Size(max = 20, message = "Maximum 20 permissions allowed") | ||
| private List<String> permissions; | ||
|
|
||
| @Size(max = 100) | ||
| private String department; | ||
|
|
||
| /** | ||
| * Get the activation status, checking both 'active' and 'enabled' fields | ||
| */ | ||
| public Boolean getActivationStatus() { | ||
| // Prioritize 'enabled' if set, otherwise use 'active' | ||
| return enabled != null ? enabled : active; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,8 +20,8 @@ public class ServiceTypeResponse { | |
| private String name; | ||
| private String description; | ||
| private String category; | ||
| private BigDecimal price; | ||
| private Integer durationMinutes; | ||
| private BigDecimal basePriceLKR; // Changed from 'price' to match frontend | ||
| private Integer estimatedDurationMinutes; // Changed from 'durationMinutes' to match frontend | ||
|
Comment on lines
+23
to
+24
|
||
| private Boolean active; | ||
| private Boolean requiresApproval; | ||
| private Integer dailyCapacity; | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -121,9 +121,8 @@ public void deleteServiceType(String id, String deletedBy) { | |||||||||||||||||||||
| ServiceType serviceType = serviceTypeRepository.findById(id) | ||||||||||||||||||||||
| .orElseThrow(() -> new IllegalArgumentException("Service type not found: " + id)); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Soft delete | ||||||||||||||||||||||
| serviceType.setActive(false); | ||||||||||||||||||||||
| serviceTypeRepository.save(serviceType); | ||||||||||||||||||||||
| // Hard delete - actually remove from database | ||||||||||||||||||||||
| serviceTypeRepository.delete(serviceType); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| log.info("Service type deleted successfully: {}", id); | ||||||||||||||||||||||
|
Comment on lines
+124
to
127
|
||||||||||||||||||||||
| // Hard delete - actually remove from database | |
| serviceTypeRepository.delete(serviceType); | |
| log.info("Service type deleted successfully: {}", id); | |
| // Soft delete - mark as inactive instead of removing from database | |
| serviceType.setActive(false); | |
| serviceType.setUpdatedAt(java.time.LocalDateTime.now()); | |
| serviceTypeRepository.save(serviceType); | |
| log.info("Service type soft-deleted (marked inactive) successfully: {}", id); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The
spring-boot-starter-oauth2-resource-serverdependency is added but not used in the configuration. The JWT authentication is implemented manually viaJwtAuthenticationFilterusing thejjwtlibrary. Consider either using the OAuth2 resource server configuration properly or removing this unused dependency to avoid confusion and reduce the application size.