diff --git a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/repository/ApplicationRepository.java b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/repository/ApplicationRepository.java index ac37c6e9d..7c42ccc73 100644 --- a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/repository/ApplicationRepository.java +++ b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/repository/ApplicationRepository.java @@ -38,6 +38,8 @@ public interface ApplicationRepository extends ReactiveMongoRepository findByPublicToAllIsTrueAndPublicToMarketplaceIsOrAgencyProfileIsAndIdIn (Boolean publicToMarketplace, Boolean agencyProfile, Collection ids); + Flux findByPublicToAllIsTrueAndPublicToMarketplaceIsAndAgencyProfileIsAndIdIn(Boolean publicToMarketplace, Boolean agencyProfile, Collection ids); + Flux findByPublicToAllIsTrueAndPublicToMarketplaceIsTrue(); Flux findByPublicToAllIsTrueAndAgencyProfileIsTrue(); diff --git a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/service/ApplicationService.java b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/service/ApplicationService.java index a97f8ca67..917d0762e 100644 --- a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/service/ApplicationService.java +++ b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/service/ApplicationService.java @@ -3,10 +3,7 @@ import static org.lowcoder.domain.application.ApplicationUtil.getDependentModulesFromDsl; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; import org.lowcoder.domain.application.model.Application; @@ -155,11 +152,43 @@ public Mono setApplicationPublicToAll(String applicationId, boolean pub return mongoUpsertHelper.updateById(application, applicationId); } - public Mono setApplicationPublicToMarketplace(String applicationId, boolean publicToMarketplace) { - Application application = Application.builder() - .publicToMarketplace(publicToMarketplace) - .build(); - return mongoUpsertHelper.updateById(application, applicationId); + public Mono setApplicationPublicToMarketplace(String applicationId, Boolean publicToMarketplace, + String title, String category, String description, String image) { + + return findById(applicationId) + .map(application -> { + Map applicationDsl = application.getEditingApplicationDSL(); + if (applicationDsl.containsKey("ui")) { + Map dataObject = (Map) applicationDsl.get("ui"); + + if(publicToMarketplace) { + Map marketplaceMeta = new HashMap<>(); + marketplaceMeta.put("title", title); + marketplaceMeta.put("description", description); + marketplaceMeta.put("category", category); + marketplaceMeta.put("image", image); + if (dataObject.containsKey("marketplaceMeta")) { + dataObject.replace("marketplaceMeta", marketplaceMeta); + } else { + dataObject.put("marketplaceMeta", marketplaceMeta); + } + } else { + dataObject.remove("marketplaceMeta"); + } + + applicationDsl.replace("ui", dataObject); + + } + + return Application.builder() + .publicToMarketplace(publicToMarketplace) + .editingApplicationDSL(applicationDsl) + .build(); + + }) + .flatMap(application -> mongoUpsertHelper.updateById(application, applicationId)); + + } public Mono setApplicationAsAgencyProfile(String applicationId, boolean agencyProfile) { @@ -171,10 +200,24 @@ public Mono setApplicationAsAgencyProfile(String applicationId, boolean @NonEmptyMono @SuppressWarnings("ReactiveStreamsNullableInLambdaInTransform") - public Mono> getPublicApplicationIds(Collection applicationIds, Boolean isAnonymous) { - return repository.findByPublicToAllIsTrueAndPublicToMarketplaceIsOrAgencyProfileIsAndIdIn(!isAnonymous, !isAnonymous, applicationIds) - .map(HasIdAndAuditing::getId) - .collect(Collectors.toSet()); + public Mono> getPublicApplicationIds(Collection applicationIds, Boolean isAnonymous, Boolean isPrivateMarketplace) { + + if(isAnonymous) { + if(isPrivateMarketplace) { + return repository.findByPublicToAllIsTrueAndPublicToMarketplaceIsAndAgencyProfileIsAndIdIn(false, false, applicationIds) + .map(HasIdAndAuditing::getId) + .collect(Collectors.toSet()); + } else { + return repository.findByPublicToAllIsTrueAndPublicToMarketplaceIsAndAgencyProfileIsAndIdIn(true, false, applicationIds) + .map(HasIdAndAuditing::getId) + .collect(Collectors.toSet()); + } + } else { + return repository.findByPublicToAllIsTrueAndPublicToMarketplaceIsOrAgencyProfileIsAndIdIn(true, true, applicationIds) + .map(HasIdAndAuditing::getId) + .collect(Collectors.toSet()); + } + } } diff --git a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/permission/service/ApplicationPermissionHandler.java b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/permission/service/ApplicationPermissionHandler.java index d93585bb0..5d6448d13 100644 --- a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/permission/service/ApplicationPermissionHandler.java +++ b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/permission/service/ApplicationPermissionHandler.java @@ -46,7 +46,7 @@ protected Mono>> getAnonymousUserPermission } Set applicationIds = newHashSet(resourceIds); - return Mono.zip(applicationService.getPublicApplicationIds(applicationIds, Boolean.TRUE), + return Mono.zip(applicationService.getPublicApplicationIds(applicationIds, Boolean.TRUE, config.getMarketplace().isPrivateMode()), templateSolution.getTemplateApplicationIds(applicationIds)) .map(tuple -> { Set publicAppIds = tuple.getT1(); @@ -61,7 +61,7 @@ protected Mono>> getAnonymousUserPermission (Collection resourceIds, ResourceAction resourceAction) { Set applicationIds = newHashSet(resourceIds); - return Mono.zip(applicationService.getPublicApplicationIds(applicationIds, Boolean.FALSE), + return Mono.zip(applicationService.getPublicApplicationIds(applicationIds, Boolean.FALSE, config.getMarketplace().isPrivateMode()), templateSolution.getTemplateApplicationIds(applicationIds)) .map(tuple -> { Set publicAppIds = tuple.getT1(); diff --git a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/permission/service/ResourcePermissionHandler.java b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/permission/service/ResourcePermissionHandler.java index 762e9bcb1..09efd31f2 100644 --- a/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/permission/service/ResourcePermissionHandler.java +++ b/server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/permission/service/ResourcePermissionHandler.java @@ -26,6 +26,7 @@ import org.lowcoder.domain.permission.model.ResourceRole; import org.lowcoder.domain.permission.model.ResourceType; import org.lowcoder.domain.permission.model.UserPermissionOnResourceStatus; +import org.lowcoder.sdk.config.CommonConfig; import org.springframework.beans.factory.annotation.Autowired; import com.google.common.collect.Maps; @@ -44,6 +45,9 @@ abstract class ResourcePermissionHandler { @Autowired private OrgMemberService orgMemberService; + @Autowired + protected CommonConfig config; + public Mono>> getAllMatchingPermissions(String userId, Collection resourceIds, ResourceAction resourceAction) { diff --git a/server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/config/CommonConfig.java b/server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/config/CommonConfig.java index 7d32ed0d8..d1fcf3ea8 100644 --- a/server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/config/CommonConfig.java +++ b/server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/config/CommonConfig.java @@ -44,6 +44,7 @@ public class CommonConfig { private Cookie cookie = new Cookie(); private JsExecutor jsExecutor = new JsExecutor(); private Set disallowedHosts = new HashSet<>(); + private Marketplace marketplace = new Marketplace(); public boolean isSelfHost() { return !isCloud(); @@ -145,6 +146,12 @@ public static class JsExecutor { private String host; } + @Data + public static class Marketplace { + + private boolean privateMode = Boolean.TRUE; + } + @Getter @Setter 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 f1eaad172..fb05dcf87 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 @@ -514,10 +514,11 @@ public Mono setApplicationPublicToAll(String applicationId, boolean pub .then(applicationService.setApplicationPublicToAll(applicationId, publicToAll)); } - public Mono setApplicationPublicToMarketplace(String applicationId, boolean publicToMarketplace) { + public Mono setApplicationPublicToMarketplace(String applicationId, ApplicationEndpoints.ApplicationPublicToMarketplaceRequest request) { return checkCurrentUserApplicationPermission(applicationId, ResourceAction.SET_APPLICATIONS_PUBLIC_TO_MARKETPLACE) .then(checkApplicationStatus(applicationId, NORMAL)) - .then(applicationService.setApplicationPublicToMarketplace(applicationId, publicToMarketplace)); + .then(applicationService.setApplicationPublicToMarketplace + (applicationId, request.publicToMarketplace(), request.title(), request.category(), request.description(), request.image())); } public Mono setApplicationAsAgencyProfile(String applicationId, boolean agencyProfile) { 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 e75de598d..86be1e576 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 @@ -17,6 +17,7 @@ import org.lowcoder.api.application.view.ApplicationView; import org.lowcoder.api.application.view.MarketplaceApplicationInfoView; import org.lowcoder.api.framework.view.ResponseView; +import org.lowcoder.api.home.SessionUserService; import org.lowcoder.api.home.UserHomeApiService; import org.lowcoder.api.home.UserHomepageView; import org.lowcoder.api.util.BusinessEventPublisher; @@ -39,6 +40,7 @@ public class ApplicationController implements ApplicationEndpoints { private final UserHomeApiService userHomeApiService; private final ApplicationApiService applicationApiService; private final BusinessEventPublisher businessEventPublisher; + private final SessionUserService sessionUserService; @Override public Mono> create(@RequestBody CreateApplicationRequest createApplicationRequest) { @@ -155,7 +157,7 @@ public Mono>> getMarketplaceAp @Override public Mono>> getAgencyProfileApplications(@RequestParam(required = false) Integer applicationType) { ApplicationType applicationTypeEnum = applicationType == null ? null : ApplicationType.fromValue(applicationType); - return userHomeApiService.getAllMarketplaceApplications(applicationTypeEnum) + return userHomeApiService.getAllAgencyProfileApplications(applicationTypeEnum) .collectList() .map(ResponseView::success); } @@ -214,7 +216,7 @@ public Mono> setApplicationPublicToAll(@PathVariable Strin @Override public Mono> setApplicationPublicToMarketplace(@PathVariable String applicationId, @RequestBody ApplicationPublicToMarketplaceRequest request) { - return applicationApiService.setApplicationPublicToMarketplace(applicationId, request.publicToMarketplace()) + return applicationApiService.setApplicationPublicToMarketplace(applicationId, request) .map(ResponseView::success); } 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 eae901f21..2ac323289 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 @@ -270,11 +270,33 @@ public Boolean publicToAll() { } } - public record ApplicationPublicToMarketplaceRequest(Boolean publicToMarketplace) { + public record ApplicationPublicToMarketplaceRequest(Boolean publicToMarketplace, String title, + String description, String category, String image) { @Override public Boolean publicToMarketplace() { return BooleanUtils.isTrue(publicToMarketplace); } + + @Override + public String title() { + return title; + } + + @Override + public String description() { + return description; + } + + @Override + public String category() { + return category; + } + + @Override + public String image() { + return image; + } + } public record ApplicationAsAgencyProfileRequest(Boolean agencyProfile) { diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/view/MarketplaceApplicationInfoView.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/view/MarketplaceApplicationInfoView.java index b45ffd499..0de5a6aaf 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/view/MarketplaceApplicationInfoView.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/view/MarketplaceApplicationInfoView.java @@ -2,12 +2,20 @@ import lombok.Builder; import lombok.Getter; +import lombok.Setter; import org.lowcoder.domain.application.model.ApplicationStatus; @Builder @Getter +@Setter public class MarketplaceApplicationInfoView { + // marketplace specific details + private String title; + private String description; + private String category; + private String image; + // org details private final String orgId; private final String orgName; diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/framework/security/SecurityConfig.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/framework/security/SecurityConfig.java index 376fc3c4b..b933a63e1 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/framework/security/SecurityConfig.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/framework/security/SecurityConfig.java @@ -108,6 +108,9 @@ SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, CONFIG_URL), // system config ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, CONFIG_URL + "/deploymentId"), // system config ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, APPLICATION_URL + "/*/view"), // application view + ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, APPLICATION_URL + "/*/view_marketplace"), // application view + ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, APPLICATION_URL + "/marketplace-apps"), // marketplace apps + ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, USER_URL + "/me"), ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, USER_URL + "/currentUser"), @@ -132,6 +135,8 @@ SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, NewUrl.CONFIG_URL + "/deploymentId"), ServerWebExchangeMatchers.pathMatchers(HttpMethod.HEAD, NewUrl.STATE_URL + "/healthCheck"), ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, NewUrl.APPLICATION_URL + "/*/view"), + ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, NewUrl.APPLICATION_URL + "/*/view_marketplace"), + ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, NewUrl.APPLICATION_URL + "/marketplace-apps"), // marketplace apps ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, NewUrl.USER_URL + "/me"), ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, NewUrl.USER_URL + "/currentUser"), ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, NewUrl.GROUP_URL + "/list"), @@ -177,6 +182,8 @@ private CorsConfigurationSource buildCorsConfigurationSource() { source.registerCorsConfiguration(GROUP_URL + "/list", skipCheckCorsForAll); source.registerCorsConfiguration(QUERY_URL + "/execute", skipCheckCorsForAll); source.registerCorsConfiguration(APPLICATION_URL + "/*/view", skipCheckCorsForAll); + source.registerCorsConfiguration(APPLICATION_URL + "/*/view_marketplace", skipCheckCorsForAll); + source.registerCorsConfiguration(APPLICATION_URL + "/marketplace-apps", skipCheckCorsForAll); source.registerCorsConfiguration(GITHUB_STAR, skipCheckCorsForAll); source.registerCorsConfiguration(ORGANIZATION_URL + "/*/datasourceTypes", skipCheckCorsForAll); source.registerCorsConfiguration(DATASOURCE_URL + "/jsDatasourcePlugins", skipCheckCorsForAll); @@ -186,6 +193,8 @@ private CorsConfigurationSource buildCorsConfigurationSource() { source.registerCorsConfiguration(NewUrl.GROUP_URL + "/list", skipCheckCorsForAll); source.registerCorsConfiguration(NewUrl.QUERY_URL + "/execute", skipCheckCorsForAll); source.registerCorsConfiguration(NewUrl.APPLICATION_URL + "/*/view", skipCheckCorsForAll); + source.registerCorsConfiguration(NewUrl.APPLICATION_URL + "/*/view_marketplace", skipCheckCorsForAll); + source.registerCorsConfiguration(NewUrl.APPLICATION_URL + "/marketplace-apps", skipCheckCorsForAll); source.registerCorsConfiguration(NewUrl.ORGANIZATION_URL + "/*/datasourceTypes", skipCheckCorsForAll); source.registerCorsConfiguration(NewUrl.DATASOURCE_URL + "/jsDatasourcePlugins", skipCheckCorsForAll); diff --git a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/UserHomeApiServiceImpl.java b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/UserHomeApiServiceImpl.java index e10fe0ea1..2662900dd 100644 --- a/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/UserHomeApiServiceImpl.java +++ b/server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/UserHomeApiServiceImpl.java @@ -40,6 +40,7 @@ import org.lowcoder.domain.user.service.UserService; import org.lowcoder.domain.user.service.UserStatusService; import org.lowcoder.infra.util.NetworkUtils; +import org.lowcoder.sdk.config.CommonConfig; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; @@ -80,6 +81,9 @@ public class UserHomeApiServiceImpl implements UserHomeApiService { @Autowired private UserApplicationInteractionService userApplicationInteractionService; + @Autowired + private CommonConfig config; + @Override public Mono buildUserProfileView(User user, ServerWebExchange exchange) { @@ -260,8 +264,13 @@ public Flux getAllAuthorisedApplications4CurrentOrgMember(@ @Override public Flux getAllMarketplaceApplications(@Nullable ApplicationType applicationType) { - return sessionUserService.getVisitorOrgMemberCache() - .flatMapMany(orgMember -> { + return sessionUserService.isAnonymousUser() + .flatMapMany(isAnonymousUser -> { + + if(config.getMarketplace().isPrivateMode() && isAnonymousUser) { + return Mono.empty(); + } + // application flux Flux applicationFlux = Flux.defer(() -> applicationService.findAllMarketplaceApps()) .filter(application -> isNull(applicationType) || application.getApplicationType() == applicationType.getValue()) @@ -287,12 +296,12 @@ public Flux getAllMarketplaceApplications(@Nulla return applicationFlux .flatMap(application -> Mono.zip(Mono.just(application), userMapMono, orgMapMono)) - .map(tuple -> { + .map(tuple2 -> { // build view - Application application = tuple.getT1(); - Map userMap = tuple.getT2(); - Map orgMap = tuple.getT3(); - return MarketplaceApplicationInfoView.builder() + Application application = tuple2.getT1(); + Map userMap = tuple2.getT2(); + Map orgMap = tuple2.getT3(); + MarketplaceApplicationInfoView marketplaceApplicationInfoView = MarketplaceApplicationInfoView.builder() .applicationId(application.getId()) .name(application.getName()) .applicationType(application.getApplicationType()) @@ -305,6 +314,17 @@ public Flux getAllMarketplaceApplications(@Nulla .createAt(application.getCreatedAt().toEpochMilli()) .createBy(application.getCreatedBy()) .build(); + + // marketplace specific fields + Map marketplaceMeta = (Map) + ((Map)application.getEditingApplicationDSL().get("ui")).get("marketplaceMeta"); + marketplaceApplicationInfoView.setTitle((String)marketplaceMeta.get("title")); + marketplaceApplicationInfoView.setCategory((String)marketplaceMeta.get("category")); + marketplaceApplicationInfoView.setDescription((String)marketplaceMeta.get("description")); + marketplaceApplicationInfoView.setImage((String)marketplaceMeta.get("image")); + + return marketplaceApplicationInfoView; + }); }); diff --git a/server/api-service/lowcoder-server/src/main/resources/application-lowcoder.yml b/server/api-service/lowcoder-server/src/main/resources/application-lowcoder.yml index 75ae0dba9..66d022e68 100644 --- a/server/api-service/lowcoder-server/src/main/resources/application-lowcoder.yml +++ b/server/api-service/lowcoder-server/src/main/resources/application-lowcoder.yml @@ -44,6 +44,8 @@ common: block-hound-enable: false js-executor: host: http://127.0.0.1:6060 + marketplace: + private-mode: false material: mongodb-grid-fs: diff --git a/server/api-service/lowcoder-server/src/main/resources/selfhost/ce/application.yml b/server/api-service/lowcoder-server/src/main/resources/selfhost/ce/application.yml index df8301982..11c0511c2 100644 --- a/server/api-service/lowcoder-server/src/main/resources/selfhost/ce/application.yml +++ b/server/api-service/lowcoder-server/src/main/resources/selfhost/ce/application.yml @@ -53,6 +53,8 @@ common: max-query-timeout: ${LOWCODER_MAX_QUERY_TIMEOUT:120} workspace: mode: ${LOWCODER_WORKSPACE_MODE:SAAS} + marketplace: + private-mode: ${MARKETPLACE_PRIVATE_MODE:true} material: mongodb-grid-fs: