diff --git a/api-gateway/src/main/java/com/petclinic/bffapigateway/domainclientlayer/AuthServiceClient.java b/api-gateway/src/main/java/com/petclinic/bffapigateway/domainclientlayer/AuthServiceClient.java index bd454dbb60..3c906ed10b 100644 --- a/api-gateway/src/main/java/com/petclinic/bffapigateway/domainclientlayer/AuthServiceClient.java +++ b/api-gateway/src/main/java/com/petclinic/bffapigateway/domainclientlayer/AuthServiceClient.java @@ -87,6 +87,16 @@ public Flux getUsers(String jwtToken) { .retrieve() .bodyToFlux(UserDetails.class); } + + public Mono deleteUser(String jwtToken, String userId) { + return webClientBuilder.build() + .delete() + .uri(authServiceUrl + "/users/{userId}", userId) + .cookie("Bearer", jwtToken) + .retrieve() + .bodyToMono(void.class); + } + //FUCK REACTIVE /* This shit is beyond cursed, but I do not care. This works, I only spent 6 HOURS OF MY LIFE. @@ -200,15 +210,6 @@ public Mono createVetUser(Mono model){ // .bodyToMono(UserDetails.class); // } // -// public Mono deleteUser(String auth, final long userId) { -// return webClientBuilder.build() -// .delete() -// .uri(authServiceUrl + "/users/{userId}", userId) -// .header("Authorization", auth) -// .retrieve() -// .bodyToMono(UserDetails.class); -// } - public Mono> verifyUser(final String token) { return webClientBuilder.build() diff --git a/api-gateway/src/main/java/com/petclinic/bffapigateway/presentationlayer/BFFApiGatewayController.java b/api-gateway/src/main/java/com/petclinic/bffapigateway/presentationlayer/BFFApiGatewayController.java index 68fc527953..3d312f97be 100644 --- a/api-gateway/src/main/java/com/petclinic/bffapigateway/presentationlayer/BFFApiGatewayController.java +++ b/api-gateway/src/main/java/com/petclinic/bffapigateway/presentationlayer/BFFApiGatewayController.java @@ -774,6 +774,13 @@ public Mono getUserById(@PathVariable String userId, @CookieValue(" return authServiceClient.getUserById(auth, userId); } + @SecuredEndpoint(allowedRoles = {Roles.ADMIN}) + @DeleteMapping(value = "users/{userId}", produces = MediaType.APPLICATION_JSON_VALUE) + public Mono> deleteUserById(@PathVariable String userId, @CookieValue("Bearer") String auth) { + return authServiceClient.deleteUser(auth, userId) + .then(Mono.just(ResponseEntity.noContent().build())) + .defaultIfEmpty(ResponseEntity.notFound().build()); + } @SecuredEndpoint(allowedRoles = {Roles.ANONYMOUS}) @PostMapping(value = "/users/login",produces = "application/json;charset=utf-8;", consumes = "application/json") diff --git a/api-gateway/src/main/resources/static/scripts/auth/admin-panel/admin-panel.controller.js b/api-gateway/src/main/resources/static/scripts/auth/admin-panel/admin-panel.controller.js index 9996ea15ed..44aaf458c5 100644 --- a/api-gateway/src/main/resources/static/scripts/auth/admin-panel/admin-panel.controller.js +++ b/api-gateway/src/main/resources/static/scripts/auth/admin-panel/admin-panel.controller.js @@ -1,6 +1,6 @@ 'use strict'; angular.module('adminPanel') - .controller('AdminPanelController', ['$http', '$scope', "authProvider", function ($http, $scope, authProvider) { + .controller('AdminPanelController', ['$http', '$scope', "authProvider", "$window", function ($http, $scope, authProvider, $window) { var self = this; self.users = [] @@ -20,11 +20,24 @@ angular.module('adminPanel') console.log("EventSource error: "+error) } } - - $scope.startsWith = function (actual, expected) { - let lowerStr = (actual + "").toLowerCase(); - let lowerExpected = (expected + "").toLowerCase(); - return lowerStr.indexOf(lowerExpected) === 0; + + $scope.search = function () { + if ($scope.query === '') { + $http.get('api/gateway/users', { + headers: {'Authorization': "Bearer " + authProvider.getUser().token} + }) + .then(function (resp) { + self.users = resp.data; + }); + } else { + $http.get('api/gateway/users', { + params: { username: $scope.query }, + headers: {'Authorization': "Bearer " + authProvider.getUser().token} + }) + .then(function (resp) { + self.users = resp.data; + }); + } }; @@ -32,12 +45,16 @@ angular.module('adminPanel') $http.delete('api/gateway/users/' + userid, { headers: {'Authorization': "Bearer " + authProvider.getUser().token}}) .then(function () { - $http.get('api/gateway/users', { - headers: {'Authorization': "Bearer " + authProvider.getUser().token}}) - .then(function (resp) { - self.users = resp.data; + $http.get('api/gateway/users', { + headers: {'Authorization': "Bearer " + authProvider.getUser().token}}) + .then(function (resp) { + self.users = resp.data; + alert("User has been deleted successfully."); + $window.location.reload(); + }); }); - }); }; + + } ]); diff --git a/api-gateway/src/main/resources/static/scripts/auth/admin-panel/admin-panel.template.html b/api-gateway/src/main/resources/static/scripts/auth/admin-panel/admin-panel.template.html index 819dd96002..9724f37d14 100644 --- a/api-gateway/src/main/resources/static/scripts/auth/admin-panel/admin-panel.template.html +++ b/api-gateway/src/main/resources/static/scripts/auth/admin-panel/admin-panel.template.html @@ -16,7 +16,7 @@

