Merge remote-tracking branch 'origin/main'
This commit is contained in:
@@ -167,7 +167,7 @@
|
|||||||
],
|
],
|
||||||
"body": {
|
"body": {
|
||||||
"mode": "raw",
|
"mode": "raw",
|
||||||
"raw": "{\n \"username\": \"newcustomer{{$timestamp}}\",\n \"password\": \"password123\",\n \"email\": \"new{{$timestamp}}@example.com\",\n \"fullName\": \"New Customer\"\n}"
|
"raw": "{\n \"username\": \"newcustomer{{$timestamp}}\",\n \"password\": \"password123\",\n \"email\": \"new{{$timestamp}}@example.com\",\n \"phone\": \"+1-555-01{{$randomInt}}\",\n \"fullName\": \"New Customer\"\n}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"event": [
|
"event": [
|
||||||
@@ -2987,6 +2987,111 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "List Staff Users",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"url": "{{baseUrl}}/api/v1/users?role=STAFF",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Content-Type",
|
||||||
|
"value": "application/json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Authorization",
|
||||||
|
"value": "Bearer {{adminToken}}",
|
||||||
|
"type": "text"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"event": [
|
||||||
|
{
|
||||||
|
"listen": "test",
|
||||||
|
"script": {
|
||||||
|
"type": "text/javascript",
|
||||||
|
"exec": [
|
||||||
|
"pm.test('Status code is 200', function () {",
|
||||||
|
" pm.response.to.have.status(200);",
|
||||||
|
"});",
|
||||||
|
"var jsonData = pm.response.json();",
|
||||||
|
"pm.test('All returned users are STAFF', function () {",
|
||||||
|
" pm.expect(jsonData.content.every(function (user) { return user.role === 'STAFF'; })).to.be.true;",
|
||||||
|
"});"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "List Admin Users",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"url": "{{baseUrl}}/api/v1/users?role=ADMIN",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Content-Type",
|
||||||
|
"value": "application/json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Authorization",
|
||||||
|
"value": "Bearer {{adminToken}}",
|
||||||
|
"type": "text"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"event": [
|
||||||
|
{
|
||||||
|
"listen": "test",
|
||||||
|
"script": {
|
||||||
|
"type": "text/javascript",
|
||||||
|
"exec": [
|
||||||
|
"pm.test('Status code is 200', function () {",
|
||||||
|
" pm.response.to.have.status(200);",
|
||||||
|
"});",
|
||||||
|
"var jsonData = pm.response.json();",
|
||||||
|
"pm.test('All returned users are ADMIN', function () {",
|
||||||
|
" pm.expect(jsonData.content.every(function (user) { return user.role === 'ADMIN'; })).to.be.true;",
|
||||||
|
"});"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "List Customer Users",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"url": "{{baseUrl}}/api/v1/users?role=CUSTOMER",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Content-Type",
|
||||||
|
"value": "application/json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Authorization",
|
||||||
|
"value": "Bearer {{adminToken}}",
|
||||||
|
"type": "text"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"event": [
|
||||||
|
{
|
||||||
|
"listen": "test",
|
||||||
|
"script": {
|
||||||
|
"type": "text/javascript",
|
||||||
|
"exec": [
|
||||||
|
"pm.test('Status code is 200', function () {",
|
||||||
|
" pm.response.to.have.status(200);",
|
||||||
|
"});",
|
||||||
|
"var jsonData = pm.response.json();",
|
||||||
|
"pm.test('All returned users are CUSTOMER', function () {",
|
||||||
|
" pm.expect(jsonData.content.every(function (user) { return user.role === 'CUSTOMER'; })).to.be.true;",
|
||||||
|
"});"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Get User",
|
"name": "Get User",
|
||||||
"request": {
|
"request": {
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ public class DataInitializer implements CommandLineRunner {
|
|||||||
admin.setPassword(passwordEncoder.encode("admin123"));
|
admin.setPassword(passwordEncoder.encode("admin123"));
|
||||||
admin.setEmail("admin@petshop.com");
|
admin.setEmail("admin@petshop.com");
|
||||||
admin.setFullName("Admin User");
|
admin.setFullName("Admin User");
|
||||||
|
admin.setPhone("000-000-1000");
|
||||||
admin.setRole(User.Role.ADMIN);
|
admin.setRole(User.Role.ADMIN);
|
||||||
admin.setActive(true);
|
admin.setActive(true);
|
||||||
admin = userRepository.save(admin);
|
admin = userRepository.save(admin);
|
||||||
@@ -51,6 +52,10 @@ public class DataInitializer implements CommandLineRunner {
|
|||||||
admin.setEmail("admin@petshop.com");
|
admin.setEmail("admin@petshop.com");
|
||||||
updated = true;
|
updated = true;
|
||||||
}
|
}
|
||||||
|
if (admin.getPhone() == null || admin.getPhone().isEmpty()) {
|
||||||
|
admin.setPhone("000-000-1000");
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
if (admin.getActive() == null) {
|
if (admin.getActive() == null) {
|
||||||
admin.setActive(true);
|
admin.setActive(true);
|
||||||
updated = true;
|
updated = true;
|
||||||
@@ -75,6 +80,7 @@ public class DataInitializer implements CommandLineRunner {
|
|||||||
staff.setPassword(passwordEncoder.encode("staff123"));
|
staff.setPassword(passwordEncoder.encode("staff123"));
|
||||||
staff.setEmail("staff@petshop.com");
|
staff.setEmail("staff@petshop.com");
|
||||||
staff.setFullName("Staff User");
|
staff.setFullName("Staff User");
|
||||||
|
staff.setPhone("000-000-1001");
|
||||||
staff.setRole(User.Role.STAFF);
|
staff.setRole(User.Role.STAFF);
|
||||||
staff.setActive(true);
|
staff.setActive(true);
|
||||||
staff = userRepository.save(staff);
|
staff = userRepository.save(staff);
|
||||||
@@ -91,6 +97,10 @@ public class DataInitializer implements CommandLineRunner {
|
|||||||
staff.setEmail("staff@petshop.com");
|
staff.setEmail("staff@petshop.com");
|
||||||
updated = true;
|
updated = true;
|
||||||
}
|
}
|
||||||
|
if (staff.getPhone() == null || staff.getPhone().isEmpty()) {
|
||||||
|
staff.setPhone("000-000-1001");
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
if (staff.getActive() == null) {
|
if (staff.getActive() == null) {
|
||||||
staff.setActive(true);
|
staff.setActive(true);
|
||||||
updated = true;
|
updated = true;
|
||||||
@@ -115,6 +125,7 @@ public class DataInitializer implements CommandLineRunner {
|
|||||||
customer.setPassword(passwordEncoder.encode("customer123"));
|
customer.setPassword(passwordEncoder.encode("customer123"));
|
||||||
customer.setEmail("customer@petshop.com");
|
customer.setEmail("customer@petshop.com");
|
||||||
customer.setFullName("Test Customer");
|
customer.setFullName("Test Customer");
|
||||||
|
customer.setPhone("000-000-1002");
|
||||||
customer.setRole(User.Role.CUSTOMER);
|
customer.setRole(User.Role.CUSTOMER);
|
||||||
customer.setActive(true);
|
customer.setActive(true);
|
||||||
customer = userRepository.save(customer);
|
customer = userRepository.save(customer);
|
||||||
@@ -131,6 +142,10 @@ public class DataInitializer implements CommandLineRunner {
|
|||||||
customer.setEmail("customer@petshop.com");
|
customer.setEmail("customer@petshop.com");
|
||||||
updated = true;
|
updated = true;
|
||||||
}
|
}
|
||||||
|
if (customer.getPhone() == null || customer.getPhone().isEmpty()) {
|
||||||
|
customer.setPhone("000-000-1002");
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
if (customer.getActive() == null) {
|
if (customer.getActive() == null) {
|
||||||
customer.setActive(true);
|
customer.setActive(true);
|
||||||
updated = true;
|
updated = true;
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.petshop.backend.config;
|
||||||
|
|
||||||
|
import org.springframework.boot.tomcat.servlet.TomcatServletWebServerFactory;
|
||||||
|
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class TomcatPathToleranceConfig implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void customize(TomcatServletWebServerFactory factory) {
|
||||||
|
factory.addConnectorCustomizers(connector -> {
|
||||||
|
connector.setAllowBackslash(true);
|
||||||
|
connector.setEncodedReverseSolidusHandling("decode");
|
||||||
|
connector.setProperty("relaxedPathChars", "\\");
|
||||||
|
connector.setProperty("relaxedQueryChars", "\\");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package com.petshop.backend.config;
|
||||||
|
|
||||||
|
import jakarta.servlet.FilterChain;
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletRequestWrapper;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import org.springframework.core.Ordered;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||||
|
public class TrailingSlashNormalizationFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean shouldNotFilter(HttpServletRequest request) {
|
||||||
|
String requestUri = request.getRequestURI();
|
||||||
|
if (requestUri == null || requestUri.isBlank()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return requestUri.equals(normalizePath(requestUri));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
|
||||||
|
String normalizedUri = normalizePath(request.getRequestURI());
|
||||||
|
String normalizedServletPath = normalizePath(request.getServletPath());
|
||||||
|
String normalizedPathInfo = normalizePath(request.getPathInfo());
|
||||||
|
|
||||||
|
HttpServletRequestWrapper wrapper = new HttpServletRequestWrapper(request) {
|
||||||
|
@Override
|
||||||
|
public String getRequestURI() {
|
||||||
|
return normalizedUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StringBuffer getRequestURL() {
|
||||||
|
String original = super.getRequestURL().toString();
|
||||||
|
int schemeSeparator = original.indexOf("://");
|
||||||
|
int pathStart = schemeSeparator >= 0 ? original.indexOf('/', schemeSeparator + 3) : original.indexOf('/');
|
||||||
|
if (pathStart < 0) {
|
||||||
|
return new StringBuffer(original);
|
||||||
|
}
|
||||||
|
String prefix = original.substring(0, pathStart);
|
||||||
|
return new StringBuffer(prefix + normalizedUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getServletPath() {
|
||||||
|
return normalizedServletPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getPathInfo() {
|
||||||
|
return normalizedPathInfo;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
filterChain.doFilter(wrapper, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizePath(String value) {
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String normalized = value.replace('\\', '/');
|
||||||
|
while (normalized.contains("//")) {
|
||||||
|
normalized = normalized.replace("//", "/");
|
||||||
|
}
|
||||||
|
if (shouldLowercase(normalized)) {
|
||||||
|
normalized = normalized.toLowerCase(java.util.Locale.ROOT);
|
||||||
|
}
|
||||||
|
int end = normalized.length();
|
||||||
|
while (end > 1 && normalized.charAt(end - 1) == '/') {
|
||||||
|
end--;
|
||||||
|
}
|
||||||
|
return normalized.substring(0, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean shouldLowercase(String path) {
|
||||||
|
String lower = path.toLowerCase(java.util.Locale.ROOT);
|
||||||
|
return lower.startsWith("/api/")
|
||||||
|
|| lower.equals("/api")
|
||||||
|
|| lower.startsWith("/ws/")
|
||||||
|
|| lower.equals("/ws");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import com.petshop.backend.repository.UserRepository;
|
|||||||
import com.petshop.backend.security.AppPrincipal;
|
import com.petshop.backend.security.AppPrincipal;
|
||||||
import com.petshop.backend.security.JwtUtil;
|
import com.petshop.backend.security.JwtUtil;
|
||||||
import com.petshop.backend.service.ChatService;
|
import com.petshop.backend.service.ChatService;
|
||||||
|
import io.jsonwebtoken.JwtException;
|
||||||
import org.springframework.messaging.Message;
|
import org.springframework.messaging.Message;
|
||||||
import org.springframework.messaging.MessageChannel;
|
import org.springframework.messaging.MessageChannel;
|
||||||
import org.springframework.messaging.simp.stomp.StompCommand;
|
import org.springframework.messaging.simp.stomp.StompCommand;
|
||||||
@@ -14,7 +15,10 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio
|
|||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.security.Principal;
|
import java.security.Principal;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class WebSocketAuthChannelInterceptor implements ChannelInterceptor {
|
public class WebSocketAuthChannelInterceptor implements ChannelInterceptor {
|
||||||
@@ -45,7 +49,7 @@ public class WebSocketAuthChannelInterceptor implements ChannelInterceptor {
|
|||||||
throw new IllegalArgumentException("Missing websocket token");
|
throw new IllegalArgumentException("Missing websocket token");
|
||||||
}
|
}
|
||||||
|
|
||||||
Long userId = jwtUtil.extractUserId(token);
|
Long userId = extractUserId(token);
|
||||||
User user = userId == null ? null : userRepository.findById(userId).orElse(null);
|
User user = userId == null ? null : userRepository.findById(userId).orElse(null);
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
throw new IllegalArgumentException("User not found");
|
throw new IllegalArgumentException("User not found");
|
||||||
@@ -73,15 +77,15 @@ public class WebSocketAuthChannelInterceptor implements ChannelInterceptor {
|
|||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (StompCommand.DISCONNECT.equals(command) || StompCommand.UNSUBSCRIBE.equals(command)) {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
User user = resolveUser(accessor.getUser(), accessor);
|
User user = resolveUser(accessor.getUser(), accessor);
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
throw new IllegalArgumentException("Unauthenticated websocket session");
|
throw new IllegalArgumentException("Unauthenticated websocket session");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StompCommand.DISCONNECT.equals(command) || StompCommand.UNSUBSCRIBE.equals(command)) {
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (StompCommand.SUBSCRIBE.equals(command)) {
|
if (StompCommand.SUBSCRIBE.equals(command)) {
|
||||||
authorizeSubscription(accessor.getDestination(), user);
|
authorizeSubscription(accessor.getDestination(), user);
|
||||||
} else if (StompCommand.SEND.equals(command)) {
|
} else if (StompCommand.SEND.equals(command)) {
|
||||||
@@ -118,15 +122,22 @@ public class WebSocketAuthChannelInterceptor implements ChannelInterceptor {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Long userId = jwtUtil.extractUserId(token);
|
Long userId = extractUserId(token);
|
||||||
User user = userId == null ? null : userRepository.findById(userId).orElse(null);
|
User user = userId == null ? null : userRepository.findById(userId).orElse(null);
|
||||||
if (user == null || user.getActive() == null || !user.getActive() || !jwtUtil.validateToken(token, user)) {
|
if (user == null) {
|
||||||
throw new IllegalArgumentException("User not found");
|
throw new IllegalArgumentException("User not found");
|
||||||
}
|
}
|
||||||
|
if (user.getActive() == null || !user.getActive()) {
|
||||||
|
throw new IllegalArgumentException("User account is inactive");
|
||||||
|
}
|
||||||
|
if (!jwtUtil.validateToken(token, user)) {
|
||||||
|
throw new IllegalArgumentException("Invalid websocket token");
|
||||||
|
}
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void authorizeSubscription(String destination, User user) {
|
private void authorizeSubscription(String destination, User user) {
|
||||||
|
destination = normalizeDestination(destination);
|
||||||
if (destination == null || destination.startsWith("/user/queue/")) {
|
if (destination == null || destination.startsWith("/user/queue/")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -147,6 +158,7 @@ public class WebSocketAuthChannelInterceptor implements ChannelInterceptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void authorizeSend(String destination, User user) {
|
private void authorizeSend(String destination, User user) {
|
||||||
|
destination = normalizeDestination(destination);
|
||||||
Long conversationId = extractConversationId(destination, "/app/chat/conversations/");
|
Long conversationId = extractConversationId(destination, "/app/chat/conversations/");
|
||||||
if (conversationId != null && destination.endsWith("/messages") && chatService.hasConversationAccess(conversationId, user.getId(), user.getRole())) {
|
if (conversationId != null && destination.endsWith("/messages") && chatService.hasConversationAccess(conversationId, user.getId(), user.getRole())) {
|
||||||
return;
|
return;
|
||||||
@@ -175,13 +187,51 @@ public class WebSocketAuthChannelInterceptor implements ChannelInterceptor {
|
|||||||
|
|
||||||
private String firstHeader(StompHeaderAccessor accessor, String name) {
|
private String firstHeader(StompHeaderAccessor accessor, String name) {
|
||||||
List<String> values = accessor.getNativeHeader(name);
|
List<String> values = accessor.getNativeHeader(name);
|
||||||
return values == null || values.isEmpty() ? null : values.get(0);
|
if (values != null && !values.isEmpty()) {
|
||||||
|
return values.get(0);
|
||||||
|
}
|
||||||
|
for (String headerName : accessor.toNativeHeaderMap().keySet()) {
|
||||||
|
if (headerName.equalsIgnoreCase(name)) {
|
||||||
|
List<String> alternateValues = accessor.getNativeHeader(headerName);
|
||||||
|
return alternateValues == null || alternateValues.isEmpty() ? null : alternateValues.get(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String extractToken(String rawValue) {
|
private String extractToken(String rawValue) {
|
||||||
if (rawValue == null || rawValue.isBlank()) {
|
if (rawValue == null || rawValue.isBlank()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return rawValue.startsWith("Bearer ") ? rawValue.substring(7) : rawValue;
|
String normalized = rawValue.trim();
|
||||||
|
return normalized.regionMatches(true, 0, "Bearer ", 0, 7) ? normalized.substring(7) : normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeDestination(String destination) {
|
||||||
|
if (destination == null || destination.isBlank()) {
|
||||||
|
return destination;
|
||||||
|
}
|
||||||
|
String normalized = destination.replace('\\', '/');
|
||||||
|
while (normalized.contains("//")) {
|
||||||
|
normalized = normalized.replace("//", "/");
|
||||||
|
}
|
||||||
|
return normalized.toLowerCase(Locale.ROOT);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Long extractUserId(String token) {
|
||||||
|
try {
|
||||||
|
return jwtUtil.extractUserId(token);
|
||||||
|
} catch (JwtException | IllegalArgumentException ex) {
|
||||||
|
throw new IllegalArgumentException("Invalid websocket token: " + ex.getMessage(), ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> buildErrorPayload(Exception ex, String destination, Principal principal) {
|
||||||
|
Map<String, Object> response = new LinkedHashMap<>();
|
||||||
|
response.put("message", ex.getMessage() == null || ex.getMessage().isBlank() ? "WebSocket request failed" : ex.getMessage());
|
||||||
|
response.put("details", ex.getClass().getSimpleName());
|
||||||
|
response.put("destination", normalizeDestination(destination));
|
||||||
|
response.put("authenticated", principal != null);
|
||||||
|
return response;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,8 +33,13 @@ public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
|
|||||||
public void registerStompEndpoints(StompEndpointRegistry registry) {
|
public void registerStompEndpoints(StompEndpointRegistry registry) {
|
||||||
registry.addEndpoint("/ws/chat")
|
registry.addEndpoint("/ws/chat")
|
||||||
.setAllowedOriginPatterns("*");
|
.setAllowedOriginPatterns("*");
|
||||||
|
registry.addEndpoint("/ws/chat/")
|
||||||
|
.setAllowedOriginPatterns("*");
|
||||||
registry.addEndpoint("/ws/chat-sockjs")
|
registry.addEndpoint("/ws/chat-sockjs")
|
||||||
.setAllowedOriginPatterns("*")
|
.setAllowedOriginPatterns("*")
|
||||||
.withSockJS();
|
.withSockJS();
|
||||||
|
registry.addEndpoint("/ws/chat-sockjs/")
|
||||||
|
.setAllowedOriginPatterns("*")
|
||||||
|
.withSockJS();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,11 +74,19 @@ public class AuthController {
|
|||||||
return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
|
return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String phone = trimToNull(request.getPhone());
|
||||||
|
if (phone != null && userRepository.findByPhone(phone).isPresent()) {
|
||||||
|
Map<String, String> error = new HashMap<>();
|
||||||
|
error.put("message", "Phone already exists");
|
||||||
|
return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
|
||||||
|
}
|
||||||
|
|
||||||
User user = new User();
|
User user = new User();
|
||||||
user.setUsername(request.getUsername());
|
user.setUsername(request.getUsername());
|
||||||
user.setPassword(passwordEncoder.encode(request.getPassword()));
|
user.setPassword(passwordEncoder.encode(request.getPassword()));
|
||||||
user.setEmail(request.getEmail());
|
user.setEmail(request.getEmail());
|
||||||
user.setFullName(request.getFullName());
|
user.setFullName(request.getFullName());
|
||||||
|
user.setPhone(phone);
|
||||||
user.setRole(User.Role.CUSTOMER);
|
user.setRole(User.Role.CUSTOMER);
|
||||||
user.setActive(true);
|
user.setActive(true);
|
||||||
|
|
||||||
@@ -93,6 +101,7 @@ public class AuthController {
|
|||||||
savedUser.getId(),
|
savedUser.getId(),
|
||||||
savedUser.getUsername(),
|
savedUser.getUsername(),
|
||||||
savedUser.getEmail(),
|
savedUser.getEmail(),
|
||||||
|
savedUser.getPhone(),
|
||||||
savedUser.getRole().name(),
|
savedUser.getRole().name(),
|
||||||
token
|
token
|
||||||
));
|
));
|
||||||
@@ -145,6 +154,7 @@ public class AuthController {
|
|||||||
user.getUsername(),
|
user.getUsername(),
|
||||||
user.getEmail(),
|
user.getEmail(),
|
||||||
user.getFullName(),
|
user.getFullName(),
|
||||||
|
user.getPhone(),
|
||||||
user.getAvatarUrl(),
|
user.getAvatarUrl(),
|
||||||
user.getRole().name(),
|
user.getRole().name(),
|
||||||
employeeStore != null ? employeeStore.getStore().getStoreId() : null,
|
employeeStore != null ? employeeStore.getStore().getStoreId() : null,
|
||||||
@@ -180,6 +190,20 @@ public class AuthController {
|
|||||||
user.setFullName(request.getFullName());
|
user.setFullName(request.getFullName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (request.getPhone() != null) {
|
||||||
|
String phone = trimToNull(request.getPhone());
|
||||||
|
if (!java.util.Objects.equals(phone, user.getPhone())) {
|
||||||
|
if (phone != null && userRepository.findByPhone(phone)
|
||||||
|
.filter(existing -> !existing.getId().equals(user.getId()))
|
||||||
|
.isPresent()) {
|
||||||
|
Map<String, String> error = new HashMap<>();
|
||||||
|
error.put("message", "Phone already exists");
|
||||||
|
return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
|
||||||
|
}
|
||||||
|
user.setPhone(phone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (request.getPassword() != null && !request.getPassword().isEmpty()) {
|
if (request.getPassword() != null && !request.getPassword().isEmpty()) {
|
||||||
user.setPassword(passwordEncoder.encode(request.getPassword()));
|
user.setPassword(passwordEncoder.encode(request.getPassword()));
|
||||||
invalidateToken = true;
|
invalidateToken = true;
|
||||||
@@ -190,6 +214,7 @@ public class AuthController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
User updatedUser = userRepository.save(user);
|
User updatedUser = userRepository.save(user);
|
||||||
|
userBusinessLinkageService.syncLinkedRecords(updatedUser);
|
||||||
|
|
||||||
EmployeeStore employeeStore = resolveEmployeeStore(updatedUser);
|
EmployeeStore employeeStore = resolveEmployeeStore(updatedUser);
|
||||||
|
|
||||||
@@ -198,6 +223,7 @@ public class AuthController {
|
|||||||
updatedUser.getUsername(),
|
updatedUser.getUsername(),
|
||||||
updatedUser.getEmail(),
|
updatedUser.getEmail(),
|
||||||
updatedUser.getFullName(),
|
updatedUser.getFullName(),
|
||||||
|
updatedUser.getPhone(),
|
||||||
updatedUser.getAvatarUrl(),
|
updatedUser.getAvatarUrl(),
|
||||||
updatedUser.getRole().name(),
|
updatedUser.getRole().name(),
|
||||||
employeeStore != null ? employeeStore.getStore().getStoreId() : null,
|
employeeStore != null ? employeeStore.getStore().getStoreId() : null,
|
||||||
@@ -215,6 +241,14 @@ public class AuthController {
|
|||||||
.orElse(null);
|
.orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String trimToNull(String value) {
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String trimmed = value.trim();
|
||||||
|
return trimmed.isEmpty() ? null : trimmed;
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping("/me/avatar")
|
@PostMapping("/me/avatar")
|
||||||
public ResponseEntity<?> uploadAvatar(@RequestParam("avatar") MultipartFile file) {
|
public ResponseEntity<?> uploadAvatar(@RequestParam("avatar") MultipartFile file) {
|
||||||
User user = getAuthenticatedUser();
|
User user = getAuthenticatedUser();
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.petshop.backend.controller;
|
package com.petshop.backend.controller;
|
||||||
|
|
||||||
|
import com.petshop.backend.config.WebSocketAuthChannelInterceptor;
|
||||||
import com.petshop.backend.dto.chat.MessageRequest;
|
import com.petshop.backend.dto.chat.MessageRequest;
|
||||||
import com.petshop.backend.dto.chat.MessageResponse;
|
import com.petshop.backend.dto.chat.MessageResponse;
|
||||||
import com.petshop.backend.entity.User;
|
import com.petshop.backend.entity.User;
|
||||||
@@ -10,6 +11,7 @@ import com.petshop.backend.service.ChatRealtimeService;
|
|||||||
import com.petshop.backend.service.ChatService;
|
import com.petshop.backend.service.ChatService;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import org.springframework.messaging.handler.annotation.DestinationVariable;
|
import org.springframework.messaging.handler.annotation.DestinationVariable;
|
||||||
|
import org.springframework.messaging.handler.annotation.MessageExceptionHandler;
|
||||||
import org.springframework.messaging.handler.annotation.MessageMapping;
|
import org.springframework.messaging.handler.annotation.MessageMapping;
|
||||||
import org.springframework.messaging.handler.annotation.Payload;
|
import org.springframework.messaging.handler.annotation.Payload;
|
||||||
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
|
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
|
||||||
@@ -17,6 +19,9 @@ import org.springframework.messaging.simp.annotation.SendToUser;
|
|||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
|
|
||||||
import java.security.Principal;
|
import java.security.Principal;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
public class ChatWebSocketController {
|
public class ChatWebSocketController {
|
||||||
@@ -25,12 +30,20 @@ public class ChatWebSocketController {
|
|||||||
private final ChatRealtimeService chatRealtimeService;
|
private final ChatRealtimeService chatRealtimeService;
|
||||||
private final UserRepository userRepository;
|
private final UserRepository userRepository;
|
||||||
private final JwtUtil jwtUtil;
|
private final JwtUtil jwtUtil;
|
||||||
|
private final WebSocketAuthChannelInterceptor webSocketAuthChannelInterceptor;
|
||||||
|
|
||||||
public ChatWebSocketController(ChatService chatService, ChatRealtimeService chatRealtimeService, UserRepository userRepository, JwtUtil jwtUtil) {
|
public ChatWebSocketController(
|
||||||
|
ChatService chatService,
|
||||||
|
ChatRealtimeService chatRealtimeService,
|
||||||
|
UserRepository userRepository,
|
||||||
|
JwtUtil jwtUtil,
|
||||||
|
WebSocketAuthChannelInterceptor webSocketAuthChannelInterceptor
|
||||||
|
) {
|
||||||
this.chatService = chatService;
|
this.chatService = chatService;
|
||||||
this.chatRealtimeService = chatRealtimeService;
|
this.chatRealtimeService = chatRealtimeService;
|
||||||
this.userRepository = userRepository;
|
this.userRepository = userRepository;
|
||||||
this.jwtUtil = jwtUtil;
|
this.jwtUtil = jwtUtil;
|
||||||
|
this.webSocketAuthChannelInterceptor = webSocketAuthChannelInterceptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@MessageMapping("/chat/conversations/{id}/messages")
|
@MessageMapping("/chat/conversations/{id}/messages")
|
||||||
@@ -42,6 +55,12 @@ public class ChatWebSocketController {
|
|||||||
chatRealtimeService.publishConversationUpdate(id);
|
chatRealtimeService.publishConversationUpdate(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MessageExceptionHandler({IllegalArgumentException.class, RuntimeException.class})
|
||||||
|
@SendToUser("/queue/chat/errors")
|
||||||
|
public Map<String, Object> handleMessageException(Exception ex, SimpMessageHeaderAccessor headerAccessor) {
|
||||||
|
return webSocketAuthChannelInterceptor.buildErrorPayload(ex, headerAccessor.getDestination(), headerAccessor.getUser());
|
||||||
|
}
|
||||||
|
|
||||||
private User resolveUser(SimpMessageHeaderAccessor headerAccessor) {
|
private User resolveUser(SimpMessageHeaderAccessor headerAccessor) {
|
||||||
Principal principal = headerAccessor.getUser();
|
Principal principal = headerAccessor.getUser();
|
||||||
if (principal instanceof org.springframework.security.authentication.UsernamePasswordAuthenticationToken authenticationToken
|
if (principal instanceof org.springframework.security.authentication.UsernamePasswordAuthenticationToken authenticationToken
|
||||||
@@ -55,20 +74,50 @@ public class ChatWebSocketController {
|
|||||||
.orElseThrow(() -> new IllegalArgumentException("User not found"));
|
.orElseThrow(() -> new IllegalArgumentException("User not found"));
|
||||||
}
|
}
|
||||||
|
|
||||||
String tokenHeader = headerAccessor.getFirstNativeHeader("Authorization");
|
String tokenHeader = firstHeader(headerAccessor, "Authorization");
|
||||||
if (tokenHeader == null || tokenHeader.isBlank()) {
|
if (tokenHeader == null || tokenHeader.isBlank()) {
|
||||||
tokenHeader = headerAccessor.getFirstNativeHeader("token");
|
tokenHeader = firstHeader(headerAccessor, "token");
|
||||||
}
|
}
|
||||||
if (tokenHeader == null || tokenHeader.isBlank()) {
|
if (tokenHeader == null || tokenHeader.isBlank()) {
|
||||||
throw new IllegalArgumentException("User not authenticated");
|
throw new IllegalArgumentException("User not authenticated");
|
||||||
}
|
}
|
||||||
|
|
||||||
String token = tokenHeader.startsWith("Bearer ") ? tokenHeader.substring(7) : tokenHeader;
|
String token = extractToken(tokenHeader);
|
||||||
Long userId = jwtUtil.extractUserId(token);
|
Long userId;
|
||||||
|
try {
|
||||||
|
userId = jwtUtil.extractUserId(token);
|
||||||
|
} catch (RuntimeException ex) {
|
||||||
|
throw new IllegalArgumentException("Invalid websocket token", ex);
|
||||||
|
}
|
||||||
User user = userId == null ? null : userRepository.findById(userId).orElse(null);
|
User user = userId == null ? null : userRepository.findById(userId).orElse(null);
|
||||||
if (user == null || user.getActive() == null || !user.getActive() || !jwtUtil.validateToken(token, user)) {
|
if (user == null) {
|
||||||
throw new IllegalArgumentException("User not found");
|
throw new IllegalArgumentException("User not found");
|
||||||
}
|
}
|
||||||
|
if (user.getActive() == null || !user.getActive()) {
|
||||||
|
throw new IllegalArgumentException("User account is inactive");
|
||||||
|
}
|
||||||
|
if (!jwtUtil.validateToken(token, user)) {
|
||||||
|
throw new IllegalArgumentException("Invalid websocket token");
|
||||||
|
}
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String firstHeader(SimpMessageHeaderAccessor headerAccessor, String name) {
|
||||||
|
List<String> values = headerAccessor.getNativeHeader(name);
|
||||||
|
if (values != null && !values.isEmpty()) {
|
||||||
|
return values.get(0);
|
||||||
|
}
|
||||||
|
Map<String, List<String>> headers = headerAccessor.toNativeHeaderMap();
|
||||||
|
for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
|
||||||
|
if (entry.getKey().equalsIgnoreCase(name)) {
|
||||||
|
return entry.getValue() == null || entry.getValue().isEmpty() ? null : entry.getValue().get(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String extractToken(String rawValue) {
|
||||||
|
String normalized = rawValue.trim();
|
||||||
|
return normalized.regionMatches(true, 0, "Bearer ", 0, 7) ? normalized.substring(7) : normalized;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package com.petshop.backend.controller;
|
||||||
|
|
||||||
|
import com.petshop.backend.dto.employee.EmployeeRequest;
|
||||||
|
import com.petshop.backend.dto.employee.EmployeeResponse;
|
||||||
|
import com.petshop.backend.service.EmployeeService;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/employees")
|
||||||
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
|
public class EmployeeController {
|
||||||
|
private final EmployeeService employeeService;
|
||||||
|
|
||||||
|
public EmployeeController(EmployeeService employeeService) {
|
||||||
|
this.employeeService = employeeService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public ResponseEntity<Page<EmployeeResponse>> getAllEmployees(@RequestParam(required = false) String q, Pageable pageable) {
|
||||||
|
return ResponseEntity.ok(employeeService.getAllEmployees(q, pageable));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public ResponseEntity<EmployeeResponse> getEmployeeById(@PathVariable Long id) {
|
||||||
|
return ResponseEntity.ok(employeeService.getEmployeeById(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public ResponseEntity<EmployeeResponse> createEmployee(@Valid @RequestBody EmployeeRequest request) {
|
||||||
|
return ResponseEntity.status(HttpStatus.CREATED).body(employeeService.createEmployee(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}")
|
||||||
|
public ResponseEntity<EmployeeResponse> updateEmployee(@PathVariable Long id, @Valid @RequestBody EmployeeRequest request) {
|
||||||
|
return ResponseEntity.ok(employeeService.updateEmployee(id, request));
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
public ResponseEntity<Void> deleteEmployee(@PathVariable Long id) {
|
||||||
|
employeeService.deleteEmployee(id);
|
||||||
|
return ResponseEntity.noContent().build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package com.petshop.backend.controller;
|
|||||||
import com.petshop.backend.dto.common.BulkDeleteRequest;
|
import com.petshop.backend.dto.common.BulkDeleteRequest;
|
||||||
import com.petshop.backend.dto.user.UserRequest;
|
import com.petshop.backend.dto.user.UserRequest;
|
||||||
import com.petshop.backend.dto.user.UserResponse;
|
import com.petshop.backend.dto.user.UserResponse;
|
||||||
|
import com.petshop.backend.entity.User;
|
||||||
import com.petshop.backend.service.UserService;
|
import com.petshop.backend.service.UserService;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
@@ -26,8 +27,9 @@ public class UserController {
|
|||||||
@GetMapping
|
@GetMapping
|
||||||
public ResponseEntity<Page<UserResponse>> getAllUsers(
|
public ResponseEntity<Page<UserResponse>> getAllUsers(
|
||||||
@RequestParam(required = false) String q,
|
@RequestParam(required = false) String q,
|
||||||
|
@RequestParam(required = false) String role,
|
||||||
Pageable pageable) {
|
Pageable pageable) {
|
||||||
return ResponseEntity.ok(userService.getAllUsers(q, pageable));
|
return ResponseEntity.ok(userService.getAllUsers(q, role, pageable));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{id}")
|
@GetMapping("/{id}")
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ public class ProfileUpdateRequest {
|
|||||||
@Size(max = 100, message = "Full name must not exceed 100 characters")
|
@Size(max = 100, message = "Full name must not exceed 100 characters")
|
||||||
private String fullName;
|
private String fullName;
|
||||||
|
|
||||||
|
@Size(max = 20, message = "Phone must not exceed 20 characters")
|
||||||
|
private String phone;
|
||||||
|
|
||||||
@Size(min = 6, message = "Password must be at least 6 characters")
|
@Size(min = 6, message = "Password must be at least 6 characters")
|
||||||
private String password;
|
private String password;
|
||||||
|
|
||||||
@@ -41,6 +44,14 @@ public class ProfileUpdateRequest {
|
|||||||
this.fullName = fullName;
|
this.fullName = fullName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getPhone() {
|
||||||
|
return phone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPhone(String phone) {
|
||||||
|
this.phone = phone;
|
||||||
|
}
|
||||||
|
|
||||||
public String getPassword() {
|
public String getPassword() {
|
||||||
return password;
|
return password;
|
||||||
}
|
}
|
||||||
@@ -57,12 +68,13 @@ public class ProfileUpdateRequest {
|
|||||||
return Objects.equals(username, that.username) &&
|
return Objects.equals(username, that.username) &&
|
||||||
Objects.equals(email, that.email) &&
|
Objects.equals(email, that.email) &&
|
||||||
Objects.equals(fullName, that.fullName) &&
|
Objects.equals(fullName, that.fullName) &&
|
||||||
|
Objects.equals(phone, that.phone) &&
|
||||||
Objects.equals(password, that.password);
|
Objects.equals(password, that.password);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(username, email, fullName, password);
|
return Objects.hash(username, email, fullName, phone, password);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -71,6 +83,7 @@ public class ProfileUpdateRequest {
|
|||||||
"username='" + username + '\'' +
|
"username='" + username + '\'' +
|
||||||
", email='" + email + '\'' +
|
", email='" + email + '\'' +
|
||||||
", fullName='" + fullName + '\'' +
|
", fullName='" + fullName + '\'' +
|
||||||
|
", phone='" + phone + '\'' +
|
||||||
", password='" + password + '\'' +
|
", password='" + password + '\'' +
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,10 @@ public class RegisterRequest {
|
|||||||
@Size(max = 100, message = "Full name must not exceed 100 characters")
|
@Size(max = 100, message = "Full name must not exceed 100 characters")
|
||||||
private String fullName;
|
private String fullName;
|
||||||
|
|
||||||
|
@NotBlank(message = "Phone is required")
|
||||||
|
@Size(max = 20, message = "Phone must not exceed 20 characters")
|
||||||
|
private String phone;
|
||||||
|
|
||||||
public String getUsername() {
|
public String getUsername() {
|
||||||
return username;
|
return username;
|
||||||
}
|
}
|
||||||
@@ -54,6 +58,14 @@ public class RegisterRequest {
|
|||||||
this.fullName = fullName;
|
this.fullName = fullName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getPhone() {
|
||||||
|
return phone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPhone(String phone) {
|
||||||
|
this.phone = phone;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
@@ -62,12 +74,13 @@ public class RegisterRequest {
|
|||||||
return Objects.equals(username, that.username) &&
|
return Objects.equals(username, that.username) &&
|
||||||
Objects.equals(password, that.password) &&
|
Objects.equals(password, that.password) &&
|
||||||
Objects.equals(email, that.email) &&
|
Objects.equals(email, that.email) &&
|
||||||
Objects.equals(fullName, that.fullName);
|
Objects.equals(fullName, that.fullName) &&
|
||||||
|
Objects.equals(phone, that.phone);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(username, password, email, fullName);
|
return Objects.hash(username, password, email, fullName, phone);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -77,6 +90,7 @@ public class RegisterRequest {
|
|||||||
", password='" + password + '\'' +
|
", password='" + password + '\'' +
|
||||||
", email='" + email + '\'' +
|
", email='" + email + '\'' +
|
||||||
", fullName='" + fullName + '\'' +
|
", fullName='" + fullName + '\'' +
|
||||||
|
", phone='" + phone + '\'' +
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,16 +6,18 @@ public class RegisterResponse {
|
|||||||
private Long id;
|
private Long id;
|
||||||
private String username;
|
private String username;
|
||||||
private String email;
|
private String email;
|
||||||
|
private String phone;
|
||||||
private String role;
|
private String role;
|
||||||
private String token;
|
private String token;
|
||||||
|
|
||||||
public RegisterResponse() {
|
public RegisterResponse() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public RegisterResponse(Long id, String username, String email, String role, String token) {
|
public RegisterResponse(Long id, String username, String email, String phone, String role, String token) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.username = username;
|
this.username = username;
|
||||||
this.email = email;
|
this.email = email;
|
||||||
|
this.phone = phone;
|
||||||
this.role = role;
|
this.role = role;
|
||||||
this.token = token;
|
this.token = token;
|
||||||
}
|
}
|
||||||
@@ -44,6 +46,14 @@ public class RegisterResponse {
|
|||||||
this.email = email;
|
this.email = email;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getPhone() {
|
||||||
|
return phone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPhone(String phone) {
|
||||||
|
this.phone = phone;
|
||||||
|
}
|
||||||
|
|
||||||
public String getRole() {
|
public String getRole() {
|
||||||
return role;
|
return role;
|
||||||
}
|
}
|
||||||
@@ -68,13 +78,14 @@ public class RegisterResponse {
|
|||||||
return Objects.equals(id, that.id) &&
|
return Objects.equals(id, that.id) &&
|
||||||
Objects.equals(username, that.username) &&
|
Objects.equals(username, that.username) &&
|
||||||
Objects.equals(email, that.email) &&
|
Objects.equals(email, that.email) &&
|
||||||
|
Objects.equals(phone, that.phone) &&
|
||||||
Objects.equals(role, that.role) &&
|
Objects.equals(role, that.role) &&
|
||||||
Objects.equals(token, that.token);
|
Objects.equals(token, that.token);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(id, username, email, role, token);
|
return Objects.hash(id, username, email, phone, role, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -83,6 +94,7 @@ public class RegisterResponse {
|
|||||||
"id=" + id +
|
"id=" + id +
|
||||||
", username='" + username + '\'' +
|
", username='" + username + '\'' +
|
||||||
", email='" + email + '\'' +
|
", email='" + email + '\'' +
|
||||||
|
", phone='" + phone + '\'' +
|
||||||
", role='" + role + '\'' +
|
", role='" + role + '\'' +
|
||||||
", token='" + token + '\'' +
|
", token='" + token + '\'' +
|
||||||
'}';
|
'}';
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ public class UserInfoResponse {
|
|||||||
private String username;
|
private String username;
|
||||||
private String email;
|
private String email;
|
||||||
private String fullName;
|
private String fullName;
|
||||||
|
private String phone;
|
||||||
private String avatarUrl;
|
private String avatarUrl;
|
||||||
private String role;
|
private String role;
|
||||||
private Long storeId;
|
private Long storeId;
|
||||||
@@ -15,11 +16,12 @@ public class UserInfoResponse {
|
|||||||
public UserInfoResponse() {
|
public UserInfoResponse() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public UserInfoResponse(Long id, String username, String email, String fullName, String avatarUrl, String role, Long storeId, String storeName) {
|
public UserInfoResponse(Long id, String username, String email, String fullName, String phone, String avatarUrl, String role, Long storeId, String storeName) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.username = username;
|
this.username = username;
|
||||||
this.email = email;
|
this.email = email;
|
||||||
this.fullName = fullName;
|
this.fullName = fullName;
|
||||||
|
this.phone = phone;
|
||||||
this.avatarUrl = avatarUrl;
|
this.avatarUrl = avatarUrl;
|
||||||
this.role = role;
|
this.role = role;
|
||||||
this.storeId = storeId;
|
this.storeId = storeId;
|
||||||
@@ -58,6 +60,14 @@ public class UserInfoResponse {
|
|||||||
this.fullName = fullName;
|
this.fullName = fullName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getPhone() {
|
||||||
|
return phone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPhone(String phone) {
|
||||||
|
this.phone = phone;
|
||||||
|
}
|
||||||
|
|
||||||
public String getAvatarUrl() {
|
public String getAvatarUrl() {
|
||||||
return avatarUrl;
|
return avatarUrl;
|
||||||
}
|
}
|
||||||
@@ -99,6 +109,7 @@ public class UserInfoResponse {
|
|||||||
Objects.equals(username, that.username) &&
|
Objects.equals(username, that.username) &&
|
||||||
Objects.equals(email, that.email) &&
|
Objects.equals(email, that.email) &&
|
||||||
Objects.equals(fullName, that.fullName) &&
|
Objects.equals(fullName, that.fullName) &&
|
||||||
|
Objects.equals(phone, that.phone) &&
|
||||||
Objects.equals(avatarUrl, that.avatarUrl) &&
|
Objects.equals(avatarUrl, that.avatarUrl) &&
|
||||||
Objects.equals(role, that.role) &&
|
Objects.equals(role, that.role) &&
|
||||||
Objects.equals(storeId, that.storeId) &&
|
Objects.equals(storeId, that.storeId) &&
|
||||||
@@ -107,7 +118,7 @@ public class UserInfoResponse {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(id, username, email, fullName, avatarUrl, role, storeId, storeName);
|
return Objects.hash(id, username, email, fullName, phone, avatarUrl, role, storeId, storeName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -117,6 +128,7 @@ public class UserInfoResponse {
|
|||||||
", username='" + username + '\'' +
|
", username='" + username + '\'' +
|
||||||
", email='" + email + '\'' +
|
", email='" + email + '\'' +
|
||||||
", fullName='" + fullName + '\'' +
|
", fullName='" + fullName + '\'' +
|
||||||
|
", phone='" + phone + '\'' +
|
||||||
", avatarUrl='" + avatarUrl + '\'' +
|
", avatarUrl='" + avatarUrl + '\'' +
|
||||||
", role='" + role + '\'' +
|
", role='" + role + '\'' +
|
||||||
", storeId=" + storeId +
|
", storeId=" + storeId +
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ public class CustomerRequest {
|
|||||||
@Email(message = "Invalid email format")
|
@Email(message = "Invalid email format")
|
||||||
private String email;
|
private String email;
|
||||||
|
|
||||||
private String phone;
|
|
||||||
|
|
||||||
public String getFirstName() {
|
public String getFirstName() {
|
||||||
return firstName;
|
return firstName;
|
||||||
}
|
}
|
||||||
@@ -40,14 +38,6 @@ public class CustomerRequest {
|
|||||||
this.email = email;
|
this.email = email;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPhone() {
|
|
||||||
return phone;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPhone(String phone) {
|
|
||||||
this.phone = phone;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
@@ -55,13 +45,12 @@ public class CustomerRequest {
|
|||||||
CustomerRequest that = (CustomerRequest) o;
|
CustomerRequest that = (CustomerRequest) o;
|
||||||
return Objects.equals(firstName, that.firstName) &&
|
return Objects.equals(firstName, that.firstName) &&
|
||||||
Objects.equals(lastName, that.lastName) &&
|
Objects.equals(lastName, that.lastName) &&
|
||||||
Objects.equals(email, that.email) &&
|
Objects.equals(email, that.email);
|
||||||
Objects.equals(phone, that.phone);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(firstName, lastName, email, phone);
|
return Objects.hash(firstName, lastName, email);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -70,7 +59,6 @@ public class CustomerRequest {
|
|||||||
"firstName='" + firstName + '\'' +
|
"firstName='" + firstName + '\'' +
|
||||||
", lastName='" + lastName + '\'' +
|
", lastName='" + lastName + '\'' +
|
||||||
", email='" + email + '\'' +
|
", email='" + email + '\'' +
|
||||||
", phone='" + phone + '\'' +
|
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,19 +8,17 @@ public class CustomerResponse {
|
|||||||
private String firstName;
|
private String firstName;
|
||||||
private String lastName;
|
private String lastName;
|
||||||
private String email;
|
private String email;
|
||||||
private String phone;
|
|
||||||
private LocalDateTime createdAt;
|
private LocalDateTime createdAt;
|
||||||
private LocalDateTime updatedAt;
|
private LocalDateTime updatedAt;
|
||||||
|
|
||||||
public CustomerResponse() {
|
public CustomerResponse() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public CustomerResponse(Long customerId, String firstName, String lastName, String email, String phone, LocalDateTime createdAt, LocalDateTime updatedAt) {
|
public CustomerResponse(Long customerId, String firstName, String lastName, String email, LocalDateTime createdAt, LocalDateTime updatedAt) {
|
||||||
this.customerId = customerId;
|
this.customerId = customerId;
|
||||||
this.firstName = firstName;
|
this.firstName = firstName;
|
||||||
this.lastName = lastName;
|
this.lastName = lastName;
|
||||||
this.email = email;
|
this.email = email;
|
||||||
this.phone = phone;
|
|
||||||
this.createdAt = createdAt;
|
this.createdAt = createdAt;
|
||||||
this.updatedAt = updatedAt;
|
this.updatedAt = updatedAt;
|
||||||
}
|
}
|
||||||
@@ -57,14 +55,6 @@ public class CustomerResponse {
|
|||||||
this.email = email;
|
this.email = email;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPhone() {
|
|
||||||
return phone;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPhone(String phone) {
|
|
||||||
this.phone = phone;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LocalDateTime getCreatedAt() {
|
public LocalDateTime getCreatedAt() {
|
||||||
return createdAt;
|
return createdAt;
|
||||||
}
|
}
|
||||||
@@ -86,12 +76,12 @@ public class CustomerResponse {
|
|||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
CustomerResponse that = (CustomerResponse) o;
|
CustomerResponse that = (CustomerResponse) o;
|
||||||
return Objects.equals(customerId, that.customerId) && Objects.equals(firstName, that.firstName) && Objects.equals(lastName, that.lastName) && Objects.equals(email, that.email) && Objects.equals(phone, that.phone) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt);
|
return Objects.equals(customerId, that.customerId) && Objects.equals(firstName, that.firstName) && Objects.equals(lastName, that.lastName) && Objects.equals(email, that.email) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(customerId, firstName, lastName, email, phone, createdAt, updatedAt);
|
return Objects.hash(customerId, firstName, lastName, email, createdAt, updatedAt);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -101,7 +91,6 @@ public class CustomerResponse {
|
|||||||
", firstName='" + firstName + '\'' +
|
", firstName='" + firstName + '\'' +
|
||||||
", lastName='" + lastName + '\'' +
|
", lastName='" + lastName + '\'' +
|
||||||
", email='" + email + '\'' +
|
", email='" + email + '\'' +
|
||||||
", phone='" + phone + '\'' +
|
|
||||||
", createdAt=" + createdAt +
|
", createdAt=" + createdAt +
|
||||||
", updatedAt=" + updatedAt +
|
", updatedAt=" + updatedAt +
|
||||||
'}';
|
'}';
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package com.petshop.backend.dto.employee;
|
||||||
|
|
||||||
|
import com.petshop.backend.entity.User;
|
||||||
|
import jakarta.validation.constraints.Email;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import jakarta.validation.constraints.Size;
|
||||||
|
|
||||||
|
public class EmployeeRequest {
|
||||||
|
@NotBlank(message = "Username is required")
|
||||||
|
@Size(min = 3, max = 50, message = "Username must be between 3 and 50 characters")
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
@Size(min = 6, message = "Password must be at least 6 characters")
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
@NotBlank(message = "First name is required")
|
||||||
|
private String firstName;
|
||||||
|
|
||||||
|
@NotBlank(message = "Last name is required")
|
||||||
|
private String lastName;
|
||||||
|
|
||||||
|
@Email(message = "Invalid email format")
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
@NotBlank(message = "Phone is required")
|
||||||
|
@Size(max = 20, message = "Phone must not exceed 20 characters")
|
||||||
|
private String phone;
|
||||||
|
|
||||||
|
@NotNull(message = "Role is required")
|
||||||
|
private User.Role role;
|
||||||
|
|
||||||
|
private Boolean active = true;
|
||||||
|
|
||||||
|
public String getUsername() { return username; }
|
||||||
|
public void setUsername(String username) { this.username = username; }
|
||||||
|
public String getPassword() { return password; }
|
||||||
|
public void setPassword(String password) { this.password = password; }
|
||||||
|
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 getEmail() { return email; }
|
||||||
|
public void setEmail(String email) { this.email = email; }
|
||||||
|
public String getPhone() { return phone; }
|
||||||
|
public void setPhone(String phone) { this.phone = phone; }
|
||||||
|
public User.Role getRole() { return role; }
|
||||||
|
public void setRole(User.Role role) { this.role = role; }
|
||||||
|
public Boolean getActive() { return active; }
|
||||||
|
public void setActive(Boolean active) { this.active = active; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package com.petshop.backend.dto.employee;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
public class EmployeeResponse {
|
||||||
|
private Long employeeId;
|
||||||
|
private Long userId;
|
||||||
|
private String username;
|
||||||
|
private String firstName;
|
||||||
|
private String lastName;
|
||||||
|
private String fullName;
|
||||||
|
private String email;
|
||||||
|
private String phone;
|
||||||
|
private String role;
|
||||||
|
private Boolean active;
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
|
||||||
|
public Long getEmployeeId() { return employeeId; }
|
||||||
|
public void setEmployeeId(Long employeeId) { this.employeeId = employeeId; }
|
||||||
|
public Long getUserId() { return userId; }
|
||||||
|
public void setUserId(Long userId) { this.userId = userId; }
|
||||||
|
public String getUsername() { return username; }
|
||||||
|
public void setUsername(String username) { this.username = username; }
|
||||||
|
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 getFullName() { return fullName; }
|
||||||
|
public void setFullName(String fullName) { this.fullName = fullName; }
|
||||||
|
public String getEmail() { return email; }
|
||||||
|
public void setEmail(String email) { this.email = email; }
|
||||||
|
public String getPhone() { return phone; }
|
||||||
|
public void setPhone(String phone) { this.phone = phone; }
|
||||||
|
public String getRole() { return role; }
|
||||||
|
public void setRole(String role) { this.role = role; }
|
||||||
|
public Boolean getActive() { return active; }
|
||||||
|
public void setActive(Boolean active) { this.active = active; }
|
||||||
|
public LocalDateTime getCreatedAt() { return createdAt; }
|
||||||
|
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
|
||||||
|
public LocalDateTime getUpdatedAt() { return updatedAt; }
|
||||||
|
public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
|
||||||
|
}
|
||||||
@@ -21,6 +21,9 @@ public class UserRequest {
|
|||||||
@Email(message = "Invalid email format")
|
@Email(message = "Invalid email format")
|
||||||
private String email;
|
private String email;
|
||||||
|
|
||||||
|
@Size(max = 20, message = "Phone must not exceed 20 characters")
|
||||||
|
private String phone;
|
||||||
|
|
||||||
@NotNull(message = "Role is required")
|
@NotNull(message = "Role is required")
|
||||||
private User.Role role;
|
private User.Role role;
|
||||||
|
|
||||||
@@ -58,6 +61,14 @@ public class UserRequest {
|
|||||||
this.email = email;
|
this.email = email;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getPhone() {
|
||||||
|
return phone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPhone(String phone) {
|
||||||
|
this.phone = phone;
|
||||||
|
}
|
||||||
|
|
||||||
public User.Role getRole() {
|
public User.Role getRole() {
|
||||||
return role;
|
return role;
|
||||||
}
|
}
|
||||||
@@ -83,13 +94,14 @@ public class UserRequest {
|
|||||||
Objects.equals(password, that.password) &&
|
Objects.equals(password, that.password) &&
|
||||||
Objects.equals(fullName, that.fullName) &&
|
Objects.equals(fullName, that.fullName) &&
|
||||||
Objects.equals(email, that.email) &&
|
Objects.equals(email, that.email) &&
|
||||||
|
Objects.equals(phone, that.phone) &&
|
||||||
role == that.role &&
|
role == that.role &&
|
||||||
Objects.equals(active, that.active);
|
Objects.equals(active, that.active);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(username, password, fullName, email, role, active);
|
return Objects.hash(username, password, fullName, email, phone, role, active);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -99,6 +111,7 @@ public class UserRequest {
|
|||||||
", password='" + password + '\'' +
|
", password='" + password + '\'' +
|
||||||
", fullName='" + fullName + '\'' +
|
", fullName='" + fullName + '\'' +
|
||||||
", email='" + email + '\'' +
|
", email='" + email + '\'' +
|
||||||
|
", phone='" + phone + '\'' +
|
||||||
", role=" + role +
|
", role=" + role +
|
||||||
", active=" + active +
|
", active=" + active +
|
||||||
'}';
|
'}';
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ public class UserResponse {
|
|||||||
private String username;
|
private String username;
|
||||||
private String fullName;
|
private String fullName;
|
||||||
private String email;
|
private String email;
|
||||||
|
private String phone;
|
||||||
private String role;
|
private String role;
|
||||||
private Boolean active;
|
private Boolean active;
|
||||||
private LocalDateTime createdAt;
|
private LocalDateTime createdAt;
|
||||||
@@ -16,11 +17,12 @@ public class UserResponse {
|
|||||||
public UserResponse() {
|
public UserResponse() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public UserResponse(Long id, String username, String fullName, String email, String role, Boolean active, LocalDateTime createdAt, LocalDateTime updatedAt) {
|
public UserResponse(Long id, String username, String fullName, String email, String phone, String role, Boolean active, LocalDateTime createdAt, LocalDateTime updatedAt) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.username = username;
|
this.username = username;
|
||||||
this.fullName = fullName;
|
this.fullName = fullName;
|
||||||
this.email = email;
|
this.email = email;
|
||||||
|
this.phone = phone;
|
||||||
this.role = role;
|
this.role = role;
|
||||||
this.active = active;
|
this.active = active;
|
||||||
this.createdAt = createdAt;
|
this.createdAt = createdAt;
|
||||||
@@ -59,6 +61,14 @@ public class UserResponse {
|
|||||||
this.email = email;
|
this.email = email;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getPhone() {
|
||||||
|
return phone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPhone(String phone) {
|
||||||
|
this.phone = phone;
|
||||||
|
}
|
||||||
|
|
||||||
public String getRole() {
|
public String getRole() {
|
||||||
return role;
|
return role;
|
||||||
}
|
}
|
||||||
@@ -96,12 +106,12 @@ public class UserResponse {
|
|||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
UserResponse that = (UserResponse) o;
|
UserResponse that = (UserResponse) o;
|
||||||
return Objects.equals(id, that.id) && Objects.equals(username, that.username) && Objects.equals(fullName, that.fullName) && Objects.equals(email, that.email) && Objects.equals(role, that.role) && Objects.equals(active, that.active) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt);
|
return Objects.equals(id, that.id) && Objects.equals(username, that.username) && Objects.equals(fullName, that.fullName) && Objects.equals(email, that.email) && Objects.equals(phone, that.phone) && Objects.equals(role, that.role) && Objects.equals(active, that.active) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(id, username, fullName, email, role, active, createdAt, updatedAt);
|
return Objects.hash(id, username, fullName, email, phone, role, active, createdAt, updatedAt);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -111,6 +121,7 @@ public class UserResponse {
|
|||||||
", username='" + username + '\'' +
|
", username='" + username + '\'' +
|
||||||
", fullName='" + fullName + '\'' +
|
", fullName='" + fullName + '\'' +
|
||||||
", email='" + email + '\'' +
|
", email='" + email + '\'' +
|
||||||
|
", phone='" + phone + '\'' +
|
||||||
", role='" + role + '\'' +
|
", role='" + role + '\'' +
|
||||||
", active=" + active +
|
", active=" + active +
|
||||||
", createdAt=" + createdAt +
|
", createdAt=" + createdAt +
|
||||||
|
|||||||
@@ -27,9 +27,6 @@ public class Customer {
|
|||||||
@Column(nullable = false, length = 100)
|
@Column(nullable = false, length = 100)
|
||||||
private String email;
|
private String email;
|
||||||
|
|
||||||
@Column(nullable = false, length = 20)
|
|
||||||
private String phone;
|
|
||||||
|
|
||||||
@CreationTimestamp
|
@CreationTimestamp
|
||||||
@Column(name = "created_at", updatable = false)
|
@Column(name = "created_at", updatable = false)
|
||||||
private LocalDateTime createdAt;
|
private LocalDateTime createdAt;
|
||||||
@@ -41,13 +38,12 @@ public class Customer {
|
|||||||
public Customer() {
|
public Customer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Customer(Long customerId, Long userId, String firstName, String lastName, String email, String phone, LocalDateTime createdAt, LocalDateTime updatedAt) {
|
public Customer(Long customerId, Long userId, String firstName, String lastName, String email, LocalDateTime createdAt, LocalDateTime updatedAt) {
|
||||||
this.customerId = customerId;
|
this.customerId = customerId;
|
||||||
this.userId = userId;
|
this.userId = userId;
|
||||||
this.firstName = firstName;
|
this.firstName = firstName;
|
||||||
this.lastName = lastName;
|
this.lastName = lastName;
|
||||||
this.email = email;
|
this.email = email;
|
||||||
this.phone = phone;
|
|
||||||
this.createdAt = createdAt;
|
this.createdAt = createdAt;
|
||||||
this.updatedAt = updatedAt;
|
this.updatedAt = updatedAt;
|
||||||
}
|
}
|
||||||
@@ -92,14 +88,6 @@ public class Customer {
|
|||||||
this.email = email;
|
this.email = email;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPhone() {
|
|
||||||
return phone;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPhone(String phone) {
|
|
||||||
this.phone = phone;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LocalDateTime getCreatedAt() {
|
public LocalDateTime getCreatedAt() {
|
||||||
return createdAt;
|
return createdAt;
|
||||||
}
|
}
|
||||||
@@ -137,7 +125,6 @@ public class Customer {
|
|||||||
", firstName='" + firstName + '\'' +
|
", firstName='" + firstName + '\'' +
|
||||||
", lastName='" + lastName + '\'' +
|
", lastName='" + lastName + '\'' +
|
||||||
", email='" + email + '\'' +
|
", email='" + email + '\'' +
|
||||||
", phone='" + phone + '\'' +
|
|
||||||
", createdAt=" + createdAt +
|
", createdAt=" + createdAt +
|
||||||
", updatedAt=" + updatedAt +
|
", updatedAt=" + updatedAt +
|
||||||
'}';
|
'}';
|
||||||
|
|||||||
@@ -27,9 +27,6 @@ public class Employee {
|
|||||||
@Column(nullable = false, length = 100)
|
@Column(nullable = false, length = 100)
|
||||||
private String email;
|
private String email;
|
||||||
|
|
||||||
@Column(nullable = false, length = 20)
|
|
||||||
private String phone;
|
|
||||||
|
|
||||||
@Column(nullable = false, length = 50)
|
@Column(nullable = false, length = 50)
|
||||||
private String role;
|
private String role;
|
||||||
|
|
||||||
@@ -47,13 +44,12 @@ public class Employee {
|
|||||||
public Employee() {
|
public Employee() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Employee(Long employeeId, Long userId, String firstName, String lastName, String email, String phone, String role, Boolean isActive, LocalDateTime createdAt, LocalDateTime updatedAt) {
|
public Employee(Long employeeId, Long userId, String firstName, String lastName, String email, String role, Boolean isActive, LocalDateTime createdAt, LocalDateTime updatedAt) {
|
||||||
this.employeeId = employeeId;
|
this.employeeId = employeeId;
|
||||||
this.userId = userId;
|
this.userId = userId;
|
||||||
this.firstName = firstName;
|
this.firstName = firstName;
|
||||||
this.lastName = lastName;
|
this.lastName = lastName;
|
||||||
this.email = email;
|
this.email = email;
|
||||||
this.phone = phone;
|
|
||||||
this.role = role;
|
this.role = role;
|
||||||
this.isActive = isActive;
|
this.isActive = isActive;
|
||||||
this.createdAt = createdAt;
|
this.createdAt = createdAt;
|
||||||
@@ -100,14 +96,6 @@ public class Employee {
|
|||||||
this.email = email;
|
this.email = email;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPhone() {
|
|
||||||
return phone;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPhone(String phone) {
|
|
||||||
this.phone = phone;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRole() {
|
public String getRole() {
|
||||||
return role;
|
return role;
|
||||||
}
|
}
|
||||||
@@ -161,7 +149,6 @@ public class Employee {
|
|||||||
", firstName='" + firstName + '\'' +
|
", firstName='" + firstName + '\'' +
|
||||||
", lastName='" + lastName + '\'' +
|
", lastName='" + lastName + '\'' +
|
||||||
", email='" + email + '\'' +
|
", email='" + email + '\'' +
|
||||||
", phone='" + phone + '\'' +
|
|
||||||
", role='" + role + '\'' +
|
", role='" + role + '\'' +
|
||||||
", isActive=" + isActive +
|
", isActive=" + isActive +
|
||||||
", createdAt=" + createdAt +
|
", createdAt=" + createdAt +
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ public class User {
|
|||||||
@Column(length = 100)
|
@Column(length = 100)
|
||||||
private String fullName;
|
private String fullName;
|
||||||
|
|
||||||
|
@Column(length = 20)
|
||||||
|
private String phone;
|
||||||
|
|
||||||
@Column(length = 255)
|
@Column(length = 255)
|
||||||
private String avatarUrl;
|
private String avatarUrl;
|
||||||
|
|
||||||
@@ -55,12 +58,13 @@ public class User {
|
|||||||
public User() {
|
public User() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public User(Long id, String username, String password, String email, String fullName, String avatarUrl, Role role, Boolean active, Integer tokenVersion, LocalDateTime createdAt, LocalDateTime updatedAt) {
|
public User(Long id, String username, String password, String email, String fullName, String phone, String avatarUrl, Role role, Boolean active, Integer tokenVersion, LocalDateTime createdAt, LocalDateTime updatedAt) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.username = username;
|
this.username = username;
|
||||||
this.password = password;
|
this.password = password;
|
||||||
this.email = email;
|
this.email = email;
|
||||||
this.fullName = fullName;
|
this.fullName = fullName;
|
||||||
|
this.phone = phone;
|
||||||
this.avatarUrl = avatarUrl;
|
this.avatarUrl = avatarUrl;
|
||||||
this.role = role;
|
this.role = role;
|
||||||
this.active = active;
|
this.active = active;
|
||||||
@@ -109,6 +113,14 @@ public class User {
|
|||||||
this.fullName = fullName;
|
this.fullName = fullName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getPhone() {
|
||||||
|
return phone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPhone(String phone) {
|
||||||
|
this.phone = phone;
|
||||||
|
}
|
||||||
|
|
||||||
public String getAvatarUrl() {
|
public String getAvatarUrl() {
|
||||||
return avatarUrl;
|
return avatarUrl;
|
||||||
}
|
}
|
||||||
@@ -178,6 +190,7 @@ public class User {
|
|||||||
", password='" + password + '\'' +
|
", password='" + password + '\'' +
|
||||||
", email='" + email + '\'' +
|
", email='" + email + '\'' +
|
||||||
", fullName='" + fullName + '\'' +
|
", fullName='" + fullName + '\'' +
|
||||||
|
", phone='" + phone + '\'' +
|
||||||
", avatarUrl='" + avatarUrl + '\'' +
|
", avatarUrl='" + avatarUrl + '\'' +
|
||||||
", role=" + role +
|
", role=" + role +
|
||||||
", active=" + active +
|
", active=" + active +
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package com.petshop.backend.exception;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.json.JsonMapper;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class ApiErrorResponder {
|
||||||
|
|
||||||
|
private final ObjectMapper objectMapper = JsonMapper.builder().findAndAddModules().build();
|
||||||
|
|
||||||
|
public void write(HttpServletResponse response, HttpStatus status, String message, String details, String path) throws IOException {
|
||||||
|
response.setStatus(status.value());
|
||||||
|
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||||
|
objectMapper.writeValue(
|
||||||
|
response.getWriter(),
|
||||||
|
new ApiErrorResponse(
|
||||||
|
status.value(),
|
||||||
|
message,
|
||||||
|
details,
|
||||||
|
path,
|
||||||
|
LocalDateTime.now()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.petshop.backend.exception;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
public record ApiErrorResponse(
|
||||||
|
int status,
|
||||||
|
String message,
|
||||||
|
String details,
|
||||||
|
String path,
|
||||||
|
LocalDateTime timestamp
|
||||||
|
) {
|
||||||
|
}
|
||||||
@@ -1,12 +1,15 @@
|
|||||||
package com.petshop.backend.exception;
|
package com.petshop.backend.exception;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import org.springframework.dao.DataIntegrityViolationException;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.dao.DataIntegrityViolationException;
|
|
||||||
import org.springframework.validation.FieldError;
|
import org.springframework.validation.FieldError;
|
||||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||||
|
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
|
||||||
|
import org.springframework.web.server.ResponseStatusException;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@@ -16,27 +19,17 @@ import java.util.Map;
|
|||||||
public class GlobalExceptionHandler {
|
public class GlobalExceptionHandler {
|
||||||
|
|
||||||
@ExceptionHandler(ResourceNotFoundException.class)
|
@ExceptionHandler(ResourceNotFoundException.class)
|
||||||
public ResponseEntity<ErrorResponse> handleResourceNotFound(ResourceNotFoundException ex) {
|
public ResponseEntity<ApiErrorResponse> handleResourceNotFound(ResourceNotFoundException ex, HttpServletRequest request) {
|
||||||
ErrorResponse error = new ErrorResponse(
|
return buildErrorResponse(HttpStatus.NOT_FOUND, ex.getMessage(), ex, request);
|
||||||
HttpStatus.NOT_FOUND.value(),
|
|
||||||
ex.getMessage(),
|
|
||||||
LocalDateTime.now()
|
|
||||||
);
|
|
||||||
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExceptionHandler(BusinessException.class)
|
@ExceptionHandler(BusinessException.class)
|
||||||
public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException ex) {
|
public ResponseEntity<ApiErrorResponse> handleBusinessException(BusinessException ex, HttpServletRequest request) {
|
||||||
ErrorResponse error = new ErrorResponse(
|
return buildErrorResponse(HttpStatus.BAD_REQUEST, ex.getMessage(), ex, request);
|
||||||
HttpStatus.BAD_REQUEST.value(),
|
|
||||||
ex.getMessage(),
|
|
||||||
LocalDateTime.now()
|
|
||||||
);
|
|
||||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||||
public ResponseEntity<Map<String, Object>> handleValidationExceptions(MethodArgumentNotValidException ex) {
|
public ResponseEntity<Map<String, Object>> handleValidationExceptions(MethodArgumentNotValidException ex, HttpServletRequest request) {
|
||||||
Map<String, String> errors = new HashMap<>();
|
Map<String, String> errors = new HashMap<>();
|
||||||
ex.getBindingResult().getAllErrors().forEach((error) -> {
|
ex.getBindingResult().getAllErrors().forEach((error) -> {
|
||||||
String fieldName = ((FieldError) error).getField();
|
String fieldName = ((FieldError) error).getField();
|
||||||
@@ -46,51 +39,74 @@ public class GlobalExceptionHandler {
|
|||||||
|
|
||||||
Map<String, Object> response = new HashMap<>();
|
Map<String, Object> response = new HashMap<>();
|
||||||
response.put("status", HttpStatus.BAD_REQUEST.value());
|
response.put("status", HttpStatus.BAD_REQUEST.value());
|
||||||
|
response.put("message", "Validation failed");
|
||||||
response.put("errors", errors);
|
response.put("errors", errors);
|
||||||
|
response.put("details", buildDetails(ex));
|
||||||
|
response.put("path", request.getRequestURI());
|
||||||
response.put("timestamp", LocalDateTime.now());
|
response.put("timestamp", LocalDateTime.now());
|
||||||
|
|
||||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
|
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExceptionHandler(org.springframework.security.access.AccessDeniedException.class)
|
@ExceptionHandler(org.springframework.security.access.AccessDeniedException.class)
|
||||||
public ResponseEntity<ErrorResponse> handleAccessDeniedException(org.springframework.security.access.AccessDeniedException ex) {
|
public ResponseEntity<ApiErrorResponse> handleAccessDeniedException(org.springframework.security.access.AccessDeniedException ex, HttpServletRequest request) {
|
||||||
ErrorResponse error = new ErrorResponse(
|
return buildErrorResponse(HttpStatus.FORBIDDEN, ex.getMessage(), ex, request);
|
||||||
HttpStatus.FORBIDDEN.value(),
|
|
||||||
ex.getMessage(),
|
|
||||||
LocalDateTime.now()
|
|
||||||
);
|
|
||||||
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExceptionHandler(IllegalArgumentException.class)
|
@ExceptionHandler(IllegalArgumentException.class)
|
||||||
public ResponseEntity<ErrorResponse> handleIllegalArgumentException(IllegalArgumentException ex) {
|
public ResponseEntity<ApiErrorResponse> handleIllegalArgumentException(IllegalArgumentException ex, HttpServletRequest request) {
|
||||||
ErrorResponse error = new ErrorResponse(
|
return buildErrorResponse(HttpStatus.BAD_REQUEST, ex.getMessage(), ex, request);
|
||||||
HttpStatus.BAD_REQUEST.value(),
|
|
||||||
ex.getMessage(),
|
|
||||||
LocalDateTime.now()
|
|
||||||
);
|
|
||||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExceptionHandler(DataIntegrityViolationException.class)
|
@ExceptionHandler(DataIntegrityViolationException.class)
|
||||||
public ResponseEntity<ErrorResponse> handleDataIntegrityViolationException(DataIntegrityViolationException ex) {
|
public ResponseEntity<ApiErrorResponse> handleDataIntegrityViolationException(DataIntegrityViolationException ex, HttpServletRequest request) {
|
||||||
ErrorResponse error = new ErrorResponse(
|
return buildErrorResponse(HttpStatus.BAD_REQUEST, "Operation violates existing data relationships", ex, request);
|
||||||
HttpStatus.BAD_REQUEST.value(),
|
}
|
||||||
"Operation violates existing data relationships",
|
|
||||||
LocalDateTime.now()
|
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
|
||||||
);
|
public ResponseEntity<ApiErrorResponse> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException ex, HttpServletRequest request) {
|
||||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
|
String message = "Invalid value for parameter: " + ex.getName();
|
||||||
|
if (ex.getValue() != null) {
|
||||||
|
message += " (" + ex.getValue() + ")";
|
||||||
|
}
|
||||||
|
return buildErrorResponse(HttpStatus.BAD_REQUEST, message, ex, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(ResponseStatusException.class)
|
||||||
|
public ResponseEntity<ApiErrorResponse> handleResponseStatusException(ResponseStatusException ex, HttpServletRequest request) {
|
||||||
|
String message = ex.getReason() != null ? ex.getReason() : ex.getMessage();
|
||||||
|
return buildErrorResponse(HttpStatus.valueOf(ex.getStatusCode().value()), message, ex, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExceptionHandler(Exception.class)
|
@ExceptionHandler(Exception.class)
|
||||||
public ResponseEntity<ErrorResponse> handleGenericException(Exception ex) {
|
public ResponseEntity<ApiErrorResponse> handleGenericException(Exception ex, HttpServletRequest request) {
|
||||||
ErrorResponse error = new ErrorResponse(
|
String message = ex.getMessage() == null || ex.getMessage().isBlank()
|
||||||
HttpStatus.INTERNAL_SERVER_ERROR.value(),
|
? "Unexpected server error"
|
||||||
"An unexpected error occurred: " + ex.getMessage(),
|
: ex.getMessage();
|
||||||
|
return buildErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR, message, ex, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ResponseEntity<ApiErrorResponse> buildErrorResponse(HttpStatus status, String message, Exception ex, HttpServletRequest request) {
|
||||||
|
ApiErrorResponse error = new ApiErrorResponse(
|
||||||
|
status.value(),
|
||||||
|
message,
|
||||||
|
buildDetails(ex),
|
||||||
|
request.getRequestURI(),
|
||||||
LocalDateTime.now()
|
LocalDateTime.now()
|
||||||
);
|
);
|
||||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
|
return ResponseEntity.status(status).body(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildDetails(Exception ex) {
|
||||||
|
Throwable rootCause = ex;
|
||||||
|
while (rootCause.getCause() != null && rootCause.getCause() != rootCause) {
|
||||||
|
rootCause = rootCause.getCause();
|
||||||
|
}
|
||||||
|
|
||||||
|
String rootMessage = rootCause.getMessage();
|
||||||
|
if (rootMessage == null || rootMessage.isBlank()) {
|
||||||
|
return rootCause.getClass().getSimpleName();
|
||||||
|
}
|
||||||
|
return rootCause.getClass().getSimpleName() + ": " + rootMessage;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
record ErrorResponse(int status, String message, LocalDateTime timestamp) {}
|
|
||||||
|
|||||||
@@ -21,6 +21,6 @@ public interface CustomerRepository extends JpaRepository<Customer, Long> {
|
|||||||
"LOWER(c.firstName) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
|
"LOWER(c.firstName) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
|
||||||
"LOWER(c.lastName) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
|
"LOWER(c.lastName) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
|
||||||
"LOWER(c.email) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
|
"LOWER(c.email) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
|
||||||
"LOWER(c.phone) LIKE LOWER(CONCAT('%', :q, '%'))")
|
"EXISTS (SELECT u FROM User u WHERE u.id = c.userId AND LOWER(COALESCE(u.phone, '')) LIKE LOWER(CONCAT('%', :q, '%')))")
|
||||||
Page<Customer> searchCustomers(@Param("q") String query, Pageable pageable);
|
Page<Customer> searchCustomers(@Param("q") String query, Pageable pageable);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
package com.petshop.backend.repository;
|
package com.petshop.backend.repository;
|
||||||
|
|
||||||
import com.petshop.backend.entity.Employee;
|
import com.petshop.backend.entity.Employee;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -11,4 +15,14 @@ import java.util.Optional;
|
|||||||
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
|
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
|
||||||
Optional<Employee> findByUserId(Long userId);
|
Optional<Employee> findByUserId(Long userId);
|
||||||
List<Employee> findAllByEmail(String email);
|
List<Employee> findAllByEmail(String email);
|
||||||
|
|
||||||
|
@Query("SELECT e FROM Employee e WHERE " +
|
||||||
|
"LOWER(e.firstName) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
|
||||||
|
"LOWER(e.lastName) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
|
||||||
|
"LOWER(e.email) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
|
||||||
|
"LOWER(e.role) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
|
||||||
|
"EXISTS (SELECT u FROM User u WHERE u.id = e.userId AND (" +
|
||||||
|
"LOWER(u.username) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
|
||||||
|
"LOWER(COALESCE(u.phone, '')) LIKE LOWER(CONCAT('%', :q, '%'))))")
|
||||||
|
Page<Employee> searchEmployees(@Param("q") String query, Pageable pageable);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,9 +14,21 @@ import java.util.Optional;
|
|||||||
public interface UserRepository extends JpaRepository<User, Long> {
|
public interface UserRepository extends JpaRepository<User, Long> {
|
||||||
Optional<User> findByUsername(String username);
|
Optional<User> findByUsername(String username);
|
||||||
Optional<User> findByEmail(String email);
|
Optional<User> findByEmail(String email);
|
||||||
|
Optional<User> findByPhone(String phone);
|
||||||
boolean existsByUsername(String username);
|
boolean existsByUsername(String username);
|
||||||
|
Page<User> findByRole(User.Role role, Pageable pageable);
|
||||||
|
|
||||||
@Query("SELECT u FROM User u WHERE " +
|
@Query("SELECT u FROM User u WHERE " +
|
||||||
"LOWER(u.username) LIKE LOWER(CONCAT('%', :q, '%'))")
|
"LOWER(u.username) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
|
||||||
|
"LOWER(COALESCE(u.fullName, '')) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
|
||||||
|
"LOWER(COALESCE(u.email, '')) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
|
||||||
|
"LOWER(COALESCE(u.phone, '')) LIKE LOWER(CONCAT('%', :q, '%'))")
|
||||||
Page<User> searchUsers(@Param("q") String query, Pageable pageable);
|
Page<User> searchUsers(@Param("q") String query, Pageable pageable);
|
||||||
|
|
||||||
|
@Query("SELECT u FROM User u WHERE u.role = :role AND (" +
|
||||||
|
"LOWER(u.username) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
|
||||||
|
"LOWER(COALESCE(u.fullName, '')) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
|
||||||
|
"LOWER(COALESCE(u.email, '')) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
|
||||||
|
"LOWER(COALESCE(u.phone, '')) LIKE LOWER(CONCAT('%', :q, '%')))")
|
||||||
|
Page<User> searchUsersByRole(@Param("q") String query, @Param("role") User.Role role, Pageable pageable);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package com.petshop.backend.security;
|
package com.petshop.backend.security;
|
||||||
|
|
||||||
import com.petshop.backend.entity.User;
|
import com.petshop.backend.entity.User;
|
||||||
|
import com.petshop.backend.exception.ApiErrorResponder;
|
||||||
import com.petshop.backend.repository.UserRepository;
|
import com.petshop.backend.repository.UserRepository;
|
||||||
|
import io.jsonwebtoken.JwtException;
|
||||||
import jakarta.servlet.FilterChain;
|
import jakarta.servlet.FilterChain;
|
||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
@@ -14,17 +16,17 @@ import org.springframework.stereotype.Component;
|
|||||||
import org.springframework.web.filter.OncePerRequestFilter;
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.time.LocalDateTime;
|
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
private final JwtUtil jwtUtil;
|
private final JwtUtil jwtUtil;
|
||||||
private final UserRepository userRepository;
|
private final UserRepository userRepository;
|
||||||
|
private final ApiErrorResponder apiErrorResponder;
|
||||||
|
|
||||||
public JwtAuthenticationFilter(JwtUtil jwtUtil, UserRepository userRepository) {
|
public JwtAuthenticationFilter(JwtUtil jwtUtil, UserRepository userRepository, ApiErrorResponder apiErrorResponder) {
|
||||||
this.jwtUtil = jwtUtil;
|
this.jwtUtil = jwtUtil;
|
||||||
this.userRepository = userRepository;
|
this.userRepository = userRepository;
|
||||||
|
this.apiErrorResponder = apiErrorResponder;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -41,16 +43,22 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
jwt = authHeader.substring(7);
|
jwt = authHeader.substring(7);
|
||||||
Long userId = jwtUtil.extractUserId(jwt);
|
Long userId;
|
||||||
|
try {
|
||||||
|
userId = jwtUtil.extractUserId(jwt);
|
||||||
|
} catch (JwtException | IllegalArgumentException ex) {
|
||||||
|
writeUnauthorized(request, response, "Invalid or expired token", ex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (userId != null && SecurityContextHolder.getContext().getAuthentication() == null) {
|
if (userId != null && SecurityContextHolder.getContext().getAuthentication() == null) {
|
||||||
User user = userRepository.findById(userId).orElse(null);
|
User user = userRepository.findById(userId).orElse(null);
|
||||||
if (user == null || user.getActive() == null || !user.getActive()) {
|
if (user == null || user.getActive() == null || !user.getActive()) {
|
||||||
writeUnauthorized(response, "User account is inactive");
|
writeUnauthorized(request, response, "User account is inactive", null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!jwtUtil.validateToken(jwt, user)) {
|
if (!jwtUtil.validateToken(jwt, user)) {
|
||||||
writeUnauthorized(response, "Invalid or expired token");
|
writeUnauthorized(request, response, "Invalid or expired token", null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,11 +79,8 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
filterChain.doFilter(request, response);
|
filterChain.doFilter(request, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeUnauthorized(HttpServletResponse response, String message) throws IOException {
|
private void writeUnauthorized(HttpServletRequest request, HttpServletResponse response, String message, Exception ex) throws IOException {
|
||||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
String details = ex == null ? message : ex.getClass().getSimpleName() + ": " + ex.getMessage();
|
||||||
response.setContentType("application/json");
|
apiErrorResponder.write(response, org.springframework.http.HttpStatus.UNAUTHORIZED, message, details, request.getRequestURI());
|
||||||
response.getWriter().write(
|
|
||||||
"{\"status\":401,\"message\":\"" + message + "\",\"timestamp\":\"" + LocalDateTime.now() + "\"}"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.petshop.backend.security;
|
||||||
|
|
||||||
|
import com.petshop.backend.exception.ApiErrorResponder;
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.security.access.AccessDeniedException;
|
||||||
|
import org.springframework.security.web.access.AccessDeniedHandler;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class RestAccessDeniedHandler implements AccessDeniedHandler {
|
||||||
|
|
||||||
|
private final ApiErrorResponder apiErrorResponder;
|
||||||
|
|
||||||
|
public RestAccessDeniedHandler(ApiErrorResponder apiErrorResponder) {
|
||||||
|
this.apiErrorResponder = apiErrorResponder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
|
||||||
|
apiErrorResponder.write(
|
||||||
|
response,
|
||||||
|
HttpStatus.FORBIDDEN,
|
||||||
|
"Access Denied",
|
||||||
|
accessDeniedException.getClass().getSimpleName() + ": " + accessDeniedException.getMessage(),
|
||||||
|
request.getRequestURI()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.petshop.backend.security;
|
||||||
|
|
||||||
|
import com.petshop.backend.exception.ApiErrorResponder;
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.security.core.AuthenticationException;
|
||||||
|
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
|
||||||
|
|
||||||
|
private final ApiErrorResponder apiErrorResponder;
|
||||||
|
|
||||||
|
public RestAuthenticationEntryPoint(ApiErrorResponder apiErrorResponder) {
|
||||||
|
this.apiErrorResponder = apiErrorResponder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
|
||||||
|
apiErrorResponder.write(
|
||||||
|
response,
|
||||||
|
HttpStatus.UNAUTHORIZED,
|
||||||
|
"Authentication required",
|
||||||
|
authException.getClass().getSimpleName() + ": " + authException.getMessage(),
|
||||||
|
request.getRequestURI()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,10 +25,19 @@ public class SecurityConfig {
|
|||||||
|
|
||||||
private final JwtAuthenticationFilter jwtAuthFilter;
|
private final JwtAuthenticationFilter jwtAuthFilter;
|
||||||
private final UserDetailsService userDetailsService;
|
private final UserDetailsService userDetailsService;
|
||||||
|
private final RestAuthenticationEntryPoint restAuthenticationEntryPoint;
|
||||||
|
private final RestAccessDeniedHandler restAccessDeniedHandler;
|
||||||
|
|
||||||
public SecurityConfig(JwtAuthenticationFilter jwtAuthFilter, UserDetailsService userDetailsService) {
|
public SecurityConfig(
|
||||||
|
JwtAuthenticationFilter jwtAuthFilter,
|
||||||
|
UserDetailsService userDetailsService,
|
||||||
|
RestAuthenticationEntryPoint restAuthenticationEntryPoint,
|
||||||
|
RestAccessDeniedHandler restAccessDeniedHandler
|
||||||
|
) {
|
||||||
this.jwtAuthFilter = jwtAuthFilter;
|
this.jwtAuthFilter = jwtAuthFilter;
|
||||||
this.userDetailsService = userDetailsService;
|
this.userDetailsService = userDetailsService;
|
||||||
|
this.restAuthenticationEntryPoint = restAuthenticationEntryPoint;
|
||||||
|
this.restAccessDeniedHandler = restAccessDeniedHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@@ -47,6 +56,10 @@ public class SecurityConfig {
|
|||||||
.requestMatchers(HttpMethod.GET, "/api/v1/appointments/availability").permitAll()
|
.requestMatchers(HttpMethod.GET, "/api/v1/appointments/availability").permitAll()
|
||||||
.anyRequest().authenticated()
|
.anyRequest().authenticated()
|
||||||
)
|
)
|
||||||
|
.exceptionHandling(ex -> ex
|
||||||
|
.authenticationEntryPoint(restAuthenticationEntryPoint)
|
||||||
|
.accessDeniedHandler(restAccessDeniedHandler)
|
||||||
|
)
|
||||||
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||||
.authenticationProvider(daoAuthenticationProvider())
|
.authenticationProvider(daoAuthenticationProvider())
|
||||||
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
|
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import com.petshop.backend.dto.customer.CustomerResponse;
|
|||||||
import com.petshop.backend.entity.Customer;
|
import com.petshop.backend.entity.Customer;
|
||||||
import com.petshop.backend.exception.ResourceNotFoundException;
|
import com.petshop.backend.exception.ResourceNotFoundException;
|
||||||
import com.petshop.backend.repository.CustomerRepository;
|
import com.petshop.backend.repository.CustomerRepository;
|
||||||
|
import com.petshop.backend.repository.UserRepository;
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@@ -15,9 +16,11 @@ import org.springframework.transaction.annotation.Transactional;
|
|||||||
public class CustomerService {
|
public class CustomerService {
|
||||||
|
|
||||||
private final CustomerRepository customerRepository;
|
private final CustomerRepository customerRepository;
|
||||||
|
private final UserRepository userRepository;
|
||||||
|
|
||||||
public CustomerService(CustomerRepository customerRepository) {
|
public CustomerService(CustomerRepository customerRepository, UserRepository userRepository) {
|
||||||
this.customerRepository = customerRepository;
|
this.customerRepository = customerRepository;
|
||||||
|
this.userRepository = userRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Page<CustomerResponse> getAllCustomers(String query, Pageable pageable) {
|
public Page<CustomerResponse> getAllCustomers(String query, Pageable pageable) {
|
||||||
@@ -42,9 +45,9 @@ public class CustomerService {
|
|||||||
customer.setFirstName(request.getFirstName());
|
customer.setFirstName(request.getFirstName());
|
||||||
customer.setLastName(request.getLastName());
|
customer.setLastName(request.getLastName());
|
||||||
customer.setEmail(request.getEmail());
|
customer.setEmail(request.getEmail());
|
||||||
customer.setPhone(request.getPhone());
|
|
||||||
|
|
||||||
customer = customerRepository.save(customer);
|
customer = customerRepository.save(customer);
|
||||||
|
syncLinkedUser(customer);
|
||||||
return mapToResponse(customer);
|
return mapToResponse(customer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,9 +59,9 @@ public class CustomerService {
|
|||||||
customer.setFirstName(request.getFirstName());
|
customer.setFirstName(request.getFirstName());
|
||||||
customer.setLastName(request.getLastName());
|
customer.setLastName(request.getLastName());
|
||||||
customer.setEmail(request.getEmail());
|
customer.setEmail(request.getEmail());
|
||||||
customer.setPhone(request.getPhone());
|
|
||||||
|
|
||||||
customer = customerRepository.save(customer);
|
customer = customerRepository.save(customer);
|
||||||
|
syncLinkedUser(customer);
|
||||||
return mapToResponse(customer);
|
return mapToResponse(customer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,9 +84,19 @@ public class CustomerService {
|
|||||||
customer.getFirstName(),
|
customer.getFirstName(),
|
||||||
customer.getLastName(),
|
customer.getLastName(),
|
||||||
customer.getEmail(),
|
customer.getEmail(),
|
||||||
customer.getPhone(),
|
|
||||||
customer.getCreatedAt(),
|
customer.getCreatedAt(),
|
||||||
customer.getUpdatedAt()
|
customer.getUpdatedAt()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void syncLinkedUser(Customer customer) {
|
||||||
|
if (customer.getUserId() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
userRepository.findById(customer.getUserId()).ifPresent(user -> {
|
||||||
|
user.setEmail(customer.getEmail());
|
||||||
|
user.setFullName((customer.getFirstName() + " " + customer.getLastName()).trim());
|
||||||
|
userRepository.save(user);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
183
src/main/java/com/petshop/backend/service/EmployeeService.java
Normal file
183
src/main/java/com/petshop/backend/service/EmployeeService.java
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
package com.petshop.backend.service;
|
||||||
|
|
||||||
|
import com.petshop.backend.dto.employee.EmployeeRequest;
|
||||||
|
import com.petshop.backend.dto.employee.EmployeeResponse;
|
||||||
|
import com.petshop.backend.entity.Employee;
|
||||||
|
import com.petshop.backend.entity.User;
|
||||||
|
import com.petshop.backend.exception.ResourceNotFoundException;
|
||||||
|
import com.petshop.backend.repository.EmployeeRepository;
|
||||||
|
import com.petshop.backend.repository.UserRepository;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.web.server.ResponseStatusException;
|
||||||
|
|
||||||
|
import static org.springframework.http.HttpStatus.CONFLICT;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class EmployeeService {
|
||||||
|
private final EmployeeRepository employeeRepository;
|
||||||
|
private final UserRepository userRepository;
|
||||||
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
private final UserBusinessLinkageService userBusinessLinkageService;
|
||||||
|
|
||||||
|
public EmployeeService(EmployeeRepository employeeRepository, UserRepository userRepository, PasswordEncoder passwordEncoder, UserBusinessLinkageService userBusinessLinkageService) {
|
||||||
|
this.employeeRepository = employeeRepository;
|
||||||
|
this.userRepository = userRepository;
|
||||||
|
this.passwordEncoder = passwordEncoder;
|
||||||
|
this.userBusinessLinkageService = userBusinessLinkageService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Page<EmployeeResponse> getAllEmployees(String query, Pageable pageable) {
|
||||||
|
Page<Employee> employees;
|
||||||
|
if (query != null && !query.trim().isEmpty()) {
|
||||||
|
employees = employeeRepository.searchEmployees(query, pageable);
|
||||||
|
} else {
|
||||||
|
employees = employeeRepository.findAll(pageable);
|
||||||
|
}
|
||||||
|
return employees.map(this::mapToResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EmployeeResponse getEmployeeById(Long id) {
|
||||||
|
Employee employee = employeeRepository.findById(id)
|
||||||
|
.orElseThrow(() -> new ResourceNotFoundException("Employee not found with id: " + id));
|
||||||
|
return mapToResponse(employee);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public EmployeeResponse createEmployee(EmployeeRequest request) {
|
||||||
|
validateRole(request.getRole());
|
||||||
|
if (request.getPassword() == null || request.getPassword().trim().length() < 6) {
|
||||||
|
throw new IllegalArgumentException("Password must be at least 6 characters");
|
||||||
|
}
|
||||||
|
if (userRepository.findByUsername(request.getUsername()).isPresent()) {
|
||||||
|
throw new ResponseStatusException(CONFLICT, "Username already exists");
|
||||||
|
}
|
||||||
|
if (request.getEmail() != null && userRepository.findByEmail(request.getEmail()).isPresent()) {
|
||||||
|
throw new ResponseStatusException(CONFLICT, "Email already exists");
|
||||||
|
}
|
||||||
|
String phone = trimToNull(request.getPhone());
|
||||||
|
if (phone != null && userRepository.findByPhone(phone).isPresent()) {
|
||||||
|
throw new ResponseStatusException(CONFLICT, "Phone already exists");
|
||||||
|
}
|
||||||
|
|
||||||
|
User user = new User();
|
||||||
|
user.setUsername(request.getUsername());
|
||||||
|
user.setPassword(passwordEncoder.encode(request.getPassword()));
|
||||||
|
user.setFullName(fullName(request));
|
||||||
|
user.setEmail(request.getEmail());
|
||||||
|
user.setPhone(phone);
|
||||||
|
user.setRole(request.getRole());
|
||||||
|
user.setActive(request.getActive() != null ? request.getActive() : true);
|
||||||
|
user = userRepository.save(user);
|
||||||
|
|
||||||
|
Employee employee = userBusinessLinkageService.ensureLinkedEmployee(user);
|
||||||
|
return mapToResponse(employee, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public EmployeeResponse updateEmployee(Long id, EmployeeRequest request) {
|
||||||
|
Employee employee = employeeRepository.findById(id)
|
||||||
|
.orElseThrow(() -> new ResourceNotFoundException("Employee not found with id: " + id));
|
||||||
|
User user = requireLinkedUser(employee);
|
||||||
|
|
||||||
|
validateRole(request.getRole());
|
||||||
|
if (!user.getUsername().equals(request.getUsername()) && userRepository.findByUsername(request.getUsername()).isPresent()) {
|
||||||
|
throw new ResponseStatusException(CONFLICT, "Username already exists");
|
||||||
|
}
|
||||||
|
if (!java.util.Objects.equals(user.getEmail(), request.getEmail()) && request.getEmail() != null && userRepository.findByEmail(request.getEmail()).isPresent()) {
|
||||||
|
throw new ResponseStatusException(CONFLICT, "Email already exists");
|
||||||
|
}
|
||||||
|
String phone = trimToNull(request.getPhone());
|
||||||
|
Long currentUserId = user.getId();
|
||||||
|
if (!java.util.Objects.equals(user.getPhone(), phone)) {
|
||||||
|
userRepository.findByPhone(phone)
|
||||||
|
.filter(existing -> !existing.getId().equals(currentUserId))
|
||||||
|
.ifPresent(existing -> { throw new ResponseStatusException(CONFLICT, "Phone already exists"); });
|
||||||
|
}
|
||||||
|
|
||||||
|
user.setUsername(request.getUsername());
|
||||||
|
if (request.getPassword() != null && !request.getPassword().trim().isEmpty()) {
|
||||||
|
user.setPassword(passwordEncoder.encode(request.getPassword()));
|
||||||
|
user.setTokenVersion(user.getTokenVersion() + 1);
|
||||||
|
}
|
||||||
|
user.setEmail(request.getEmail());
|
||||||
|
user.setPhone(phone);
|
||||||
|
user.setFullName(fullName(request));
|
||||||
|
user.setRole(request.getRole());
|
||||||
|
user.setActive(request.getActive() != null ? request.getActive() : true);
|
||||||
|
user = userRepository.save(user);
|
||||||
|
|
||||||
|
employee = userBusinessLinkageService.ensureLinkedEmployee(user);
|
||||||
|
return mapToResponse(employee, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void deleteEmployee(Long id) {
|
||||||
|
Employee employee = employeeRepository.findById(id)
|
||||||
|
.orElseThrow(() -> new ResourceNotFoundException("Employee not found with id: " + id));
|
||||||
|
if (employee.getUserId() != null && userRepository.existsById(employee.getUserId())) {
|
||||||
|
userRepository.deleteById(employee.getUserId());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
employeeRepository.deleteById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private EmployeeResponse mapToResponse(Employee employee) {
|
||||||
|
User user = employee.getUserId() == null ? null : userRepository.findById(employee.getUserId()).orElse(null);
|
||||||
|
return mapToResponse(employee, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
private EmployeeResponse mapToResponse(Employee employee, User user) {
|
||||||
|
EmployeeResponse response = new EmployeeResponse();
|
||||||
|
response.setEmployeeId(employee.getEmployeeId());
|
||||||
|
response.setUserId(user != null ? user.getId() : employee.getUserId());
|
||||||
|
response.setUsername(user != null ? user.getUsername() : null);
|
||||||
|
response.setFirstName(employee.getFirstName());
|
||||||
|
response.setLastName(employee.getLastName());
|
||||||
|
response.setFullName(user != null ? user.getFullName() : fullName(employee));
|
||||||
|
response.setEmail(user != null ? user.getEmail() : employee.getEmail());
|
||||||
|
response.setPhone(user != null ? user.getPhone() : null);
|
||||||
|
response.setRole(user != null ? user.getRole().name() : normalizeRole(employee.getRole()));
|
||||||
|
response.setActive(user != null ? user.getActive() : employee.getIsActive());
|
||||||
|
response.setCreatedAt(employee.getCreatedAt());
|
||||||
|
response.setUpdatedAt(employee.getUpdatedAt());
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
private User requireLinkedUser(Employee employee) {
|
||||||
|
if (employee.getUserId() == null) {
|
||||||
|
throw new ResourceNotFoundException("Employee user account not found");
|
||||||
|
}
|
||||||
|
return userRepository.findById(employee.getUserId())
|
||||||
|
.orElseThrow(() -> new ResourceNotFoundException("Employee user account not found"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateRole(User.Role role) {
|
||||||
|
if (role != User.Role.STAFF && role != User.Role.ADMIN) {
|
||||||
|
throw new IllegalArgumentException("Employee role must be STAFF or ADMIN");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String fullName(EmployeeRequest request) {
|
||||||
|
return (request.getFirstName().trim() + " " + request.getLastName().trim()).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String fullName(Employee employee) {
|
||||||
|
return (employee.getFirstName().trim() + " " + employee.getLastName().trim()).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeRole(String role) {
|
||||||
|
return role == null ? null : role.trim().toUpperCase(java.util.Locale.ROOT);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String trimToNull(String value) {
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String trimmed = value.trim();
|
||||||
|
return trimmed.isEmpty() ? null : trimmed;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,88 +25,104 @@ public class UserBusinessLinkageService {
|
|||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public Employee ensureLinkedEmployee(User user) {
|
public Employee ensureLinkedEmployee(User user) {
|
||||||
// Check if already linked
|
|
||||||
if (user.getId() != null) {
|
if (user.getId() != null) {
|
||||||
var existing = employeeRepository.findByUserId(user.getId());
|
var existing = employeeRepository.findByUserId(user.getId());
|
||||||
if (existing.isPresent()) {
|
if (existing.isPresent()) {
|
||||||
return existing.get();
|
return syncEmployee(existing.get(), user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for email matches
|
|
||||||
List<Employee> emailMatches = employeeRepository.findAllByEmail(user.getEmail());
|
List<Employee> emailMatches = employeeRepository.findAllByEmail(user.getEmail());
|
||||||
|
|
||||||
// If exactly one match exists and has no userId, link it
|
|
||||||
if (emailMatches.size() == 1) {
|
if (emailMatches.size() == 1) {
|
||||||
Employee employee = emailMatches.get(0);
|
Employee employee = emailMatches.get(0);
|
||||||
if (employee.getUserId() == null) {
|
if (employee.getUserId() == null) {
|
||||||
employee.setUserId(user.getId());
|
employee.setUserId(user.getId());
|
||||||
return employeeRepository.save(employee);
|
return syncEmployee(employee, user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise create a new linked Employee
|
|
||||||
Employee newEmployee = new Employee();
|
Employee newEmployee = new Employee();
|
||||||
newEmployee.setUserId(user.getId());
|
newEmployee.setUserId(user.getId());
|
||||||
newEmployee.setEmail(user.getEmail());
|
newEmployee.setEmail(user.getEmail());
|
||||||
|
|
||||||
// Split fullName into firstName and lastName
|
|
||||||
String[] nameParts = splitFullName(user.getFullName());
|
String[] nameParts = splitFullName(user.getFullName());
|
||||||
newEmployee.setFirstName(nameParts[0]);
|
newEmployee.setFirstName(nameParts[0]);
|
||||||
newEmployee.setLastName(nameParts[1]);
|
newEmployee.setLastName(nameParts[1]);
|
||||||
|
|
||||||
// Set required fields with deterministic values
|
|
||||||
newEmployee.setPhone("000-000-0000");
|
|
||||||
newEmployee.setIsActive(true);
|
newEmployee.setIsActive(true);
|
||||||
|
|
||||||
// Map role based on user role
|
|
||||||
if (user.getRole() == User.Role.ADMIN) {
|
if (user.getRole() == User.Role.ADMIN) {
|
||||||
newEmployee.setRole("Manager");
|
newEmployee.setRole("Manager");
|
||||||
} else if (user.getRole() == User.Role.STAFF) {
|
} else if (user.getRole() == User.Role.STAFF) {
|
||||||
newEmployee.setRole("Staff");
|
newEmployee.setRole("Staff");
|
||||||
} else {
|
} else {
|
||||||
newEmployee.setRole("Staff"); // fallback
|
newEmployee.setRole("Staff");
|
||||||
}
|
}
|
||||||
|
|
||||||
return employeeRepository.save(newEmployee);
|
return syncEmployee(newEmployee, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public Customer ensureLinkedCustomer(User user) {
|
public Customer ensureLinkedCustomer(User user) {
|
||||||
// Check if already linked
|
|
||||||
if (user.getId() != null) {
|
if (user.getId() != null) {
|
||||||
var existing = customerRepository.findByUserId(user.getId());
|
var existing = customerRepository.findByUserId(user.getId());
|
||||||
if (existing.isPresent()) {
|
if (existing.isPresent()) {
|
||||||
return existing.get();
|
return syncCustomer(existing.get(), user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for email matches
|
|
||||||
List<Customer> emailMatches = customerRepository.findAllByEmail(user.getEmail());
|
List<Customer> emailMatches = customerRepository.findAllByEmail(user.getEmail());
|
||||||
|
|
||||||
// If exactly one match exists and has no userId, link it
|
|
||||||
if (emailMatches.size() == 1) {
|
if (emailMatches.size() == 1) {
|
||||||
Customer customer = emailMatches.get(0);
|
Customer customer = emailMatches.get(0);
|
||||||
if (customer.getUserId() == null) {
|
if (customer.getUserId() == null) {
|
||||||
customer.setUserId(user.getId());
|
customer.setUserId(user.getId());
|
||||||
return customerRepository.save(customer);
|
return syncCustomer(customer, user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise create a new linked Customer
|
|
||||||
Customer newCustomer = new Customer();
|
Customer newCustomer = new Customer();
|
||||||
newCustomer.setUserId(user.getId());
|
newCustomer.setUserId(user.getId());
|
||||||
newCustomer.setEmail(user.getEmail());
|
newCustomer.setEmail(user.getEmail());
|
||||||
|
|
||||||
// Split fullName into firstName and lastName
|
|
||||||
String[] nameParts = splitFullName(user.getFullName());
|
String[] nameParts = splitFullName(user.getFullName());
|
||||||
newCustomer.setFirstName(nameParts[0]);
|
newCustomer.setFirstName(nameParts[0]);
|
||||||
newCustomer.setLastName(nameParts[1]);
|
newCustomer.setLastName(nameParts[1]);
|
||||||
|
|
||||||
// Set required fields with deterministic values
|
return syncCustomer(newCustomer, user);
|
||||||
newCustomer.setPhone("000-000-0001");
|
}
|
||||||
|
|
||||||
return customerRepository.save(newCustomer);
|
@Transactional
|
||||||
|
public void syncLinkedRecords(User user) {
|
||||||
|
if (user.getRole() == User.Role.CUSTOMER) {
|
||||||
|
ensureLinkedCustomer(user);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ensureLinkedEmployee(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Employee syncEmployee(Employee employee, User user) {
|
||||||
|
employee.setUserId(user.getId());
|
||||||
|
employee.setEmail(user.getEmail());
|
||||||
|
String[] nameParts = splitFullName(user.getFullName());
|
||||||
|
employee.setFirstName(nameParts[0]);
|
||||||
|
employee.setLastName(nameParts[1]);
|
||||||
|
if (user.getRole() == User.Role.ADMIN) {
|
||||||
|
employee.setRole("Manager");
|
||||||
|
} else {
|
||||||
|
employee.setRole("Staff");
|
||||||
|
}
|
||||||
|
return employeeRepository.save(employee);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Customer syncCustomer(Customer customer, User user) {
|
||||||
|
customer.setUserId(user.getId());
|
||||||
|
customer.setEmail(user.getEmail());
|
||||||
|
String[] nameParts = splitFullName(user.getFullName());
|
||||||
|
customer.setFirstName(nameParts[0]);
|
||||||
|
customer.setLastName(nameParts[1]);
|
||||||
|
return customerRepository.save(customer);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String[] splitFullName(String fullName) {
|
private String[] splitFullName(String fullName) {
|
||||||
@@ -118,11 +134,9 @@ public class UserBusinessLinkageService {
|
|||||||
int spaceIndex = trimmed.indexOf(' ');
|
int spaceIndex = trimmed.indexOf(' ');
|
||||||
|
|
||||||
if (spaceIndex == -1) {
|
if (spaceIndex == -1) {
|
||||||
// Single token
|
|
||||||
return new String[]{trimmed, "User"};
|
return new String[]{trimmed, "User"};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Multiple tokens
|
|
||||||
String firstName = trimmed.substring(0, spaceIndex).trim();
|
String firstName = trimmed.substring(0, spaceIndex).trim();
|
||||||
String lastName = trimmed.substring(spaceIndex + 1).trim();
|
String lastName = trimmed.substring(spaceIndex + 1).trim();
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,12 @@ import org.springframework.data.domain.Pageable;
|
|||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.web.server.ResponseStatusException;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import static org.springframework.http.HttpStatus.BAD_REQUEST;
|
||||||
|
import static org.springframework.http.HttpStatus.CONFLICT;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class UserService {
|
public class UserService {
|
||||||
@@ -25,10 +31,16 @@ public class UserService {
|
|||||||
this.userBusinessLinkageService = userBusinessLinkageService;
|
this.userBusinessLinkageService = userBusinessLinkageService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Page<UserResponse> getAllUsers(String query, Pageable pageable) {
|
public Page<UserResponse> getAllUsers(String query, String role, Pageable pageable) {
|
||||||
|
User.Role parsedRole = parseRole(role);
|
||||||
Page<User> users;
|
Page<User> users;
|
||||||
if (query != null && !query.trim().isEmpty()) {
|
boolean hasQuery = query != null && !query.trim().isEmpty();
|
||||||
|
if (hasQuery && parsedRole != null) {
|
||||||
|
users = userRepository.searchUsersByRole(query, parsedRole, pageable);
|
||||||
|
} else if (hasQuery) {
|
||||||
users = userRepository.searchUsers(query, pageable);
|
users = userRepository.searchUsers(query, pageable);
|
||||||
|
} else if (parsedRole != null) {
|
||||||
|
users = userRepository.findByRole(parsedRole, pageable);
|
||||||
} else {
|
} else {
|
||||||
users = userRepository.findAll(pageable);
|
users = userRepository.findAll(pageable);
|
||||||
}
|
}
|
||||||
@@ -48,17 +60,15 @@ public class UserService {
|
|||||||
user.setPassword(passwordEncoder.encode(request.getPassword()));
|
user.setPassword(passwordEncoder.encode(request.getPassword()));
|
||||||
user.setFullName(request.getFullName());
|
user.setFullName(request.getFullName());
|
||||||
user.setEmail(request.getEmail());
|
user.setEmail(request.getEmail());
|
||||||
|
user.setPhone(trimToNull(request.getPhone()));
|
||||||
user.setRole(request.getRole());
|
user.setRole(request.getRole());
|
||||||
user.setActive(request.getActive() != null ? request.getActive() : true);
|
user.setActive(request.getActive() != null ? request.getActive() : true);
|
||||||
|
|
||||||
|
validateUniquePhone(user.getPhone(), null);
|
||||||
|
|
||||||
user = userRepository.save(user);
|
user = userRepository.save(user);
|
||||||
|
|
||||||
// Create or link business entity based on role
|
userBusinessLinkageService.syncLinkedRecords(user);
|
||||||
if (user.getRole() == User.Role.STAFF || user.getRole() == User.Role.ADMIN) {
|
|
||||||
userBusinessLinkageService.ensureLinkedEmployee(user);
|
|
||||||
} else if (user.getRole() == User.Role.CUSTOMER) {
|
|
||||||
userBusinessLinkageService.ensureLinkedCustomer(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
return mapToResponse(user);
|
return mapToResponse(user);
|
||||||
}
|
}
|
||||||
@@ -80,6 +90,11 @@ public class UserService {
|
|||||||
}
|
}
|
||||||
user.setFullName(request.getFullName());
|
user.setFullName(request.getFullName());
|
||||||
user.setEmail(request.getEmail());
|
user.setEmail(request.getEmail());
|
||||||
|
String phone = trimToNull(request.getPhone());
|
||||||
|
if (!java.util.Objects.equals(user.getPhone(), phone)) {
|
||||||
|
validateUniquePhone(phone, user.getId());
|
||||||
|
}
|
||||||
|
user.setPhone(phone);
|
||||||
user.setRole(request.getRole());
|
user.setRole(request.getRole());
|
||||||
user.setActive(request.getActive() != null ? request.getActive() : true);
|
user.setActive(request.getActive() != null ? request.getActive() : true);
|
||||||
if (invalidateToken) {
|
if (invalidateToken) {
|
||||||
@@ -87,6 +102,7 @@ public class UserService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
user = userRepository.save(user);
|
user = userRepository.save(user);
|
||||||
|
userBusinessLinkageService.syncLinkedRecords(user);
|
||||||
return mapToResponse(user);
|
return mapToResponse(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,10 +125,42 @@ public class UserService {
|
|||||||
response.setUsername(user.getUsername());
|
response.setUsername(user.getUsername());
|
||||||
response.setFullName(user.getFullName());
|
response.setFullName(user.getFullName());
|
||||||
response.setEmail(user.getEmail());
|
response.setEmail(user.getEmail());
|
||||||
|
response.setPhone(user.getPhone());
|
||||||
response.setRole(user.getRole().toString());
|
response.setRole(user.getRole().toString());
|
||||||
response.setActive(user.getActive());
|
response.setActive(user.getActive());
|
||||||
response.setCreatedAt(user.getCreatedAt());
|
response.setCreatedAt(user.getCreatedAt());
|
||||||
response.setUpdatedAt(user.getUpdatedAt());
|
response.setUpdatedAt(user.getUpdatedAt());
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void validateUniquePhone(String phone, Long currentUserId) {
|
||||||
|
if (phone == null || phone.isBlank()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
userRepository.findByPhone(phone)
|
||||||
|
.filter(existing -> !existing.getId().equals(currentUserId))
|
||||||
|
.ifPresent(existing -> {
|
||||||
|
throw new ResponseStatusException(CONFLICT, "Phone already exists");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private String trimToNull(String value) {
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String trimmed = value.trim();
|
||||||
|
return trimmed.isEmpty() ? null : trimmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private User.Role parseRole(String role) {
|
||||||
|
String normalizedRole = trimToNull(role);
|
||||||
|
if (normalizedRole == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return User.Role.valueOf(normalizedRole.toUpperCase(Locale.ROOT));
|
||||||
|
} catch (IllegalArgumentException ex) {
|
||||||
|
throw new ResponseStatusException(BAD_REQUEST, "Invalid value for parameter: role");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
8
src/main/resources/db/migration/V6__user_phone.sql
Normal file
8
src/main/resources/db/migration/V6__user_phone.sql
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
ALTER TABLE users
|
||||||
|
ADD COLUMN phone VARCHAR(20) NULL AFTER fullName;
|
||||||
|
|
||||||
|
UPDATE users u
|
||||||
|
LEFT JOIN customer c ON c.user_id = u.id
|
||||||
|
LEFT JOIN employee e ON e.user_id = u.id
|
||||||
|
SET u.phone = COALESCE(NULLIF(c.phone, ''), NULLIF(e.phone, ''), u.phone)
|
||||||
|
WHERE u.phone IS NULL OR u.phone = '';
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
UPDATE users u
|
||||||
|
LEFT JOIN customer c ON c.user_id = u.id
|
||||||
|
LEFT JOIN employee e ON e.user_id = u.id
|
||||||
|
SET u.phone = COALESCE(NULLIF(u.phone, ''), NULLIF(c.phone, ''), NULLIF(e.phone, ''))
|
||||||
|
WHERE u.phone IS NULL OR u.phone = '';
|
||||||
|
|
||||||
|
ALTER TABLE customer
|
||||||
|
DROP COLUMN phone;
|
||||||
|
|
||||||
|
ALTER TABLE employee
|
||||||
|
DROP COLUMN phone;
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.petshop.backend.security;
|
package com.petshop.backend.security;
|
||||||
|
|
||||||
import com.petshop.backend.entity.User;
|
import com.petshop.backend.entity.User;
|
||||||
|
import com.petshop.backend.exception.ApiErrorResponder;
|
||||||
import com.petshop.backend.repository.UserRepository;
|
import com.petshop.backend.repository.UserRepository;
|
||||||
import jakarta.servlet.FilterChain;
|
import jakarta.servlet.FilterChain;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
@@ -42,7 +43,7 @@ class JwtAuthenticationFilterTest {
|
|||||||
User user = buildUser();
|
User user = buildUser();
|
||||||
String token = jwtUtil.generateToken(user);
|
String token = jwtUtil.generateToken(user);
|
||||||
AtomicBoolean chainCalled = new AtomicBoolean(false);
|
AtomicBoolean chainCalled = new AtomicBoolean(false);
|
||||||
JwtAuthenticationFilter filter = new JwtAuthenticationFilter(jwtUtil, userRepositoryFor(user));
|
JwtAuthenticationFilter filter = new JwtAuthenticationFilter(jwtUtil, userRepositoryFor(user), new ApiErrorResponder());
|
||||||
|
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
request.addHeader("Authorization", "Bearer " + token);
|
request.addHeader("Authorization", "Bearer " + token);
|
||||||
@@ -63,7 +64,7 @@ class JwtAuthenticationFilterTest {
|
|||||||
User user = buildUser();
|
User user = buildUser();
|
||||||
user.setActive(false);
|
user.setActive(false);
|
||||||
String token = jwtUtil.generateToken(user);
|
String token = jwtUtil.generateToken(user);
|
||||||
JwtAuthenticationFilter filter = new JwtAuthenticationFilter(jwtUtil, userRepositoryFor(user));
|
JwtAuthenticationFilter filter = new JwtAuthenticationFilter(jwtUtil, userRepositoryFor(user), new ApiErrorResponder());
|
||||||
|
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
request.addHeader("Authorization", "Bearer " + token);
|
request.addHeader("Authorization", "Bearer " + token);
|
||||||
@@ -73,6 +74,8 @@ class JwtAuthenticationFilterTest {
|
|||||||
});
|
});
|
||||||
|
|
||||||
assertEquals(401, response.getStatus());
|
assertEquals(401, response.getStatus());
|
||||||
|
assertTrue(response.getContentAsString().contains("\"message\":\"User account is inactive\""));
|
||||||
|
assertTrue(response.getContentAsString().contains("\"path\":\"\""));
|
||||||
assertNull(SecurityContextHolder.getContext().getAuthentication());
|
assertNull(SecurityContextHolder.getContext().getAuthentication());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,7 +84,7 @@ class JwtAuthenticationFilterTest {
|
|||||||
User user = buildUser();
|
User user = buildUser();
|
||||||
String token = jwtUtil.generateToken(user);
|
String token = jwtUtil.generateToken(user);
|
||||||
user.setTokenVersion(4);
|
user.setTokenVersion(4);
|
||||||
JwtAuthenticationFilter filter = new JwtAuthenticationFilter(jwtUtil, userRepositoryFor(user));
|
JwtAuthenticationFilter filter = new JwtAuthenticationFilter(jwtUtil, userRepositoryFor(user), new ApiErrorResponder());
|
||||||
|
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
request.addHeader("Authorization", "Bearer " + token);
|
request.addHeader("Authorization", "Bearer " + token);
|
||||||
@@ -91,6 +94,7 @@ class JwtAuthenticationFilterTest {
|
|||||||
});
|
});
|
||||||
|
|
||||||
assertEquals(401, response.getStatus());
|
assertEquals(401, response.getStatus());
|
||||||
|
assertTrue(response.getContentAsString().contains("\"message\":\"Invalid or expired token\""));
|
||||||
assertNull(SecurityContextHolder.getContext().getAuthentication());
|
assertNull(SecurityContextHolder.getContext().getAuthentication());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user