From e79a4f8ff4d619421c2b64dbaf1e1df203cf5b5a Mon Sep 17 00:00:00 2001 From: Thomasr Date: Tue, 24 Jun 2025 04:58:46 -0400 Subject: [PATCH] Feat: add API to list groups and users without permissions, with search and pagination support --- .../application/ApplicationApiService.java | 3 + .../ApplicationApiServiceImpl.java | 73 ++++++++++++++++++- .../application/ApplicationController.java | 35 ++++++++- .../api/application/ApplicationEndpoints.java | 14 ++++ 4 files changed, 120 insertions(+), 5 deletions(-) diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationApiService.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationApiService.java index fdb40cc04..a1b655bbe 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationApiService.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationApiService.java @@ -15,6 +15,7 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import java.util.List; import java.util.Set; public interface ApplicationApiService { @@ -66,4 +67,6 @@ Mono grantPermission(String applicationId, Mono setApplicationAsAgencyProfile(String applicationId, boolean agencyProfile); Mono updateSlug(String applicationId, String slug); + + Mono> getGroupsOrMembersWithoutPermissions(String appId); } diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationApiServiceImpl.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationApiServiceImpl.java index 89ff852d8..ce40d6cc1 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationApiServiceImpl.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationApiServiceImpl.java @@ -22,7 +22,11 @@ import org.lowcoder.api.home.UserHomeApiService; import org.lowcoder.api.permission.PermissionHelper; import org.lowcoder.api.permission.view.PermissionItemView; +import org.lowcoder.api.usermanagement.GroupApiService; +import org.lowcoder.api.usermanagement.OrgApiService; import org.lowcoder.api.usermanagement.OrgDevChecker; +import org.lowcoder.api.usermanagement.view.GroupView; +import org.lowcoder.api.usermanagement.view.OrgMemberListView; import org.lowcoder.domain.application.model.*; import org.lowcoder.domain.application.service.ApplicationHistorySnapshotService; import org.lowcoder.domain.application.service.ApplicationRecordService; @@ -30,7 +34,10 @@ import org.lowcoder.domain.datasource.model.Datasource; import org.lowcoder.domain.datasource.service.DatasourceService; import org.lowcoder.domain.folder.service.FolderElementRelationService; +import org.lowcoder.domain.group.model.Group; +import org.lowcoder.domain.group.model.GroupMember; import org.lowcoder.domain.interaction.UserApplicationInteractionService; +import org.lowcoder.domain.organization.model.OrgMember; import org.lowcoder.domain.organization.model.Organization; import org.lowcoder.domain.organization.service.OrgMemberService; import org.lowcoder.domain.organization.service.OrganizationService; @@ -54,13 +61,11 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.core.scheduler.Schedulers; +import reactor.util.function.Tuple2; import java.time.Duration; import java.time.Instant; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; import static org.lowcoder.domain.application.model.ApplicationStatus.NORMAL; @@ -99,6 +104,8 @@ public class ApplicationApiServiceImpl implements ApplicationApiService { private final ApplicationHistorySnapshotService applicationHistorySnapshotService; private final ApplicationRecordService applicationRecordService; private final FolderElementRelationService folderElementRelationService; + private final GroupApiService groupApiService; + private final OrgApiService orgApiService; @Override public Mono create(CreateApplicationRequest createApplicationRequest) { @@ -743,4 +750,62 @@ private Mono checkDatasourcePermissions(Application application) { }); }); } + + @Override + public Mono> getGroupsOrMembersWithoutPermissions(String appId) { + return applicationService.findById(appId) + .flatMap(application -> { + String orgId = application.getOrganizationId(); + Mono> applicationPermissions = resourcePermissionService.getByApplicationId(application.getId()).cache(); + + Mono> groupPermissionPairsMono = applicationPermissions + .flatMap(permissionHelper::getGroupPermissions); + + Mono> userPermissionPairsMono = applicationPermissions + .flatMap(permissionHelper::getUserPermissions); + Mono orgMemberListViewMono = orgApiService.getOrganizationMembers(orgId, 1, 0); + Mono> groupsViewMono = groupApiService.getGroups(); + + return Mono.zip(groupPermissionPairsMono, userPermissionPairsMono, orgMemberListViewMono, groupsViewMono) + .map(tuple -> { + List groupPermissionPairs = tuple.getT1(); + List userPermissionPairs = tuple.getT2(); + OrgMemberListView orgMemberListViews = tuple.getT3(); + List groupListViews = tuple.getT4(); + + Set groupIdsWithPerm = groupPermissionPairs.stream() + .map(PermissionItemView::getId) + .collect(Collectors.toSet()); + + List> filteredGroups = groupListViews.stream() + .filter(group -> !groupIdsWithPerm.contains(group.getGroupId())) + .map(group -> { + Map map = new HashMap<>(); + map.put("type", "Group"); + map.put("data", group); + return map; + }) + .toList(); + + Set userIdsWithPerm = userPermissionPairs.stream() + .map(PermissionItemView::getId) + .collect(Collectors.toSet()); + + List> filteredMembers = orgMemberListViews.getMembers().stream() + .filter(member -> !userIdsWithPerm.contains(member.getUserId())) + .map(member -> { + Map map = new HashMap<>(); + map.put("type", "User"); + map.put("data", member); + return map; + }) + .toList(); + + List result = new ArrayList<>(); + result.addAll(filteredGroups); + result.addAll(filteredMembers); + return result; + }); + }); + } } diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationController.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationController.java index 42ec869ec..cfec54a13 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationController.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationController.java @@ -27,6 +27,8 @@ import static org.lowcoder.plugin.api.event.LowcoderEvent.EventType.*; import static org.lowcoder.sdk.exception.BizError.INVALID_PARAMETER; import static org.lowcoder.sdk.util.ExceptionUtils.ofError; +import reactor.core.publisher.Flux; +import java.util.Map; @RequiredArgsConstructor @RestController @@ -298,5 +300,36 @@ public Mono> setApplicationAsAgencyProfile(@PathVariable S .map(ResponseView::success)); } - + @Override + public Mono>> getGroupsOrMembersWithoutPermissions( + @PathVariable String applicationId, + @RequestParam(required = false) String search, + @RequestParam(required = false, defaultValue = "1") Integer pageNum, + @RequestParam(required = false, defaultValue = "1000") Integer pageSize) { + + return gidService.convertLibraryQueryIdToObjectId(applicationId).flatMap(appId -> { + var flx = applicationApiService.getGroupsOrMembersWithoutPermissions(appId) + .flatMapMany(Flux::fromIterable) + .filter(item -> { + if (search == null || search.isBlank()) return true; + if (!(item instanceof Map map)) return false; + Object type = map.get("type"); + Object data = map.get("data"); + if ("Group".equals(type) && data instanceof org.lowcoder.api.usermanagement.view.GroupView group) { + return group.getGroupName() != null && group.getGroupName().toLowerCase().contains(search.toLowerCase()); + } + if ("User".equals(type) && data instanceof org.lowcoder.api.usermanagement.view.OrgMemberListView.OrgMemberView user) { + return user.getName() != null && user.getName().toLowerCase().contains(search.toLowerCase()); + } + return false; + }) + .cache(); + var countMono = flx.count(); + var flux1 = flx.skip((long) (pageNum - 1) * pageSize); + if (pageSize > 0) flux1 = flux1.take(pageSize); + return flux1.collectList() + .zipWith(countMono) + .map(tuple -> PageResponseView.success(tuple.getT1(), pageNum, pageSize, Math.toIntExact(tuple.getT2()))); + }); + } } diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationEndpoints.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationEndpoints.java index 21615764c..499862e8d 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationEndpoints.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationEndpoints.java @@ -245,6 +245,20 @@ public Mono> grantPermission( @GetMapping("/{applicationId}/permissions") public Mono> getApplicationPermissions(@PathVariable String applicationId); + @Operation( + tags = ApplicationEndpoints.TAG_APPLICATION_PERMISSIONS, + operationId = "listGroupsOrMembersWithoutPermissions", + summary = "Get groups or members without permissions", + description = "Retrieve the groups or members of a specific Lowcoder Application identified by its ID that do not have permissions." + ) + @GetMapping("/{applicationId}/get_groups_or_members_without_permissions") + public Mono>> getGroupsOrMembersWithoutPermissions( + @PathVariable String applicationId, + @RequestParam(required = false) String search, + @RequestParam(required = false, defaultValue = "1") Integer pageNum, + @RequestParam(required = false, defaultValue = "1000") Integer pageSize + ); + @Operation( tags = TAG_APPLICATION_MANAGEMENT, operationId = "setApplicationAsPublic",