Users

- + {{ user.username }} @@ -24,7 +24,7 @@

Users

{{user.email}} - + diff --git a/api-gateway/src/main/resources/static/scripts/auth/user-details/user-details.controller.js b/api-gateway/src/main/resources/static/scripts/auth/user-details/user-details.controller.js index 08f774f597..b0912e0779 100644 --- a/api-gateway/src/main/resources/static/scripts/auth/user-details/user-details.controller.js +++ b/api-gateway/src/main/resources/static/scripts/auth/user-details/user-details.controller.js @@ -1,7 +1,7 @@ 'use strict'; angular.module('userDetails') - .controller('UserDetailsController', ['$http', '$stateParams', function ($http, $stateParams, $scope) { + .controller('UserDetailsController', ['$http', '$stateParams', '$location', function ($http, $stateParams, $location) { let self = this; self.userId = $stateParams.userId; @@ -14,5 +14,8 @@ angular.module('userDetails') .catch(function (error) { $scope.errorMessages = n.data.message.split`\n`; }); - }]); + self.goToAdminPanel = function() { + $location.path("/adminPanel"); + } + }]); diff --git a/api-gateway/src/main/resources/static/scripts/auth/user-details/user-details.template.html b/api-gateway/src/main/resources/static/scripts/auth/user-details/user-details.template.html index 5ccf5b22b7..107a5aa0f3 100644 --- a/api-gateway/src/main/resources/static/scripts/auth/user-details/user-details.template.html +++ b/api-gateway/src/main/resources/static/scripts/auth/user-details/user-details.template.html @@ -4,39 +4,35 @@ Title - -
-

User Details for ID: {{ userDetails.userId }}

-

Username: {{ userDetails.user ? userDetails.user.username : 'N/A' }}

-

Email: {{ userDetails.user ? userDetails.user.email : 'N/A' }}

-

Roles: {{ userDetails.user ? userDetails.user.roles : 'N/A' }}

+ +
+

User Details for ID: {{ userDetails.userId }}

+

Username: {{ userDetails.user ? userDetails.user.username : 'N/A' }}

