chore: Use Spring projection for workspace user pics (#30804)
We use Spring's native support for projections here, instead of setting the `.fields()` ourselves. The advantage is that this way translates directly to Postgres. The cost is that, the field names in projection are duplicated. For example, if `profilePhotoAssetId` is renamed or otherwise changed, we won't see a compile error. This can be prevented with a test. The `findPhotoAssetsByUserIds_WhenPhotoAssetIdExist_ReturnsPhotoAssetId` test is able to catch this case perfectly well.
This commit is contained in:
parent
af407904bd
commit
6d0b7af54e
|
|
@ -0,0 +1,7 @@
|
|||
package com.appsmith.server.projections;
|
||||
|
||||
public interface UserDataProfilePhotoProjection {
|
||||
String getUserId();
|
||||
|
||||
String getProfilePhotoAssetId();
|
||||
}
|
||||
|
|
@ -3,7 +3,6 @@ package com.appsmith.server.repositories.ce;
|
|||
import com.appsmith.server.domains.UserData;
|
||||
import com.appsmith.server.repositories.AppsmithRepository;
|
||||
import com.mongodb.client.result.UpdateResult;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.List;
|
||||
|
|
@ -14,7 +13,5 @@ public interface CustomUserDataRepositoryCE extends AppsmithRepository<UserData>
|
|||
|
||||
Mono<UpdateResult> removeIdFromRecentlyUsedList(String userId, String workspaceId, List<String> applicationIds);
|
||||
|
||||
Flux<UserData> findPhotoAssetsByUserIds(Iterable<String> userId);
|
||||
|
||||
Mono<String> fetchMostRecentlyUsedWorkspaceId(String userId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,16 +6,13 @@ import com.appsmith.server.dtos.QRecentlyUsedEntityDTO;
|
|||
import com.appsmith.server.dtos.RecentlyUsedEntityDTO;
|
||||
import com.appsmith.server.repositories.BaseAppsmithRepositoryImpl;
|
||||
import com.appsmith.server.repositories.CacheableRepositoryHelper;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.mongodb.BasicDBObject;
|
||||
import com.mongodb.client.result.UpdateResult;
|
||||
import org.springframework.data.mongodb.core.ReactiveMongoOperations;
|
||||
import org.springframework.data.mongodb.core.convert.MongoConverter;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.data.mongodb.core.query.Update;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.List;
|
||||
|
|
@ -56,23 +53,6 @@ public class CustomUserDataRepositoryCEImpl extends BaseAppsmithRepositoryImpl<U
|
|||
query(where(fieldName(QUserData.userData.userId)).is(userId)), update, UserData.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a list of UserData objects from DB where userId matches with the provided a list of userId.
|
||||
* The returned UserData objects will have only the userId and photoAssetId fields.
|
||||
*
|
||||
* @param userId List of userId as a list
|
||||
* @return Flux of UserData with only the photoAssetId and userId fields
|
||||
*/
|
||||
@Override
|
||||
public Flux<UserData> findPhotoAssetsByUserIds(Iterable<String> userId) {
|
||||
// need to convert from Iterable to ArrayList because the "in" method of criteria takes a collection as input
|
||||
Criteria criteria = where(fieldName(QUserData.userData.userId)).in(Lists.newArrayList(userId));
|
||||
return queryAll()
|
||||
.criteria(criteria)
|
||||
.fields(fieldName(QUserData.userData.profilePhotoAssetId), fieldName(QUserData.userData.userId))
|
||||
.submit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<String> fetchMostRecentlyUsedWorkspaceId(String userId) {
|
||||
final Query query = query(where(fieldName(QUserData.userData.userId)).is(userId));
|
||||
|
|
|
|||
|
|
@ -1,11 +1,24 @@
|
|||
package com.appsmith.server.repositories.ce;
|
||||
|
||||
import com.appsmith.server.domains.UserData;
|
||||
import com.appsmith.server.projections.UserDataProfilePhotoProjection;
|
||||
import com.appsmith.server.repositories.BaseRepository;
|
||||
import com.appsmith.server.repositories.CustomUserDataRepository;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public interface UserDataRepositoryCE extends BaseRepository<UserData, String>, CustomUserDataRepository {
|
||||
|
||||
Mono<UserData> findByUserId(String userId);
|
||||
|
||||
/**
|
||||
* Fetches a list of UserData objects from DB where userId matches with the provided a list of userId.
|
||||
* The returned UserData objects will have only the userId and photoAssetId fields.
|
||||
*
|
||||
* @param userIds Collection of userId strings
|
||||
* @return Flux of UserData with only the photoAssetId and userId fields
|
||||
*/
|
||||
Flux<UserDataProfilePhotoProjection> findByUserIdIn(Collection<String> userIds);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,8 @@
|
|||
package com.appsmith.server.services;
|
||||
|
||||
import com.appsmith.server.notifications.EmailSender;
|
||||
import com.appsmith.server.repositories.UserDataRepository;
|
||||
import com.appsmith.server.repositories.UserRepository;
|
||||
import com.appsmith.server.services.ce.UserWorkspaceServiceCEImpl;
|
||||
import com.appsmith.server.solutions.PermissionGroupPermission;
|
||||
import com.appsmith.server.solutions.PolicySolution;
|
||||
import com.appsmith.server.solutions.WorkspacePermission;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
|
@ -18,9 +15,6 @@ public class UserWorkspaceServiceImpl extends UserWorkspaceServiceCEImpl impleme
|
|||
SessionUserService sessionUserService,
|
||||
WorkspaceService workspaceService,
|
||||
UserRepository userRepository,
|
||||
UserDataRepository userDataRepository,
|
||||
PolicySolution policySolution,
|
||||
EmailSender emailSender,
|
||||
UserDataService userDataService,
|
||||
PermissionGroupService permissionGroupService,
|
||||
TenantService tenantService,
|
||||
|
|
@ -31,9 +25,6 @@ public class UserWorkspaceServiceImpl extends UserWorkspaceServiceCEImpl impleme
|
|||
sessionUserService,
|
||||
workspaceService,
|
||||
userRepository,
|
||||
userDataRepository,
|
||||
policySolution,
|
||||
emailSender,
|
||||
userDataService,
|
||||
permissionGroupService,
|
||||
tenantService,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import org.springframework.http.codec.multipart.Part;
|
|||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
public interface UserDataServiceCE {
|
||||
|
|
@ -21,6 +22,8 @@ public interface UserDataServiceCE {
|
|||
|
||||
Mono<UserData> getForUserEmail(String email);
|
||||
|
||||
Mono<Map<String, String>> getProfilePhotoAssetIdsForUserIds(Collection<String> userIds);
|
||||
|
||||
Mono<UserData> updateForCurrentUser(UserData updates);
|
||||
|
||||
Mono<UserData> updateForUser(User user, UserData updates);
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import com.appsmith.server.dtos.RecentlyUsedEntityDTO;
|
|||
import com.appsmith.server.exceptions.AppsmithError;
|
||||
import com.appsmith.server.exceptions.AppsmithException;
|
||||
import com.appsmith.server.helpers.CollectionUtils;
|
||||
import com.appsmith.server.projections.UserDataProfilePhotoProjection;
|
||||
import com.appsmith.server.repositories.ApplicationRepository;
|
||||
import com.appsmith.server.repositories.UserDataRepository;
|
||||
import com.appsmith.server.repositories.UserRepository;
|
||||
|
|
@ -40,6 +41,7 @@ import reactor.core.scheduler.Scheduler;
|
|||
import reactor.util.function.Tuple2;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
|
@ -125,6 +127,15 @@ public class UserDataServiceCEImpl extends BaseService<UserDataRepository, UserD
|
|||
.flatMap(this::getForUser);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Map<String, String>> getProfilePhotoAssetIdsForUserIds(Collection<String> userIds) {
|
||||
return repository
|
||||
.findByUserIdIn(userIds)
|
||||
.collectMap(
|
||||
UserDataProfilePhotoProjection::getUserId,
|
||||
UserDataProfilePhotoProjection::getProfilePhotoAssetId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<UserData> updateForCurrentUser(UserData updates) {
|
||||
return sessionUserService
|
||||
|
|
|
|||
|
|
@ -12,8 +12,6 @@ import com.appsmith.server.dtos.UpdatePermissionGroupDTO;
|
|||
import com.appsmith.server.exceptions.AppsmithError;
|
||||
import com.appsmith.server.exceptions.AppsmithException;
|
||||
import com.appsmith.server.helpers.AppsmithComparators;
|
||||
import com.appsmith.server.notifications.EmailSender;
|
||||
import com.appsmith.server.repositories.UserDataRepository;
|
||||
import com.appsmith.server.repositories.UserRepository;
|
||||
import com.appsmith.server.services.PermissionGroupService;
|
||||
import com.appsmith.server.services.SessionUserService;
|
||||
|
|
@ -21,7 +19,6 @@ import com.appsmith.server.services.TenantService;
|
|||
import com.appsmith.server.services.UserDataService;
|
||||
import com.appsmith.server.services.WorkspaceService;
|
||||
import com.appsmith.server.solutions.PermissionGroupPermission;
|
||||
import com.appsmith.server.solutions.PolicySolution;
|
||||
import com.appsmith.server.solutions.WorkspacePermission;
|
||||
import com.mongodb.client.result.UpdateResult;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
|
@ -52,9 +49,6 @@ public class UserWorkspaceServiceCEImpl implements UserWorkspaceServiceCE {
|
|||
private final SessionUserService sessionUserService;
|
||||
private final WorkspaceService workspaceService;
|
||||
private final UserRepository userRepository;
|
||||
private final UserDataRepository userDataRepository;
|
||||
private final PolicySolution policySolution;
|
||||
private final EmailSender emailSender;
|
||||
private final UserDataService userDataService;
|
||||
private final PermissionGroupService permissionGroupService;
|
||||
private final TenantService tenantService;
|
||||
|
|
@ -66,9 +60,6 @@ public class UserWorkspaceServiceCEImpl implements UserWorkspaceServiceCE {
|
|||
SessionUserService sessionUserService,
|
||||
WorkspaceService workspaceService,
|
||||
UserRepository userRepository,
|
||||
UserDataRepository userDataRepository,
|
||||
PolicySolution policySolution,
|
||||
EmailSender emailSender,
|
||||
UserDataService userDataService,
|
||||
PermissionGroupService permissionGroupService,
|
||||
TenantService tenantService,
|
||||
|
|
@ -77,9 +68,6 @@ public class UserWorkspaceServiceCEImpl implements UserWorkspaceServiceCE {
|
|||
this.sessionUserService = sessionUserService;
|
||||
this.workspaceService = workspaceService;
|
||||
this.userRepository = userRepository;
|
||||
this.userDataRepository = userDataRepository;
|
||||
this.policySolution = policySolution;
|
||||
this.emailSender = emailSender;
|
||||
this.userDataService = userDataService;
|
||||
this.permissionGroupService = permissionGroupService;
|
||||
this.tenantService = tenantService;
|
||||
|
|
@ -261,27 +249,23 @@ public class UserWorkspaceServiceCEImpl implements UserWorkspaceServiceCE {
|
|||
userIdsMono.flatMapMany(userRepository::findAllById).collectMap(User::getId);
|
||||
|
||||
// Create a map of UserData.userUd to UserData
|
||||
Mono<Map<String, UserData>> userDataMapMono = userIdsMono
|
||||
Mono<Map<String, String>> userDataMapMono = userIdsMono
|
||||
// get the profile photos of the list of users
|
||||
.flatMapMany(userIdsSet -> userDataRepository.findPhotoAssetsByUserIds(
|
||||
userIdsSet.stream().toList()))
|
||||
.collectMap(UserData::getUserId);
|
||||
.flatMap(userIdsSet -> userDataService.getProfilePhotoAssetIdsForUserIds(
|
||||
userIdsSet.stream().toList()));
|
||||
|
||||
// Update name and username in the list of UserAndGroupDTO
|
||||
userAndPermissionGroupDTOsMono = Mono.zip(userAndPermissionGroupDTOsMono, userMapMono, userDataMapMono)
|
||||
.map(tuple -> {
|
||||
List<MemberInfoDTO> workspaceMemberInfoDTOList = tuple.getT1();
|
||||
Map<String, User> userMap = tuple.getT2();
|
||||
Map<String, UserData> userDataMap = tuple.getT3();
|
||||
Map<String, String> userDataMap = tuple.getT3();
|
||||
workspaceMemberInfoDTOList.forEach(userAndPermissionGroupDTO -> {
|
||||
User user = userMap.get(userAndPermissionGroupDTO.getUserId());
|
||||
UserData userData = userDataMap.get(userAndPermissionGroupDTO.getUserId());
|
||||
userAndPermissionGroupDTO.setName(
|
||||
Optional.ofNullable(user.getName()).orElse(user.computeFirstName()));
|
||||
userAndPermissionGroupDTO.setUsername(user.getUsername());
|
||||
if (userData != null) {
|
||||
userAndPermissionGroupDTO.setPhotoId(userData.getProfilePhotoAssetId());
|
||||
}
|
||||
userAndPermissionGroupDTO.setPhotoId(userDataMap.get(userAndPermissionGroupDTO.getUserId()));
|
||||
});
|
||||
return workspaceMemberInfoDTOList;
|
||||
});
|
||||
|
|
@ -324,9 +308,8 @@ public class UserWorkspaceServiceCEImpl implements UserWorkspaceServiceCE {
|
|||
userIdsMono.flatMapMany(userRepository::findAllById).collectMap(User::getId);
|
||||
|
||||
// Create a map of UserData.userUd to UserData
|
||||
Mono<Map<String, UserData>> userDataMapMono = userIdsMono
|
||||
.flatMapMany(userDataRepository::findPhotoAssetsByUserIds)
|
||||
.collectMap(UserData::getUserId);
|
||||
Mono<Map<String, String>> userDataMapMono =
|
||||
userIdsMono.flatMap(userDataService::getProfilePhotoAssetIdsForUserIds);
|
||||
|
||||
Flux<Map<String, Collection<PermissionGroup>>> permissionGroupsByWorkspaceFlux =
|
||||
permissionGroupsByWorkspacesMono.repeat();
|
||||
|
|
@ -347,16 +330,14 @@ public class UserWorkspaceServiceCEImpl implements UserWorkspaceServiceCE {
|
|||
.map(tuple1 -> {
|
||||
List<MemberInfoDTO> workspaceMemberInfoDTOList = tuple1.getT1();
|
||||
Map<String, User> userMap = tuple1.getT2();
|
||||
Map<String, UserData> userDataMap = tuple1.getT3();
|
||||
Map<String, String> userDataMap = tuple1.getT3();
|
||||
workspaceMemberInfoDTOList.forEach(userAndPermissionGroupDTO -> {
|
||||
User user = userMap.get(userAndPermissionGroupDTO.getUserId());
|
||||
UserData userData = userDataMap.get(userAndPermissionGroupDTO.getUserId());
|
||||
userAndPermissionGroupDTO.setName(
|
||||
Optional.ofNullable(user.getName()).orElse(user.computeFirstName()));
|
||||
userAndPermissionGroupDTO.setUsername(user.getUsername());
|
||||
if (userData != null) {
|
||||
userAndPermissionGroupDTO.setPhotoId(userData.getProfilePhotoAssetId());
|
||||
}
|
||||
userAndPermissionGroupDTO.setPhotoId(
|
||||
userDataMap.get(userAndPermissionGroupDTO.getUserId()));
|
||||
});
|
||||
return workspaceMemberInfoDTOList;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.appsmith.server.repositories;
|
||||
|
||||
import com.appsmith.server.domains.UserData;
|
||||
import com.appsmith.server.projections.UserDataProfilePhotoProjection;
|
||||
import com.mongodb.client.result.UpdateResult;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
|
@ -160,29 +161,25 @@ public class CustomUserDataRepositoryTest {
|
|||
UserData userDataOne = new UserData();
|
||||
userDataOne.setUserId(firstId);
|
||||
userDataOne.setProfilePhotoAssetId(photoId);
|
||||
userDataOne.setRecentlyUsedAppIds(List.of("abc"));
|
||||
|
||||
UserData userDataTwo = new UserData();
|
||||
userDataTwo.setUserId(secondId);
|
||||
userDataTwo.setRecentlyUsedAppIds(List.of("abc"));
|
||||
|
||||
Flux<UserData> userDataFlux = userDataRepository
|
||||
Flux<UserDataProfilePhotoProjection> userDataFlux = userDataRepository
|
||||
.saveAll(List.of(userDataOne, userDataTwo))
|
||||
.map(UserData::getUserId)
|
||||
.collectList()
|
||||
.flatMapMany(userDataRepository::findPhotoAssetsByUserIds);
|
||||
.flatMapMany(userDataRepository::findByUserIdIn);
|
||||
|
||||
StepVerifier.create(userDataFlux.collectMap(UserData::getUserId))
|
||||
StepVerifier.create(userDataFlux.collectMap(UserDataProfilePhotoProjection::getUserId))
|
||||
.assertNext(userDataMap -> {
|
||||
assertThat(userDataMap.size()).isEqualTo(2);
|
||||
assertThat(userDataMap).hasSize(2);
|
||||
|
||||
UserData firstUserData = userDataMap.get(firstId);
|
||||
UserDataProfilePhotoProjection firstUserData = userDataMap.get(firstId);
|
||||
assertThat(firstUserData.getProfilePhotoAssetId()).isEqualTo(photoId);
|
||||
assertThat(firstUserData.getRecentlyUsedAppIds()).isNull();
|
||||
|
||||
UserData secondUserData = userDataMap.get(secondId);
|
||||
UserDataProfilePhotoProjection secondUserData = userDataMap.get(secondId);
|
||||
assertThat(secondUserData.getProfilePhotoAssetId()).isNull();
|
||||
assertThat(secondUserData.getRecentlyUsedAppIds()).isNull();
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user