diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/datasource/DatasourceApiService.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/datasource/DatasourceApiService.java index 4df2cd16a..6f68972c8 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/datasource/DatasourceApiService.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/datasource/DatasourceApiService.java @@ -1,18 +1,18 @@ package org.lowcoder.api.datasource; -import jakarta.annotation.Nullable; +import java.util.List; +import java.util.Set; + import org.lowcoder.api.permission.view.CommonPermissionView; import org.lowcoder.domain.datasource.model.Datasource; import org.lowcoder.domain.permission.model.ResourceRole; import org.lowcoder.domain.plugin.client.dto.GetPluginDynamicConfigRequestDTO; import org.lowcoder.sdk.models.DatasourceTestResult; -import org.springframework.web.bind.annotation.RequestParam; + +import jakarta.annotation.Nullable; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import java.util.List; -import java.util.Set; - public interface DatasourceApiService { Mono create(Datasource datasource); @@ -41,4 +41,6 @@ public interface DatasourceApiService { Mono updatePermission(String permissionId, ResourceRole role); Mono deletePermission(String permissionId); + + Mono> getGroupsOrMembersWithoutPermissions(String datasourceId); } diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/datasource/DatasourceApiServiceImpl.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/datasource/DatasourceApiServiceImpl.java index 16758884a..7db2191cc 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/datasource/DatasourceApiServiceImpl.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/datasource/DatasourceApiServiceImpl.java @@ -1,8 +1,19 @@ package org.lowcoder.api.datasource; -import com.github.f4b6a3.uuid.UuidCreator; -import jakarta.annotation.Nullable; -import lombok.RequiredArgsConstructor; +import static org.lowcoder.domain.permission.model.ResourceAction.MANAGE_DATASOURCES; +import static org.lowcoder.domain.permission.model.ResourceAction.READ_APPLICATIONS; +import static org.lowcoder.domain.permission.model.ResourceAction.USE_DATASOURCES; +import static org.lowcoder.sdk.exception.BizError.NOT_AUTHORIZED; +import static org.lowcoder.sdk.util.ExceptionUtils.deferredError; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.lowcoder.api.application.ApplicationApiService; @@ -10,7 +21,11 @@ import org.lowcoder.api.permission.PermissionHelper; import org.lowcoder.api.permission.view.CommonPermissionView; 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.service.ApplicationService; import org.lowcoder.domain.datasource.model.Datasource; import org.lowcoder.domain.datasource.model.DatasourceStatus; @@ -38,18 +53,13 @@ import org.lowcoder.sdk.models.HasIdAndAuditing; import org.lowcoder.sdk.models.JsDatasourceConnectionConfig; import org.springframework.stereotype.Service; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; +import com.github.f4b6a3.uuid.UuidCreator; -import static org.lowcoder.domain.permission.model.ResourceAction.*; -import static org.lowcoder.sdk.exception.BizError.NOT_AUTHORIZED; -import static org.lowcoder.sdk.util.ExceptionUtils.deferredError; +import jakarta.annotation.Nullable; +import lombok.RequiredArgsConstructor; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; @RequiredArgsConstructor @Service @@ -71,6 +81,8 @@ public class DatasourceApiServiceImpl implements DatasourceApiService { private final DatasourcePluginClient datasourcePluginClient; private final DatasourceRepository datasourceRepository; private final ApplicationApiService applicationApiService; + private final OrgApiService orgApiService; + private final GroupApiService groupApiService; @Override public Mono create(Datasource datasource) { @@ -267,6 +279,66 @@ public Mono grantPermission(String datasourceId, @Nullable Set .thenReturn(true); } + @Override + public Mono> getGroupsOrMembersWithoutPermissions(String datasourceId) { + return datasourceService.getById(datasourceId) + .switchIfEmpty(Mono.error(new ServerException("data source not exist. {}", datasourceId))) + .flatMap(datasource -> { + String orgId = datasource.getOrganizationId(); + Mono> datasourcePermissions = resourcePermissionService.getByDataSourceId(datasource.getId()).cache(); + + Mono> groupPermissionPairsMono = datasourcePermissions + .flatMap(permissionHelper::getGroupPermissions); + + Mono> userPermissionPairsMono = datasourcePermissions + .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(java.util.stream.Collectors.toSet()); + + List> filteredGroups = groupListViews.stream() + .filter(group -> !groupIdsWithPerm.contains(group.getGroupId())) + .map(group -> { + java.util.Map map = new java.util.HashMap<>(); + map.put("type", "Group"); + map.put("data", group); + return map; + }) + .toList(); + + Set userIdsWithPerm = userPermissionPairs.stream() + .map(PermissionItemView::getId) + .collect(java.util.stream.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; + }); + }); + } + + @Override public Mono updatePermission(String permissionId, ResourceRole role) { return checkBeforePermissionDeleteOrUpdate(permissionId) diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/datasource/DatasourceController.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/datasource/DatasourceController.java index ef4136706..444548f0e 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/datasource/DatasourceController.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/datasource/DatasourceController.java @@ -10,6 +10,8 @@ import org.lowcoder.api.framework.view.PageResponseView; import org.lowcoder.api.framework.view.ResponseView; import org.lowcoder.api.permission.view.CommonPermissionView; +import org.lowcoder.api.usermanagement.view.GroupView; +import org.lowcoder.api.usermanagement.view.OrgMemberListView; import org.lowcoder.api.usermanagement.view.UpdateGroupRequest; import org.lowcoder.api.util.BusinessEventPublisher; import org.lowcoder.api.util.GidService; @@ -26,17 +28,17 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import java.util.Collections; -import java.util.List; -import java.util.Locale; +import java.util.*; import static org.lowcoder.api.util.Pagination.fluxToPageResponseView; 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 static org.lowcoder.sdk.util.LocaleUtils.getLocale; +import static reactor.core.publisher.Flux.fromIterable; @RequiredArgsConstructor @RestController @@ -227,4 +229,38 @@ public Mono> info(@RequestParam(required = false) String da Mono.just(ResponseView.success(datasourceApiService.info(objectId)))); } + @Override + public Mono> getGroupsOrMembersWithoutPermissions( + @PathVariable String datasourceId, + @RequestParam(required = false) String search, + @RequestParam(required = false, defaultValue = "1") Integer pageNum, + @RequestParam(required = false, defaultValue = "1000") Integer pageSize) { + + return gidService.convertDatasourceIdToObjectId(datasourceId).flatMap(dsId -> { + var flx = datasourceApiService.getGroupsOrMembersWithoutPermissions(dsId) + .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 GroupView group) { + return group.getGroupName() != null && group.getGroupName().toLowerCase().contains(search.toLowerCase()); + } + if ("User".equals(type) && data instanceof 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/datasource/DatasourceEndpoints.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/datasource/DatasourceEndpoints.java index 775d70229..fb8ffeca3 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/datasource/DatasourceEndpoints.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/datasource/DatasourceEndpoints.java @@ -190,6 +190,21 @@ public Mono> updatePermission(@PathVariable("permissionId" @GetMapping("/info") public Mono> info(@RequestParam(required = false) String datasourceId); + + @Operation( + tags = TAG_DATASOURCE_PERMISSIONS, + operationId = "listGroupsOrMembersWithoutPermissionsForDatasource", + summary = "Get groups or members without permissions for datasource", + description = "Retrieve the groups or members of a specific datasource identified by its ID that do not have permissions." + ) + @GetMapping("/{datasourceId}/available") + public Mono> getGroupsOrMembersWithoutPermissions( + @PathVariable String datasourceId, + @RequestParam(required = false) String search, + @RequestParam(required = false, defaultValue = "1") Integer pageNum, + @RequestParam(required = false, defaultValue = "1000") Integer pageSize + ); + public record BatchAddPermissionRequest(String role, Set userIds, Set groupIds) { }