+

Email: {{ userDetails.user ? userDetails.user.email : 'N/A' }}

+

Roles: + + {{role.name}} + , + +

+ +
- + + - - - - - - - - - - - - + diff --git a/api-gateway/src/test/java/com/petclinic/bffapigateway/domainclientlayer/AuthServiceClientIntegrationTest.java b/api-gateway/src/test/java/com/petclinic/bffapigateway/domainclientlayer/AuthServiceClientIntegrationTest.java index 1468a79696..40e9ec595d 100644 --- a/api-gateway/src/test/java/com/petclinic/bffapigateway/domainclientlayer/AuthServiceClientIntegrationTest.java +++ b/api-gateway/src/test/java/com/petclinic/bffapigateway/domainclientlayer/AuthServiceClientIntegrationTest.java @@ -534,4 +534,50 @@ void ShouldVerifyUserToken_ShouldReturnInvalid(){ .expectNextCount(0) .verifyError(); } + + @Test + @DisplayName("Should create a vet user") + void shouldCreateVetUser() throws IOException { + // Arrange + RegisterVet registerVet = RegisterVet.builder() + .username("username") + .password("password") + .email("email") + .build(); + Mono registerVetMono = Mono.just(registerVet); + + // Set up the MockWebServer to return a specific response + final MockResponse mockResponse = new MockResponse(); + mockResponse + .setHeader("Content-Type", "application/json") + .setResponseCode(200); + server.enqueue(mockResponse); + + // Act + Mono result = authServiceClient.createVetUser(registerVetMono); + + // Assert + StepVerifier.create(result) + .expectNextCount(0) + .verifyComplete(); + } + + @Test + void deleteUser_ShouldReturnOk() throws Exception { + final MockResponse mockResponse = new MockResponse(); + mockResponse + .setResponseCode(200); + + server.enqueue(mockResponse); + + String jwtToken = "jwtToken"; + String userId = "userId"; + + final Mono validatedTokenResponse = authServiceClient.deleteUser(jwtToken, userId); + + // check status response in step verifier + StepVerifier.create(Mono.just(validatedTokenResponse)) + .expectNextCount(1) + .verifyComplete(); + } } diff --git a/api-gateway/src/test/java/com/petclinic/bffapigateway/presentationlayer/ApiGatewayControllerTest.java b/api-gateway/src/test/java/com/petclinic/bffapigateway/presentationlayer/ApiGatewayControllerTest.java index 2985f97e98..504630c6f1 100755 --- a/api-gateway/src/test/java/com/petclinic/bffapigateway/presentationlayer/ApiGatewayControllerTest.java +++ b/api-gateway/src/test/java/com/petclinic/bffapigateway/presentationlayer/ApiGatewayControllerTest.java @@ -3708,6 +3708,26 @@ public void getUserById_ValidUserId_ShouldReturnUser() { assertEquals(userDetails.getEmail(), u.getEmail()); }); } + + @Test + void deleteUserById_ValidUserId_ShouldDeleteUser() { + UserDetails userDetails = UserDetails.builder() + .userId("validUserId") + .username("validUsername") + .email("validEmail") + .build(); + + when(authServiceClient.deleteUser(anyString(), anyString())) + .thenReturn(Mono.empty()); + + client.delete() + .uri("/api/gateway/users/validUserId") + .cookie("Bearer", "validToken") + .exchange() + .expectStatus().isNoContent(); + } + + private EducationResponseDTO buildEducation(){ return EducationResponseDTO.builder() .educationId("1") diff --git a/auth-service/src/main/java/com/petclinic/authservice/businesslayer/UserService.java b/auth-service/src/main/java/com/petclinic/authservice/businesslayer/UserService.java index 86c633ba4e..8c391b806e 100644 --- a/auth-service/src/main/java/com/petclinic/authservice/businesslayer/UserService.java +++ b/auth-service/src/main/java/com/petclinic/authservice/businesslayer/UserService.java @@ -38,24 +38,21 @@ public interface UserService { User createUser(UserIDLessRoleLessDTO user); - - List findAllWithoutPage(); - //void deleteUser(long id); - Mail generateVerificationMail(User user); UserPasswordLessDTO verifyEmailFromToken(String token); HashMap login(UserIDLessUsernameLessDTO user) throws IncorrectPasswordException; - User getUserByEmail(String email) throws NotFoundException; - User getUserByUserId(String userIid); - List getUsersByUsernameContaining(String username); + User getUserByUserId(String userId); + + void deleteUser(String userId); + List getUsersByUsernameContaining(String username); void processForgotPassword(UserResetPwdRequestModel userResetPwdWithTokenRequestModel); diff --git a/auth-service/src/main/java/com/petclinic/authservice/businesslayer/UserServiceImpl.java b/auth-service/src/main/java/com/petclinic/authservice/businesslayer/UserServiceImpl.java index 5cad2a5a11..858fe9f579 100644 --- a/auth-service/src/main/java/com/petclinic/authservice/businesslayer/UserServiceImpl.java +++ b/auth-service/src/main/java/com/petclinic/authservice/businesslayer/UserServiceImpl.java @@ -82,9 +82,6 @@ public User createUser(@Valid UserIDLessRoleLessDTO userIDLessDTO) { format("User with username %s already exists", userIDLessDTO.getUsername())); } - -// add exception when trying to create a user with existing username - User user = userMapper.idLessRoleLessDTOToModel(userIDLessDTO); if (userIDLessDTO.getDefaultRole() == null|| userIDLessDTO.getDefaultRole().isEmpty()){ @@ -446,6 +443,17 @@ public User getUserByUserId(String userId) { .orElseThrow(() -> new NotFoundException("No user with userId: " + userId)); } + @Override + public void deleteUser(String userId) { + User user = userRepo.findUserByUserIdentifier_UserId(userId); + if (user != null) { + userRepo.delete(user); + } else { + throw new NotFoundException("No user with userId: " + userId); + } + } + + @Override public List getUsersByUsernameContaining(String username) { return userMapper.modelToDetailsList(userRepo.findByUsernameContaining(username)); diff --git a/auth-service/src/main/java/com/petclinic/authservice/presentationlayer/User/UserController.java b/auth-service/src/main/java/com/petclinic/authservice/presentationlayer/User/UserController.java index e95bef9f5c..34baa70bad 100644 --- a/auth-service/src/main/java/com/petclinic/authservice/presentationlayer/User/UserController.java +++ b/auth-service/src/main/java/com/petclinic/authservice/presentationlayer/User/UserController.java @@ -35,6 +35,7 @@ import org.springframework.security.authentication.BadCredentialsException; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import reactor.core.publisher.Mono; import java.util.Base64; import java.util.HashMap; @@ -177,6 +178,15 @@ public ResponseEntity processResetPassword(@RequestBody @Valid UserResetPw return ResponseEntity.ok().build(); } + @DeleteMapping("/{userId}") + public ResponseEntity deleteUser(@PathVariable String userId) { + + userService.deleteUser(userId); + + return ResponseEntity.noContent().build(); + } + + private boolean isValidBase64(String s) { try { Base64.getDecoder().decode(s); diff --git a/auth-service/src/test/java/com/petclinic/authservice/businesslayer/AuthServiceUserServiceTests.java b/auth-service/src/test/java/com/petclinic/authservice/businesslayer/AuthServiceUserServiceTests.java index 10bef0a170..2a9c0f20ca 100644 --- a/auth-service/src/test/java/com/petclinic/authservice/businesslayer/AuthServiceUserServiceTests.java +++ b/auth-service/src/test/java/com/petclinic/authservice/businesslayer/AuthServiceUserServiceTests.java @@ -283,6 +283,42 @@ void updatePassword_ShouldThrowNotFoundExceptionForNonExistingUser() { verify(userRepo).findById(validToken.getUserIdentifier()); } + @Test + @DisplayName("Delete user, should succeed") + void deleteUser_ShouldSucceed() { + // Arrange + User user = User.builder() + .username(USER) + .userIdentifier(new UserIdentifier()) + .email(EMAIL) + .password(passwordEncoder.encode(PASS)) + .verified(true) + .build(); + userRepo.save(user); + + when(userRepo.findUserByUserIdentifier_UserId(any())) + .thenReturn(user); + + // Act + userService.deleteUser(user.getUserIdentifier().getUserId()); + + // Assert + verify(userRepo, times(1)).delete(user); + } + + @Test + @DisplayName("Delete user, should throw NotFoundException") + void deleteUser_ShouldThrowNotFoundException() { + // Arrange + String nonExistentUserId = "nonExistentUserId"; + + when(userRepo.findUserByUserIdentifier_UserId(nonExistentUserId)) + .thenReturn(null); + + // Act and Assert + assertThrows(NotFoundException.class, () -> userService.deleteUser(nonExistentUserId)); + } + // Next Story // @Test diff --git a/auth-service/src/test/java/com/petclinic/authservice/presentationlayer/User/UserControllerIntegrationTest.java b/auth-service/src/test/java/com/petclinic/authservice/presentationlayer/User/UserControllerIntegrationTest.java index 04bb3b4c67..455d4cd37d 100644 --- a/auth-service/src/test/java/com/petclinic/authservice/presentationlayer/User/UserControllerIntegrationTest.java +++ b/auth-service/src/test/java/com/petclinic/authservice/presentationlayer/User/UserControllerIntegrationTest.java @@ -7,6 +7,7 @@ import com.petclinic.authservice.datalayer.user.*; import org.aspectj.lang.annotation.Before; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; @@ -58,6 +59,8 @@ class UserControllerIntegrationTest { private final String VALID_USER_ID = "7c0d42c2-0c2d-41ce-bd9c-6ca67478956f"; + private final String VALID_USER_ID2 = "f470653d-05c5-4c45-b7a0-7d70f003d2ac"; + @Before("setup") public void setup() { String baseUri = "http://localhost:" + "9200"; @@ -543,7 +546,7 @@ void getAllUsers_ShouldSucceed(){ .expectStatus().isOk() .expectBodyList(UserDetails.class) .value(users -> { - assertEquals(19,users.size()); + assertEquals(18,users.size()); }); } @Test @@ -610,10 +613,38 @@ public void getAllUsersWithoutUsernameParam_ShouldReturnAllUsers() { .expectStatus().isOk() .expectBodyList(UserDetails.class) .value(users -> { - assertEquals(19,users.size()); + assertEquals(18,users.size()); }); } + @Test + void deleteUser_ShouldSucceed(){ + String token = jwtTokenUtil.generateToken(userRepo.findAll().get(0)); + + webTestClient.delete() + .uri("/users/{userId}" , VALID_USER_ID2) + .accept(MediaType.APPLICATION_JSON) + .cookie("Bearer",token) + .exchange() + .expectStatus().isNoContent(); + + assertNull(userRepo.findUserByUserIdentifier_UserId(VALID_USER_ID2)); + } + + @Test + void deleteUser_ShouldThrowNotFoundException(){ + String token = jwtTokenUtil.generateToken(userRepo.findAll().get(0)); + String nonExistingUserId = "nonExistingUserId"; + + webTestClient.delete() + .uri("/users/{userId}" , nonExistingUserId) + .accept(MediaType.APPLICATION_JSON) + .cookie("Bearer",token) + .exchange() + .expectStatus().isNotFound(); + } + + @Test void updateUserRole_validUserId() { RolesChangeRequestDTO updatedUser = RolesChangeRequestDTO.builder()