Skip to content

Commit ae322f3

Browse files
authored
Merge pull request #20 from OpenLMIS/OLMIS-8065
OLMIS-8065: Added batch APIs needed for user import functionality
2 parents fd245dd + 14e9d49 commit ae322f3

File tree

9 files changed

+481
-162
lines changed

9 files changed

+481
-162
lines changed

package-lock.json

Lines changed: 178 additions & 160 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/integration-test/java/org/openlmis/auth/web/UserControllerIntegrationTest.java

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
import static org.mockito.BDDMockito.willThrow;
3333
import static org.mockito.Mockito.doAnswer;
3434
import static org.mockito.Mockito.doThrow;
35+
import static org.mockito.Mockito.verify;
36+
import static org.mockito.Mockito.when;
3537
import static org.openlmis.auth.i18n.MessageKeys.ERROR_NO_FOLLOWING_PERMISSION;
3638
import static org.openlmis.auth.i18n.MessageKeys.USER_NOT_FOUND;
3739
import static org.openlmis.auth.service.ExpirationTokenNotifier.TOKEN_VALIDITY_HOURS;
@@ -42,6 +44,10 @@
4244
import com.jayway.restassured.response.ValidatableResponse;
4345
import guru.nidi.ramltester.junit.RamlMatchers;
4446
import java.time.ZonedDateTime;
47+
import java.util.Arrays;
48+
import java.util.HashSet;
49+
import java.util.List;
50+
import java.util.Set;
4551
import java.util.UUID;
4652
import org.junit.Before;
4753
import org.junit.Test;
@@ -51,6 +57,7 @@
5157
import org.openlmis.auth.domain.PasswordResetToken;
5258
import org.openlmis.auth.domain.User;
5359
import org.openlmis.auth.dto.PasswordResetRequestDto;
60+
import org.openlmis.auth.dto.UserAuthDetailsResponseDto;
5461
import org.openlmis.auth.dto.UserDto;
5562
import org.openlmis.auth.dto.referencedata.UserMainDetailsDto;
5663
import org.openlmis.auth.exception.PermissionMessageException;
@@ -78,6 +85,7 @@
7885
public class UserControllerIntegrationTest extends BaseWebIntegrationTest {
7986

8087
private static final String RESOURCE_URL = "/api/users/auth";
88+
private static final String BATCH_RESOURCE_URL = RESOURCE_URL + "/batch";
8189
private static final String ID_URL = RESOURCE_URL + "/{id}";
8290
private static final String RESET_PASS_URL = RESOURCE_URL + "/passwordReset";
8391
private static final String FORGOT_PASS_URL = RESOURCE_URL + "/forgotPassword";
@@ -365,7 +373,93 @@ public void shouldNotCreatePasswordResetTokenWhenUserHasNoPermission() {
365373
.statusCode(403)
366374
.body(Fields.MESSAGE, equalTo(getMessage(ex.asMessage())));
367375
}
368-
376+
377+
@Test
378+
public void shouldSaveUsers() {
379+
UserDto user1 = new UserDto();
380+
user1.setId(UUID.randomUUID());
381+
user1.setUsername("john1");
382+
383+
UserDto user2 = new UserDto();
384+
user2.setId(UUID.randomUUID());
385+
user2.setUsername("john2");
386+
387+
when(userReferenceDataService.findOne(any())).thenReturn(new UserMainDetailsDto());
388+
389+
List<UserDto> requestBody = Arrays.asList(user1, user2);
390+
391+
UserAuthDetailsResponseDto response = startRequest(USER_TOKEN)
392+
.header("Content-Type", "application/json")
393+
.body(requestBody)
394+
.given()
395+
.post(BATCH_RESOURCE_URL)
396+
.then()
397+
.statusCode(200)
398+
.extract()
399+
.as(UserAuthDetailsResponseDto.class);
400+
401+
verify(permissionService).canManageUsers(any());
402+
assertEquals(2, response.getSuccessfulResults().size());
403+
assertEquals(0, response.getFailedResults().size());
404+
}
405+
406+
@Test
407+
public void shouldNotSaveUsersWithEmptyUsername() {
408+
UserDto user1 = new UserDto();
409+
user1.setId(UUID.randomUUID());
410+
user1.setUsername("john3");
411+
userRepository.save(User.newInstance(user1));
412+
413+
UserDto user2 = new UserDto();
414+
user2.setId(UUID.randomUUID());
415+
user2.setUsername("john4");
416+
userRepository.save(User.newInstance(user2));
417+
418+
when(userReferenceDataService.findOne(any())).thenReturn(new UserMainDetailsDto());
419+
420+
User savedUser1 = userRepository.findOneByUsernameIgnoreCase("john3");
421+
savedUser1.setUsername(null);
422+
User savedUser2 = userRepository.findOneByUsernameIgnoreCase("john4");
423+
424+
List<UserDto> requestBody = Arrays.asList(convertToDto(savedUser1), convertToDto(savedUser2));
425+
426+
UserAuthDetailsResponseDto response = startRequest(USER_TOKEN)
427+
.header("Content-Type", "application/json")
428+
.body(requestBody)
429+
.given()
430+
.post(BATCH_RESOURCE_URL)
431+
.then()
432+
.statusCode(200)
433+
.extract()
434+
.as(UserAuthDetailsResponseDto.class);
435+
436+
verify(permissionService).canManageUsers(any());
437+
assertEquals(1, response.getSuccessfulResults().size());
438+
assertEquals(1, response.getFailedResults().size());
439+
}
440+
441+
@Test
442+
public void shouldDeleteAuthDetails() {
443+
User userToCreate = new User();
444+
userToCreate.setUsername("john");
445+
User savedUser = userRepository.save(userToCreate);
446+
447+
User user = userRepository.findOneByUsernameIgnoreCase("john");
448+
assertNotNull(user);
449+
450+
Set<UUID> idsToRemove = new HashSet<>();
451+
idsToRemove.add(savedUser.getId());
452+
453+
startRequest(USER_TOKEN)
454+
.header("Content-Type", "application/json")
455+
.body(idsToRemove)
456+
.given()
457+
.delete(BATCH_RESOURCE_URL);
458+
459+
User userAfterRemoving = userRepository.findOneByUsernameIgnoreCase("john");
460+
assertNull(userAfterRemoving);
461+
}
462+
369463
private ValidatableResponse passwordReset(String password, String token) {
370464
return passwordReset(DummyUserMainDetailsDto.USERNAME, password, token);
371465
}
@@ -443,4 +537,11 @@ private Object mockBindingResults(InvocationOnMock invocation) {
443537
+ "be between 8 and 16.");
444538
return bindingResult;
445539
}
540+
541+
private UserDto convertToDto(User user) {
542+
UserDto dto = new UserDto();
543+
dto.setId(user.getId());
544+
dto.setUsername(user.getUsername());
545+
return dto;
546+
}
446547
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* This program is part of the OpenLMIS logistics management information system platform software.
3+
* Copyright © 2017 VillageReach
4+
*
5+
* This program is free software: you can redistribute it and/or modify it under the terms
6+
* of the GNU Affero General Public License as published by the Free Software Foundation, either
7+
* version 3 of the License, or (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
10+
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11+
* See the GNU Affero General Public License for more details. You should have received a copy of
12+
* the GNU Affero General Public License along with this program. If not, see
13+
* http://www.gnu.org/licenses.  For additional information contact info@OpenLMIS.org.
14+
*/
15+
16+
package org.openlmis.auth.dto;
17+
18+
import java.util.List;
19+
import lombok.AllArgsConstructor;
20+
import lombok.Getter;
21+
import lombok.NoArgsConstructor;
22+
23+
@NoArgsConstructor
24+
@AllArgsConstructor
25+
@Getter
26+
public class SaveBatchResultDto<T> {
27+
private List<T> successfulResults;
28+
private List<T> failedResults;
29+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* This program is part of the OpenLMIS logistics management information system platform software.
3+
* Copyright © 2017 VillageReach
4+
*
5+
* This program is free software: you can redistribute it and/or modify it under the terms
6+
* of the GNU Affero General Public License as published by the Free Software Foundation, either
7+
* version 3 of the License, or (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
10+
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11+
* See the GNU Affero General Public License for more details. You should have received a copy of
12+
* the GNU Affero General Public License along with this program. If not, see
13+
* http://www.gnu.org/licenses.  For additional information contact info@OpenLMIS.org.
14+
*/
15+
16+
package org.openlmis.auth.dto;
17+
18+
import java.util.List;
19+
import java.util.UUID;
20+
21+
import lombok.AllArgsConstructor;
22+
import lombok.Getter;
23+
import lombok.NoArgsConstructor;
24+
import lombok.Setter;
25+
26+
@NoArgsConstructor
27+
@AllArgsConstructor
28+
@Getter
29+
@Setter
30+
public class UserAuthDetailsResponseDto {
31+
private List<UserAuthResponse> successfulResults;
32+
33+
private List<UserAuthResponse> failedResults;
34+
35+
@NoArgsConstructor
36+
@AllArgsConstructor
37+
@Getter
38+
@Setter
39+
public static class UserAuthResponse {
40+
private UUID referenceDataUserId;
41+
}
42+
43+
@NoArgsConstructor
44+
@AllArgsConstructor
45+
@Getter
46+
@Setter
47+
public static class FailedUserDetailsResponse extends UserAuthResponse {
48+
private List<String> errors;
49+
50+
public FailedUserDetailsResponse(UUID referenceDataUserId, List<String> errors) {
51+
super(referenceDataUserId);
52+
this.errors = errors;
53+
}
54+
}
55+
}

src/main/java/org/openlmis/auth/i18n/MessageKeys.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@ public abstract class MessageKeys {
8383
public static final String ERROR_SIZE_NOT_POSITIVE =
8484
ERROR_PREFIX + ".pageable.size.notPositive";
8585

86+
public static final String ERROR_SAVING_BATCH_AUTH_DETAILS =
87+
ERROR_PREFIX + ".save.batch.auth.details";
88+
8689
private MessageKeys() {
8790
throw new UnsupportedOperationException();
8891
}

src/main/java/org/openlmis/auth/repository/UserRepository.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,21 @@
1515

1616
package org.openlmis.auth.repository;
1717

18+
import java.util.Set;
1819
import java.util.UUID;
1920
import org.openlmis.auth.domain.User;
21+
import org.springframework.data.jpa.repository.Modifying;
22+
import org.springframework.data.jpa.repository.Query;
2023
import org.springframework.data.repository.CrudRepository;
2124
import org.springframework.data.repository.query.Param;
2225

2326
public interface UserRepository extends CrudRepository<User, UUID> {
2427

2528
User findOneByUsernameIgnoreCase(@Param("username") String username);
2629

30+
@Modifying
31+
@Query(value = "DELETE FROM auth.auth_users au "
32+
+ "WHERE au.id IN (:userIds)",
33+
nativeQuery = true)
34+
void deleteByUserIds(@Param("userIds") Set<UUID> userIds);
2735
}

src/main/java/org/openlmis/auth/service/UserService.java

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,36 @@
1515

1616
package org.openlmis.auth.service;
1717

18+
import java.util.ArrayList;
19+
import java.util.Collections;
20+
import java.util.List;
1821
import java.util.Optional;
22+
import java.util.Set;
23+
import java.util.UUID;
24+
import java.util.stream.Collectors;
1925
import org.openlmis.auth.domain.User;
26+
import org.openlmis.auth.dto.SaveBatchResultDto;
27+
import org.openlmis.auth.dto.UserAuthDetailsResponseDto;
2028
import org.openlmis.auth.dto.UserDto;
29+
import org.openlmis.auth.i18n.MessageKeys;
2130
import org.openlmis.auth.repository.UserRepository;
31+
import org.openlmis.auth.web.UserDtoValidator;
2232
import org.springframework.beans.factory.annotation.Autowired;
33+
import org.springframework.context.support.DefaultMessageSourceResolvable;
2334
import org.springframework.stereotype.Service;
35+
import org.springframework.transaction.annotation.Transactional;
36+
import org.springframework.validation.BeanPropertyBindingResult;
37+
import org.springframework.validation.BindingResult;
2438

2539
@Service
2640
public class UserService {
2741

2842
@Autowired
2943
private UserRepository userRepository;
3044

45+
@Autowired
46+
private UserDtoValidator userDtoValidator;
47+
3148
/**
3249
* Creates a new user or updates an existing one.
3350
*
@@ -48,6 +65,51 @@ public UserDto saveUser(UserDto request) {
4865
return toDto(userRepository.save(savedUser));
4966
}
5067

68+
/**
69+
* Saves auth user details.
70+
*
71+
* @param userDto user contact details object
72+
* @return {@link SaveBatchResultDto} object with saving results
73+
*/
74+
public SaveBatchResultDto<UserAuthDetailsResponseDto.UserAuthResponse> saveAuthUserDetails(
75+
UserDto userDto) {
76+
List<UserAuthDetailsResponseDto.UserAuthResponse> successfulResults = new ArrayList<>();
77+
List<UserAuthDetailsResponseDto.UserAuthResponse> failedResults = new ArrayList<>();
78+
try {
79+
BindingResult bindingResult = new BeanPropertyBindingResult(userDto, "userDto");
80+
userDtoValidator.validate(userDto, bindingResult);
81+
List<String> errors;
82+
if (bindingResult.hasErrors()) {
83+
errors = bindingResult.getAllErrors().stream()
84+
.map(DefaultMessageSourceResolvable::getDefaultMessage)
85+
.collect(Collectors.toList());
86+
failedResults.add(
87+
new UserAuthDetailsResponseDto.FailedUserDetailsResponse(userDto.getId(), errors));
88+
} else {
89+
saveUser(userDto);
90+
successfulResults.add(new UserAuthDetailsResponseDto.UserAuthResponse(userDto.getId()));
91+
}
92+
} catch (Exception ex) {
93+
String errorMessage = String.format("%s: %s", MessageKeys.ERROR_SAVING_BATCH_AUTH_DETAILS,
94+
ex.getMessage());
95+
failedResults.add(new UserAuthDetailsResponseDto.FailedUserDetailsResponse(
96+
userDto.getId(),
97+
Collections.singletonList(errorMessage)));
98+
}
99+
100+
return new SaveBatchResultDto<>(successfulResults, failedResults);
101+
}
102+
103+
/**
104+
* Deletes user auth details.
105+
*
106+
* @param userIds user ids for whom auth details will be removed
107+
*/
108+
@Transactional
109+
public void deleteByUserIds(Set<UUID> userIds) {
110+
userRepository.deleteByUserIds(userIds);
111+
}
112+
51113
private UserDto toDto(User existing) {
52114
UserDto dto = new UserDto();
53115
existing.export(dto);

0 commit comments

Comments
 (0)