Skip to content
Snippets Groups Projects
Commit 67794159 authored by OZGCloud's avatar OZGCloud
Browse files

OZG-5031 OZG-6794 Implement grpc interface to provide list of user profiles...

OZG-5031 OZG-6794 Implement grpc interface to provide list of user profiles including user settings belonging to Organisationseinheit
parent dbca6c25
No related branches found
No related tags found
No related merge requests found
Showing
with 295 additions and 32 deletions
{
"java.configuration.updateBuildConfiguration": "automatic",
"java.debug.settings.onBuildFailureProceed": true
}
\ No newline at end of file
......@@ -30,6 +30,7 @@ option java_package = "de.ozgcloud.user.recipient";
option java_outer_classname = "RecipientModelProto";
message GrpcRecipient {
option deprecated = true;
string firstName = 1;
string lastName = 2;
string email = 3;
......
......@@ -32,13 +32,17 @@ option java_package = "de.ozgcloud.user.grpc.recipient";
option java_outer_classname = "RecipientProto";
service RecipientService {
rpc findRecipientByOrganisationsEinheitId(GrpcFindRecipientRequest) returns (GrpcFindRecipientResponse);
rpc findRecipientByOrganisationsEinheitId(GrpcFindRecipientRequest) returns (GrpcFindRecipientResponse){
option deprecated = true;
};
}
message GrpcFindRecipientRequest {
option deprecated = true;
string organisationsEinheitId = 1;
}
message GrpcFindRecipientResponse {
option deprecated = true;
repeated GrpcRecipient recipient = 1;
}
\ No newline at end of file
......@@ -34,6 +34,15 @@ message GrpcUserProfile {
string firstName = 2;
string lastName = 3;
string email = 4;
GrpcUserSettings userSettings = 5;
}
message GrpcUserSettings {
bool notificationsSendForAll = 1;
bool vorgangCreated = 2;
bool vorgangAssignedToUser = 3;
bool postfachNachrichtFromAntragsteller = 4;
bool wiedervorlageOverdue = 5;
}
......@@ -45,6 +54,14 @@ message GrpcGetUserProfileResponse {
GrpcUserProfile userProfile = 1;
}
message GrpcGetAllByOrganisationsEinheitIdRequest{
string organisationsEinheitId = 1;
}
message GrpcGetAllByOrganisationsEinheitIdResponse{
repeated GrpcUserProfile userProfile = 1;
}
message GrpcFindInactiveUserIdsResponse {
repeated GrpcUserProfileId userProfileIds = 1;
}
......
......@@ -37,6 +37,8 @@ service UserProfileService {
rpc GetByExternalId(GrpcGetUserProfileRequest) returns (GrpcGetUserProfileResponse);
rpc GetAllByOrganisationsEinheitId(GrpcGetAllByOrganisationsEinheitIdRequest) returns (GrpcGetAllByOrganisationsEinheitIdResponse);
rpc FindInactiveUserIds(GrpcFindInactiveUserIdsRequest) returns (GrpcFindInactiveUserIdsResponse);
rpc DeleteInactiveUser(GrpcDeleteInactiveUserRequest) returns (GrpcDeleteInactiveUserResponse);
......
......@@ -28,13 +28,14 @@ import static de.ozgcloud.user.User.*;
import java.util.Optional;
import java.util.stream.Stream;
import jakarta.enterprise.context.ApplicationScoped;
import org.bson.types.ObjectId;
import de.ozgcloud.common.logging.OzgCloudLogging;
import de.ozgcloud.user.common.errorhandling.ResourceNotFoundException;
import io.quarkus.mongodb.panache.PanacheMongoRepository;
import io.quarkus.panache.common.Parameters;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
@OzgCloudLogging
......@@ -127,6 +128,10 @@ class UserRepository implements PanacheMongoRepository<User> {
return find(DELETED_FIELD, true).project(UserIdProjection.class).stream().map(UserIdProjection::getAsString);
}
public Stream<User> findAllUsersByOrganisationsEinheitId(String organisationsEinheitId) {
return stream(ORGANISATIONS_EINHEIT_IDS_FIELD, organisationsEinheitId);
}
public void deleteById(String id) {
deleteById(new ObjectId(id));
}
......
......@@ -28,26 +28,28 @@ import java.util.Optional;
import java.util.stream.Stream;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.apache.commons.lang3.StringUtils;
import de.ozgcloud.common.logging.OzgCloudLogging;
import de.ozgcloud.user.common.errorhandling.FunctionalException;
import de.ozgcloud.user.common.errorhandling.ResourceNotFoundException;
import de.ozgcloud.user.common.errorhandling.TechnicalException;
import de.ozgcloud.user.keycloak.KeycloakUserRemoteService;
import de.ozgcloud.user.settings.UserSettings;
import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.infrastructure.Infrastructure;
import lombok.RequiredArgsConstructor;
@ApplicationScoped
@OzgCloudLogging
@RequiredArgsConstructor
public class UserService {
@Inject
UserRepository repository;
@Inject
KeycloakUserRemoteService keycloakUserRemoteService;
static final String MISSING_ORGANISATIONS_EINHEIT_ID_MESSAGE_TEMPLATE = "OrganisationsEinheitId ('%s') can not be null or empty";
private final UserRepository repository;
private final KeycloakUserRemoteService keycloakUserRemoteService;
public User save(User user) {
findUser(user).ifPresentOrElse(persistedUser -> repository.updateUser(addIdUser(user, persistedUser)),
......@@ -146,4 +148,11 @@ public class UserService {
throw new TechnicalException(String.format("Deleting active users is not allowed [user id: %s].", user.getId().toHexString()));
}
}
public Stream<User> findAllUsersOfOrganisationsEinheit(String organisationsEinheitId) {
if (StringUtils.isEmpty(organisationsEinheitId)) {
throw new FunctionalException(() -> String.format(MISSING_ORGANISATIONS_EINHEIT_ID_MESSAGE_TEMPLATE, organisationsEinheitId));
}
return repository.findAllUsersByOrganisationsEinheitId(organisationsEinheitId);
}
}
\ No newline at end of file
......@@ -35,6 +35,7 @@ import io.grpc.stub.StreamObserver;
import io.quarkus.grpc.GrpcService;
@GrpcService
@Deprecated
public class RecipientGrpcService extends RecipientServiceImplBase {
@Inject
......
......@@ -34,6 +34,7 @@ import de.ozgcloud.user.User;
unmappedSourcePolicy = ReportingPolicy.WARN, //
nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS, //
collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED)
@Deprecated
interface RecipientMapper {
GrpcRecipient toRecipient(User user);
......
......@@ -15,6 +15,7 @@ import io.quarkus.mongodb.panache.PanacheMongoRepository;
@ApplicationScoped
@OzgCloudLogging
@Deprecated
class RecipientRepository implements PanacheMongoRepository<User> {
private static final String SEARCH_RECIPIENT_QUERY = ORGANISATIONS_EINHEIT_IDS_FIELD + " = ?1 and "
......
......@@ -25,15 +25,16 @@ package de.ozgcloud.user.recipient;
import java.util.List;
import de.ozgcloud.user.common.errorhandling.FunctionalException;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import com.cronutils.utils.StringUtils;
import de.ozgcloud.user.User;
import de.ozgcloud.user.common.errorhandling.FunctionalException;
@ApplicationScoped
@Deprecated
class RecipientService {
static final String MISSING_ORGANISATIONS_EINHEIT_ID_MESSAGE_TEMPLATE = "OrganisationsEinheitId ('%s') can not be null or empty";
......
package de.ozgcloud.user.settings;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.NullValueCheckStrategy;
import org.mapstruct.ReportingPolicy;
import de.ozgcloud.user.userprofile.GrpcUserSettings;
@Mapper(unmappedTargetPolicy = ReportingPolicy.WARN, nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS)
public interface UserSettingsMapper {
@Mapping(target = "mergeFrom", ignore = true)
@Mapping(target = "clearField", ignore = true)
@Mapping(target = "clearOneof", ignore = true)
@Mapping(target = "mergeUnknownFields", ignore = true)
@Mapping(target = "unknownFields", ignore = true)
@Mapping(target = "allFields", ignore = true)
@Mapping(target = "notificationsSendForAll", source = "notificationsSendFor")
GrpcUserSettings toGrpc(UserSettings userSettings);
default boolean map(NotificationsSendFor sendFor) {
return NotificationsSendFor.ALL.equals(sendFor);
}
}
......@@ -25,22 +25,20 @@ package de.ozgcloud.user.userprofile;
import java.util.stream.Stream;
import jakarta.inject.Inject;
import de.ozgcloud.user.User;
import de.ozgcloud.user.UserService;
import de.ozgcloud.user.grpc.userprofile.UserProfileServiceGrpc.UserProfileServiceImplBase;
import io.grpc.stub.StreamObserver;
import io.quarkus.grpc.GrpcService;
import lombok.RequiredArgsConstructor;
@GrpcService
@RequiredArgsConstructor
public class UserProfileGrpcService extends UserProfileServiceImplBase {
@Inject
UserService service;
private final UserService service;
@Inject
UserProfileMapper mapper;
private final UserProfileMapper mapper;
@Override
public void getById(GrpcGetUserProfileRequest request, StreamObserver<GrpcGetUserProfileResponse> responseObserver) {
......@@ -62,6 +60,22 @@ public class UserProfileGrpcService extends UserProfileServiceImplBase {
responseObserver.onCompleted();
}
@Override
public void getAllByOrganisationsEinheitId(GrpcGetAllByOrganisationsEinheitIdRequest request,
StreamObserver<GrpcGetAllByOrganisationsEinheitIdResponse> responseObserver) {
var userProfiles = service.findAllUsersOfOrganisationsEinheit(request.getOrganisationsEinheitId());
responseObserver.onNext(buildGrpcGetAllByOrganisationsEinheitIdResponse(userProfiles));
responseObserver.onCompleted();
}
GrpcGetAllByOrganisationsEinheitIdResponse buildGrpcGetAllByOrganisationsEinheitIdResponse(Stream<User> users) {
return GrpcGetAllByOrganisationsEinheitIdResponse.newBuilder()
.addAllUserProfile(users.map(mapper::mapTo).toList())
.build();
}
@Override
public void findInactiveUserIds(GrpcFindInactiveUserIdsRequest request, StreamObserver<GrpcFindInactiveUserIdsResponse> responseObserver) {
var userIds = service.findAllInactiveUserIds();
......
......@@ -30,9 +30,9 @@ import org.mapstruct.NullValueCheckStrategy;
import org.mapstruct.ReportingPolicy;
import de.ozgcloud.user.User;
import de.ozgcloud.user.settings.UserSettingsMapper;
@Mapper(unmappedTargetPolicy = ReportingPolicy.WARN, //
nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS)
@Mapper(unmappedTargetPolicy = ReportingPolicy.WARN, nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS, uses = UserSettingsMapper.class)
interface UserProfileMapper {
@Mapping(target = "emailBytes", ignore = true)
......@@ -45,6 +45,7 @@ interface UserProfileMapper {
@Mapping(target = "lastNameBytes", ignore = true)
@Mapping(target = "unknownFields", ignore = true)
@Mapping(target = "allFields", ignore = true)
@Mapping(target = "mergeUserSettings", ignore = true)
GrpcUserProfile mapTo(User userProfile);
default String toString(ObjectId objectId) {
......
......@@ -29,6 +29,7 @@ import static org.assertj.core.api.Assertions.*;
import java.time.Instant;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.stream.Stream;
import jakarta.inject.Inject;
......@@ -51,7 +52,7 @@ import io.quarkus.test.junit.TestProfile;
class UserRepositoryITCase {
@Inject
UserRepository repository;
private UserRepository repository;
@DisplayName("Update unsynced users")
@Nested
......@@ -249,8 +250,7 @@ class UserRepositoryITCase {
Arrays.asList(
UserTestFactory.createBuilder().id(null).username("user1").fullName("Franz Vogel").build(),
UserTestFactory.createBuilder().id(null).username("user2").fullName("Franz von Holzhausen").build(),
UserTestFactory.createBuilder().id(null).username("user3").fullName("Peter Lustig").build()
).forEach(repository::persist);
UserTestFactory.createBuilder().id(null).username("user3").fullName("Peter Lustig").build()).forEach(repository::persist);
var foundUsernames = repository.findUsers(query, 10).map(User::getUsername);
......@@ -266,8 +266,7 @@ class UserRepositoryITCase {
Arguments.of("Franz von", new String[] { "user2" }),
Arguments.of("Franz von Holzhausen", new String[] { "user2" }),
Arguments.of("Franz L", new String[0]),
Arguments.of("Peter V", new String[0])
);
Arguments.of("Peter V", new String[0]));
}
}
......@@ -285,8 +284,7 @@ class UserRepositoryITCase {
Arrays.asList(
UserTestFactory.createBuilder().id(null).username("user1").fullNameReversed("Langbein Franz").build(),
UserTestFactory.createBuilder().id(null).username("user2").fullNameReversed("Lustig Peter").build(),
UserTestFactory.createBuilder().id(null).username("user3").fullNameReversed("Ilona Nowak").build()
).forEach(repository::persist);
UserTestFactory.createBuilder().id(null).username("user3").fullNameReversed("Ilona Nowak").build()).forEach(repository::persist);
var foundUsernames = repository.findUsers(query, 10).map(User::getUsername);
......@@ -297,8 +295,7 @@ class UserRepositoryITCase {
return Stream.of(
Arguments.of("L", new String[] { "user1", "user2", "user3" }),
Arguments.of("Lustig", new String[] { "user2" }),
Arguments.of("Lustig Peter", new String[] { "user2" })
);
Arguments.of("Lustig Peter", new String[] { "user2" }));
}
}
......@@ -573,6 +570,44 @@ class UserRepositoryITCase {
}
}
@Nested
class TestFindAllUsersByOrganisationsEinheitId {
private static final String ORGANISATIONSEINHEIT_ID_1 = UUID.randomUUID().toString();
private static final String ORGANISATIONSEINHEIT_ID_2 = UUID.randomUUID().toString();
private final User user1 = UserTestFactory.createBuilder()
.id(null)
.clearOrganisationsEinheitIds()
.organisationsEinheitId(ORGANISATIONSEINHEIT_ID_1)
.build();
private final User user2 = UserTestFactory.createBuilder()
.id(null)
.clearOrganisationsEinheitIds()
.organisationsEinheitId(ORGANISATIONSEINHEIT_ID_1)
.build();
private final User user3 = UserTestFactory.createBuilder()
.id(null)
.clearOrganisationsEinheitIds()
.organisationsEinheitId(ORGANISATIONSEINHEIT_ID_2)
.build();
@BeforeEach
void init() {
repository.deleteAll();
repository.persist(user1);
repository.persist(user2);
repository.persist(user3);
}
@Test
void shouldFindUsersByOrganisationsEinheitId() {
var users = repository.findAllUsersByOrganisationsEinheitId(ORGANISATIONSEINHEIT_ID_1);
assertThat(users).usingRecursiveFieldByFieldElementComparator().containsExactlyInAnyOrder(user1, user2);
}
}
@Nested
class TestDeleteById {
......
......@@ -30,6 +30,7 @@ import static org.mockito.Mockito.*;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;
import org.assertj.core.api.Condition;
import org.bson.types.ObjectId;
......@@ -49,12 +50,12 @@ import io.quarkus.panache.common.Parameters;
class UserRepositoryTest {
@Spy
UserRepository userRepository;
private UserRepository userRepository;
@Mock
PanacheQuery<User> panacheQuery;
private PanacheQuery<User> panacheQuery;
User user = UserTestFactory.create();
private final User user = UserTestFactory.create();
@DisplayName("Test updating users")
@Nested
......@@ -201,4 +202,31 @@ class UserRepositoryTest {
assertThat(regex).isEqualTo("/.*abc.*/i");
}
}
@Nested
class TestFindAllUsersByOrganisationsEinheitId {
@BeforeEach
void mock() {
doReturn(Stream.of(user)).when(userRepository).stream(User.ORGANISATIONS_EINHEIT_IDS_FIELD, UserTestFactory.ORGANISTATIONSEINHEITEN_ID);
}
@Test
void shouldFindUsers() {
callRepository();
verify(userRepository).stream(User.ORGANISATIONS_EINHEIT_IDS_FIELD, UserTestFactory.ORGANISTATIONSEINHEITEN_ID);
}
@Test
void shouldReturnStreamOfUsers() {
var users = callRepository();
assertThat(users).containsExactly(user);
}
private Stream<User> callRepository() {
return userRepository.findAllUsersByOrganisationsEinheitId(UserTestFactory.ORGANISTATIONSEINHEITEN_ID);
}
}
}
......@@ -38,10 +38,13 @@ import org.bson.types.ObjectId;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.NullAndEmptySource;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import de.ozgcloud.user.common.errorhandling.FunctionalException;
import de.ozgcloud.user.common.errorhandling.ResourceNotFoundException;
import de.ozgcloud.user.common.errorhandling.TechnicalException;
import de.ozgcloud.user.keycloak.KeycloakUserRemoteService;
......@@ -399,4 +402,42 @@ class UserServiceTest {
verify(repository).persist(user);
}
}
@Nested
class TestFindAllUsersOfOrganisationsEinheit {
private static final String EXPECTED_EXCEPTION_MESSAGE_TEMPLATE = "Functional error: "
+ UserService.MISSING_ORGANISATIONS_EINHEIT_ID_MESSAGE_TEMPLATE;
@Test
void shouldCallRepository() {
callService();
verify(repository).findAllUsersByOrganisationsEinheitId(UserTestFactory.ORGANISTATIONSEINHEITEN_ID);
}
@Test
void shouldReturnFoundUsers() {
var users = Stream.of(UserTestFactory.create());
when(repository.findAllUsersByOrganisationsEinheitId(UserTestFactory.ORGANISTATIONSEINHEITEN_ID)).thenReturn(users);
var returnedUsers = callService();
assertThat(returnedUsers).isEqualTo(users);
}
private Stream<User> callService() {
return service.findAllUsersOfOrganisationsEinheit(UserTestFactory.ORGANISTATIONSEINHEITEN_ID);
}
@ParameterizedTest
@NullAndEmptySource
void shouldThrowFunctionalExceptionOnInvalidOrganisationsId(String organisationsEinheitId) {
var expectedMessage = String.format(EXPECTED_EXCEPTION_MESSAGE_TEMPLATE, organisationsEinheitId);
assertThatExceptionOfType(FunctionalException.class)
.isThrownBy(() -> service.findAllUsersOfOrganisationsEinheit(organisationsEinheitId))
.withMessageStartingWith(expectedMessage);
}
}
}
\ No newline at end of file
......@@ -27,6 +27,7 @@ import java.util.List;
import org.bson.types.ObjectId;
import de.ozgcloud.user.settings.UserSettings;
import de.ozgcloud.user.settings.UserSettingsTestFactory;
public class UserTestFactory {
......@@ -45,6 +46,7 @@ public class UserTestFactory {
public static final String EXTERNAL_ID = "aec115b5-67be-42c9-a693-36d4d711a87a";
public static final String ORGANISTATIONSEINHEITEN_ID = "0815";
public static final List<String> ROLES = List.of("ROLE_1", "POSTSTELLE");
public static final UserSettings USER_SETTINGS = UserSettingsTestFactory.create();
public static User create() {
return createBuilder().build();
......@@ -64,6 +66,6 @@ public class UserTestFactory {
.externalId(EXTERNAL_ID)
.organisationsEinheitId(ORGANISTATIONSEINHEITEN_ID)
.roles(ROLES)
.userSettings(UserSettingsTestFactory.create());
.userSettings(USER_SETTINGS);
}
}
/*
* Copyright (C) 2022 Das Land Schleswig-Holstein vertreten durch den
* Ministerpräsidenten des Landes Schleswig-Holstein
* Staatskanzlei
* Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
*
* Lizenziert unter der EUPL, Version 1.2 oder - sobald
* diese von der Europäischen Kommission genehmigt wurden -
* Folgeversionen der EUPL ("Lizenz");
* Sie dürfen dieses Werk ausschließlich gemäß
* dieser Lizenz nutzen.
* Eine Kopie der Lizenz finden Sie hier:
*
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
*
* Sofern nicht durch anwendbare Rechtsvorschriften
* gefordert oder in schriftlicher Form vereinbart, wird
* die unter der Lizenz verbreitete Software "so wie sie
* ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
* ausdrücklich oder stillschweigend - verbreitet.
* Die sprachspezifischen Genehmigungen und Beschränkungen
* unter der Lizenz sind dem Lizenztext zu entnehmen.
*/
package de.ozgcloud.user.settings;
import de.ozgcloud.user.userprofile.GrpcUserSettings;
public class GrpcUserSettingsTestFactory {
public static final boolean NOTIFICATIONS_SEND_FOR = NotificationsSendFor.ALL == UserSettingsTestFactory.NOTIFICATIONS_SEND_FOR;
public static final boolean VORGANG_CREATED = UserSettingsTestFactory.VORGANG_CREATED;
public static final boolean VORGANG_ASSIGNED_TO_USER = UserSettingsTestFactory.VORGANG_ASSIGNED_TO_USER;
public static final boolean POSTFACH_NACHRICHT_FROM_ANTRAGSTELLER = UserSettingsTestFactory.POSTFACH_NACHRICHT_FROM_ANTRAGSTELLER;
public static final boolean WIEDERVORLAGE_OVERDUE = UserSettingsTestFactory.WIEDERVORLAGE_OVERDUE;
public static GrpcUserSettings create() {
return createBuilder().build();
}
public static GrpcUserSettings.Builder createBuilder() {
return GrpcUserSettings.newBuilder()
.setNotificationsSendForAll(NOTIFICATIONS_SEND_FOR)
.setVorgangCreated(VORGANG_CREATED)
.setVorgangAssignedToUser(VORGANG_ASSIGNED_TO_USER)
.setPostfachNachrichtFromAntragsteller(POSTFACH_NACHRICHT_FROM_ANTRAGSTELLER)
.setWiedervorlageOverdue(WIEDERVORLAGE_OVERDUE);
}
}
\ No newline at end of file
package de.ozgcloud.user.settings;
import static org.assertj.core.api.Assertions.*;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.mapstruct.factory.Mappers;
class UserSettingsMapperTest {
private final UserSettingsMapper mapper = Mappers.getMapper(UserSettingsMapper.class);
@Nested
class TestToGrpc {
@Test
void shouldMapToGrpc() {
var userSettings = mapper.toGrpc(UserSettingsTestFactory.create());
assertThat(userSettings).usingRecursiveComparison().isEqualTo(GrpcUserSettingsTestFactory.create());
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment