Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
<dependency>
<groupId>dev.vality</groupId>
<artifactId>org-management-proto</artifactId>
<version>1.21-23aa5d8</version>
<version>1.22-4a6c2e7</version>
<exclusions>
<exclusion>
<groupId>dev.vality</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@
import dev.vality.orgmanagement.OrganizationStatus;
import dev.vality.orgmanagement.RoleAssignment;
import dev.vality.orgmanagement.RoleScope;
import dev.vality.orgmanagement.User;
import dev.vality.orgmanager.entity.InvitationEntity;
import dev.vality.orgmanager.entity.MemberEntity;
import dev.vality.orgmanager.entity.MemberRoleEntity;
import dev.vality.orgmanager.entity.OrganizationEntity;
import dev.vality.orgmanager.entity.OrganizationRoleEntity;
import dev.vality.orgmanager.entity.StoredInvitationStatus;
import dev.vality.orgmanager.service.dto.MemberWithRoleDto;
import dev.vality.orgmanager.service.dto.UserDto;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
Expand Down Expand Up @@ -49,18 +51,30 @@ public Organization toOrganization(OrganizationEntity entity) {
return organization;
}

public User toUser(MemberEntity entity) {
return toUser(entity.getId(), entity.getEmail());
}

public User toUser(UserDto row) {
return toUser(row.getId(), row.getEmail());
}

private User toUser(String id, String email) {
User user = new User(id);
if (email != null) {
user.setEmail(email);
}
return user;
}

public Member toMember(MemberEntity entity, String organizationId) {
List<MemberRole> roles = collectionOrEmpty(entity.getRoles()).stream()
.filter(MemberRoleEntity::isActive)
.filter(role -> organizationId.equals(role.getOrganizationId()))
.sorted(Comparator.comparing(MemberRoleEntity::getId))
.map(this::toMemberRole)
.toList();
Member member = new Member(entity.getId(), roles);
if (entity.getEmail() != null) {
member.setEmail(entity.getEmail());
}
return member;
return new Member(toUser(entity), roles);
}

/**
Expand All @@ -70,13 +84,9 @@ public Member toMember(MemberEntity entity, String organizationId) {
public List<Member> toMembers(List<MemberWithRoleDto> rows) {
Map<String, Member> members = new LinkedHashMap<>();
for (MemberWithRoleDto row : collectionOrEmpty(rows)) {
Member member = members.computeIfAbsent(row.getId(), id -> {
Member created = new Member(id, new ArrayList<>());
if (row.getEmail() != null) {
created.setEmail(row.getEmail());
}
return created;
});
Member member = members.computeIfAbsent(
row.getId(),
id -> new Member(toUser(id, row.getEmail()), new ArrayList<>()));
if (row.getMemberRoleId() != null) {
member.getRoles().add(toMemberRole(row));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import dev.vality.orgmanager.entity.MemberEntity;
import dev.vality.orgmanager.service.dto.MemberWithRoleDto;
import dev.vality.orgmanager.service.dto.UserDto;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.NativeQuery;
Expand Down Expand Up @@ -72,6 +73,17 @@ public interface MemberRepository extends JpaRepository<MemberEntity, String> {
" ORDER BY m.id, mr.id ")
List<MemberWithRoleDto> getOrgMemberListWithRoles(String orgId, Collection<String> memberIds);

/**
* Страница пользователей по возрастанию id
*/
@NativeQuery("SELECT m.id, " +
" m.email " +
" FROM org_manager.member m " +
" WHERE (CAST(?1 AS VARCHAR) IS NULL OR m.id > ?1) " +
" AND (CAST(?2 AS VARCHAR) IS NULL OR LOWER(m.email) = LOWER(CAST(?2 AS VARCHAR))) " +
" ORDER BY m.id ")
List<UserDto> getUserPage(String continuationToken, String email, Pageable pageable);

boolean existsById(String id);


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
public class AdminManagementService implements AdminManagementSrv.Iface {

private final AdminOrganizationService adminOrganizationService;
private final AdminUserService adminUserService;
private final AdminMemberService adminMemberService;
private final AdminInvitationService adminInvitationService;

Expand Down Expand Up @@ -53,6 +54,16 @@ public Organization activateOrganization(String organizationId)
return adminOrganizationService.activate(organizationId);
}

@Override
public ListUsersResult listUsers(ListUsersRequest request) {
return adminUserService.list(request);
}

@Override
public User getUser(String userId) throws UnknownUser {
return adminUserService.get(userId);
}

@Override
public OrganizationRole getOrganizationRole(String organizationId, String roleId)
throws OrganizationNotFound, RoleNotFound {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package dev.vality.orgmanager.service;

import dev.vality.orgmanagement.ListUsersRequest;
import dev.vality.orgmanagement.ListUsersResult;
import dev.vality.orgmanagement.UnknownUser;
import dev.vality.orgmanagement.User;
import dev.vality.orgmanager.converter.AdminManagementConverter;
import dev.vality.orgmanager.repository.MemberRepository;
import dev.vality.orgmanager.service.dto.AdminPage;
import dev.vality.orgmanager.service.dto.UserDto;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import static dev.vality.orgmanager.service.AdminCommonService.pageLimit;
import static java.util.Objects.requireNonNullElseGet;

/** Пользователи в административном контракте: страница пользователей и чтение по идентификатору. */
@Slf4j
@Service
@RequiredArgsConstructor
public class AdminUserService {

private final MemberRepository memberRepository;
private final AdminManagementConverter converter;

@Transactional(readOnly = true)
public ListUsersResult list(ListUsersRequest request) {
log.info("List users: request={}", request);
ListUsersRequest safeRequest = requireNonNullElseGet(request, ListUsersRequest::new);
int limit = pageLimit(safeRequest.getLimit());
AdminPage<UserDto> page = AdminPage.of(
memberRepository.getUserPage(
safeRequest.getContinuationToken(),
safeRequest.getEmail(),
PageRequest.ofSize(limit + 1)),
limit,
UserDto::getId);
ListUsersResult result = new ListUsersResult(page.items().stream()
.map(converter::toUser)
.toList());
page.continuationToken().ifPresent(result::setContinuationToken);
return result;
}

@Transactional(readOnly = true)
public User get(String userId) throws UnknownUser {
log.info("Get user: userId={}", userId);
if (userId == null || userId.isBlank()) {
throw new UnknownUser();
}
return memberRepository.findById(userId)
.map(converter::toUser)
.orElseThrow(UnknownUser::new);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package dev.vality.orgmanager.service.dto;

public interface UserDto {

String getId();

String getEmail();

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import dev.vality.orgmanager.repository.OrganizationRoleRepository;
import dev.vality.orgmanager.repository.ScopeRepository;
import dev.vality.orgmanager.service.dto.MemberWithRoleDto;
import dev.vality.orgmanager.service.dto.UserDto;
import dev.vality.orgmanager.util.JsonCodec;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -84,6 +85,7 @@ void setUp() {
scopeRepository,
converter,
commonService),
new AdminUserService(memberRepository, converter),
new AdminMemberService(
organizationRepository,
memberRepository,
Expand Down Expand Up @@ -286,7 +288,8 @@ void shouldAddMemberAndManageRoleWithoutLastRoleRestriction() throws Exception {
var role = service.assignMemberRole("org", "user", new AssignMemberRoleRequest("manager"));
service.removeMemberRole("org", "user", role.getId());

assertEquals("user@example.com", member.getEmail());
assertEquals("user", member.getUser().getId());
assertEquals("user@example.com", member.getUser().getEmail());
assertTrue(service.getMember("org", "user").getRoles().isEmpty());
}

Expand All @@ -306,7 +309,7 @@ void shouldUpdateEmailWhenMemberIsAddedTwice() throws Exception {

var member = service.addMember("org", new AddMemberRequest("user", "new@example.com"));

assertEquals("new@example.com", member.getEmail());
assertEquals("new@example.com", member.getUser().getEmail());
assertEquals(1, organization.getMembers().size());
}

Expand Down Expand Up @@ -487,11 +490,11 @@ void shouldListMembersIncludingOnesWithoutRoles() throws Exception {
var result = service.listMembers("org", new ListMembersRequest());
var members = result.getMembers();

assertEquals(List.of("user-1", "user-2"), members.stream().map(it -> it.getId()).toList());
assertEquals(List.of("user-1", "user-2"), members.stream().map(it -> it.getUser().getId()).toList());
assertEquals(List.of("manager", "accountant"),
members.get(0).getRoles().stream().map(it -> it.getRoleId()).toList());
assertTrue(members.get(1).getRoles().isEmpty());
assertEquals("user-2@example.com", members.get(1).getEmail());
assertEquals("user-2@example.com", members.get(1).getUser().getEmail());
assertFalse(result.isSetContinuationToken());
}

Expand All @@ -505,7 +508,7 @@ void shouldReturnContinuationTokenWhenMoreMembersExist() throws Exception {

var result = service.listMembers("org", new ListMembersRequest().setLimit(1));

assertEquals(List.of("user-1"), result.getMembers().stream().map(it -> it.getId()).toList());
assertEquals(List.of("user-1"), result.getMembers().stream().map(it -> it.getUser().getId()).toList());
assertEquals("user-1", result.getContinuationToken());
}

Expand All @@ -521,7 +524,7 @@ void shouldReadNextMembersPageByContinuationToken() throws Exception {
"org",
new ListMembersRequest().setLimit(1).setContinuationToken("user-1"));

assertEquals(List.of("user-2"), result.getMembers().stream().map(it -> it.getId()).toList());
assertEquals(List.of("user-2"), result.getMembers().stream().map(it -> it.getUser().getId()).toList());
assertFalse(result.isSetContinuationToken());
}

Expand All @@ -544,6 +547,94 @@ void shouldRejectListingMembersOfUnknownOrganization() {
verifyNoInteractions(memberRepository);
}

@Test
void shouldListUsersWithoutOrganizationContext() throws Exception {
when(memberRepository.getUserPage(isNull(), isNull(), any(Pageable.class))).thenReturn(List.of(
userRow("user-1", "user-1@example.com"),
userRow("user-2", null)));

var result = service.listUsers(new ListUsersRequest());

assertEquals(List.of("user-1", "user-2"), result.getUsers().stream().map(User::getId).toList());
assertEquals("user-1@example.com", result.getUsers().get(0).getEmail());
assertFalse(result.getUsers().get(1).isSetEmail());
assertFalse(result.isSetContinuationToken());
}

@Test
void shouldPassEmailFilterToQuery() throws Exception {
when(memberRepository.getUserPage(isNull(), eq("user-1@example.com"), any(Pageable.class)))
.thenReturn(List.of(userRow("user-1", "user-1@example.com")));

var result = service.listUsers(new ListUsersRequest().setEmail("user-1@example.com"));

assertEquals(List.of("user-1"), result.getUsers().stream().map(User::getId).toList());
}

@Test
void shouldReturnContinuationTokenWhenMoreUsersExist() throws Exception {
when(memberRepository.getUserPage(isNull(), isNull(), any(Pageable.class))).thenReturn(List.of(
userRow("user-1", "user-1@example.com"),
userRow("user-2", "user-2@example.com")));

var result = service.listUsers(new ListUsersRequest().setLimit(1));

assertEquals(List.of("user-1"), result.getUsers().stream().map(User::getId).toList());
assertEquals("user-1", result.getContinuationToken());
}

@Test
void shouldReadNextUsersPageByContinuationToken() throws Exception {
when(memberRepository.getUserPage(eq("user-1"), isNull(), any(Pageable.class)))
.thenReturn(List.of(userRow("user-2", "user-2@example.com")));

var result = service.listUsers(new ListUsersRequest().setLimit(1).setContinuationToken("user-1"));

assertEquals(List.of("user-2"), result.getUsers().stream().map(User::getId).toList());
assertFalse(result.isSetContinuationToken());
}

@Test
void shouldGetUserById() throws Exception {
when(memberRepository.findById("user")).thenReturn(Optional.of(MemberEntity.builder()
.id("user")
.email("user@example.com")
.roles(new HashSet<>())
.build()));

var user = service.getUser("user");

assertEquals("user", user.getId());
assertEquals("user@example.com", user.getEmail());
}

@Test
void shouldRejectUnknownUser() {
when(memberRepository.findById("user")).thenReturn(Optional.empty());

assertThrows(UnknownUser.class, () -> service.getUser("user"));
}

@Test
void shouldRejectBlankUserIdWithoutQuery() {
assertThrows(UnknownUser.class, () -> service.getUser(" "));
verifyNoInteractions(memberRepository);
}

private UserDto userRow(String userId, String email) {
return new UserDto() {
@Override
public String getId() {
return userId;
}

@Override
public String getEmail() {
return email;
}
};
}

private MemberWithRoleDto memberRow(String memberId, String email, String roleId, String role) {
return new MemberWithRoleDto() {
@Override
Expand Down
Loading