Skip to content

Commit b3a33dd

Browse files
authored
Merge pull request #707 from lowcoder-org/ptm-apps-enhancements
Private/Public Marketplace Feature
2 parents 5cfac16 + ac12f11 commit b3a33dd

File tree

13 files changed

+149
-27
lines changed

13 files changed

+149
-27
lines changed

server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/repository/ApplicationRepository.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ public interface ApplicationRepository extends ReactiveMongoRepository<Applicati
3838
Flux<Application> findByPublicToAllIsTrueAndPublicToMarketplaceIsOrAgencyProfileIsAndIdIn
3939
(Boolean publicToMarketplace, Boolean agencyProfile, Collection<String> ids);
4040

41+
Flux<Application> findByPublicToAllIsTrueAndPublicToMarketplaceIsAndAgencyProfileIsAndIdIn(Boolean publicToMarketplace, Boolean agencyProfile, Collection<String> ids);
42+
4143
Flux<Application> findByPublicToAllIsTrueAndPublicToMarketplaceIsTrue();
4244

4345
Flux<Application> findByPublicToAllIsTrueAndAgencyProfileIsTrue();

server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/application/service/ApplicationService.java

Lines changed: 56 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,7 @@
33

44
import static org.lowcoder.domain.application.ApplicationUtil.getDependentModulesFromDsl;
55

6-
import java.util.Collection;
7-
import java.util.List;
8-
import java.util.Map;
9-
import java.util.Set;
6+
import java.util.*;
107
import java.util.stream.Collectors;
118

129
import org.lowcoder.domain.application.model.Application;
@@ -155,11 +152,43 @@ public Mono<Boolean> setApplicationPublicToAll(String applicationId, boolean pub
155152
return mongoUpsertHelper.updateById(application, applicationId);
156153
}
157154

158-
public Mono<Boolean> setApplicationPublicToMarketplace(String applicationId, boolean publicToMarketplace) {
159-
Application application = Application.builder()
160-
.publicToMarketplace(publicToMarketplace)
161-
.build();
162-
return mongoUpsertHelper.updateById(application, applicationId);
155+
public Mono<Boolean> setApplicationPublicToMarketplace(String applicationId, Boolean publicToMarketplace,
156+
String title, String category, String description, String image) {
157+
158+
return findById(applicationId)
159+
.map(application -> {
160+
Map<String, Object> applicationDsl = application.getEditingApplicationDSL();
161+
if (applicationDsl.containsKey("ui")) {
162+
Map<String, Object> dataObject = (Map<String, Object>) applicationDsl.get("ui");
163+
164+
if(publicToMarketplace) {
165+
Map<String, Object> marketplaceMeta = new HashMap<>();
166+
marketplaceMeta.put("title", title);
167+
marketplaceMeta.put("description", description);
168+
marketplaceMeta.put("category", category);
169+
marketplaceMeta.put("image", image);
170+
if (dataObject.containsKey("marketplaceMeta")) {
171+
dataObject.replace("marketplaceMeta", marketplaceMeta);
172+
} else {
173+
dataObject.put("marketplaceMeta", marketplaceMeta);
174+
}
175+
} else {
176+
dataObject.remove("marketplaceMeta");
177+
}
178+
179+
applicationDsl.replace("ui", dataObject);
180+
181+
}
182+
183+
return Application.builder()
184+
.publicToMarketplace(publicToMarketplace)
185+
.editingApplicationDSL(applicationDsl)
186+
.build();
187+
188+
})
189+
.flatMap(application -> mongoUpsertHelper.updateById(application, applicationId));
190+
191+
163192
}
164193

165194
public Mono<Boolean> setApplicationAsAgencyProfile(String applicationId, boolean agencyProfile) {
@@ -171,10 +200,24 @@ public Mono<Boolean> setApplicationAsAgencyProfile(String applicationId, boolean
171200

172201
@NonEmptyMono
173202
@SuppressWarnings("ReactiveStreamsNullableInLambdaInTransform")
174-
public Mono<Set<String>> getPublicApplicationIds(Collection<String> applicationIds, Boolean isAnonymous) {
175-
return repository.findByPublicToAllIsTrueAndPublicToMarketplaceIsOrAgencyProfileIsAndIdIn(!isAnonymous, !isAnonymous, applicationIds)
176-
.map(HasIdAndAuditing::getId)
177-
.collect(Collectors.toSet());
203+
public Mono<Set<String>> getPublicApplicationIds(Collection<String> applicationIds, Boolean isAnonymous, Boolean isPrivateMarketplace) {
204+
205+
if(isAnonymous) {
206+
if(isPrivateMarketplace) {
207+
return repository.findByPublicToAllIsTrueAndPublicToMarketplaceIsAndAgencyProfileIsAndIdIn(false, false, applicationIds)
208+
.map(HasIdAndAuditing::getId)
209+
.collect(Collectors.toSet());
210+
} else {
211+
return repository.findByPublicToAllIsTrueAndPublicToMarketplaceIsAndAgencyProfileIsAndIdIn(true, false, applicationIds)
212+
.map(HasIdAndAuditing::getId)
213+
.collect(Collectors.toSet());
214+
}
215+
} else {
216+
return repository.findByPublicToAllIsTrueAndPublicToMarketplaceIsOrAgencyProfileIsAndIdIn(true, true, applicationIds)
217+
.map(HasIdAndAuditing::getId)
218+
.collect(Collectors.toSet());
219+
}
220+
178221

179222
}
180223
}

server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/permission/service/ApplicationPermissionHandler.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ protected Mono<Map<String, List<ResourcePermission>>> getAnonymousUserPermission
4646
}
4747

4848
Set<String> applicationIds = newHashSet(resourceIds);
49-
return Mono.zip(applicationService.getPublicApplicationIds(applicationIds, Boolean.TRUE),
49+
return Mono.zip(applicationService.getPublicApplicationIds(applicationIds, Boolean.TRUE, config.getMarketplace().isPrivateMode()),
5050
templateSolution.getTemplateApplicationIds(applicationIds))
5151
.map(tuple -> {
5252
Set<String> publicAppIds = tuple.getT1();
@@ -61,7 +61,7 @@ protected Mono<Map<String, List<ResourcePermission>>> getAnonymousUserPermission
6161
(Collection<String> resourceIds, ResourceAction resourceAction) {
6262

6363
Set<String> applicationIds = newHashSet(resourceIds);
64-
return Mono.zip(applicationService.getPublicApplicationIds(applicationIds, Boolean.FALSE),
64+
return Mono.zip(applicationService.getPublicApplicationIds(applicationIds, Boolean.FALSE, config.getMarketplace().isPrivateMode()),
6565
templateSolution.getTemplateApplicationIds(applicationIds))
6666
.map(tuple -> {
6767
Set<String> publicAppIds = tuple.getT1();

server/api-service/lowcoder-domain/src/main/java/org/lowcoder/domain/permission/service/ResourcePermissionHandler.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.lowcoder.domain.permission.model.ResourceRole;
2727
import org.lowcoder.domain.permission.model.ResourceType;
2828
import org.lowcoder.domain.permission.model.UserPermissionOnResourceStatus;
29+
import org.lowcoder.sdk.config.CommonConfig;
2930
import org.springframework.beans.factory.annotation.Autowired;
3031

3132
import com.google.common.collect.Maps;
@@ -44,6 +45,9 @@ abstract class ResourcePermissionHandler {
4445
@Autowired
4546
private OrgMemberService orgMemberService;
4647

48+
@Autowired
49+
protected CommonConfig config;
50+
4751
public Mono<Map<String, List<ResourcePermission>>> getAllMatchingPermissions(String userId,
4852
Collection<String> resourceIds,
4953
ResourceAction resourceAction) {

server/api-service/lowcoder-sdk/src/main/java/org/lowcoder/sdk/config/CommonConfig.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ public class CommonConfig {
4444
private Cookie cookie = new Cookie();
4545
private JsExecutor jsExecutor = new JsExecutor();
4646
private Set<String> disallowedHosts = new HashSet<>();
47+
private Marketplace marketplace = new Marketplace();
4748

4849
public boolean isSelfHost() {
4950
return !isCloud();
@@ -145,6 +146,12 @@ public static class JsExecutor {
145146
private String host;
146147
}
147148

149+
@Data
150+
public static class Marketplace {
151+
152+
private boolean privateMode = Boolean.TRUE;
153+
}
154+
148155

149156
@Getter
150157
@Setter

server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationApiService.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -514,10 +514,11 @@ public Mono<Boolean> setApplicationPublicToAll(String applicationId, boolean pub
514514
.then(applicationService.setApplicationPublicToAll(applicationId, publicToAll));
515515
}
516516

517-
public Mono<Boolean> setApplicationPublicToMarketplace(String applicationId, boolean publicToMarketplace) {
517+
public Mono<Boolean> setApplicationPublicToMarketplace(String applicationId, ApplicationEndpoints.ApplicationPublicToMarketplaceRequest request) {
518518
return checkCurrentUserApplicationPermission(applicationId, ResourceAction.SET_APPLICATIONS_PUBLIC_TO_MARKETPLACE)
519519
.then(checkApplicationStatus(applicationId, NORMAL))
520-
.then(applicationService.setApplicationPublicToMarketplace(applicationId, publicToMarketplace));
520+
.then(applicationService.setApplicationPublicToMarketplace
521+
(applicationId, request.publicToMarketplace(), request.title(), request.category(), request.description(), request.image()));
521522
}
522523

523524
public Mono<Boolean> setApplicationAsAgencyProfile(String applicationId, boolean agencyProfile) {

server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationController.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import org.lowcoder.api.application.view.ApplicationView;
1818
import org.lowcoder.api.application.view.MarketplaceApplicationInfoView;
1919
import org.lowcoder.api.framework.view.ResponseView;
20+
import org.lowcoder.api.home.SessionUserService;
2021
import org.lowcoder.api.home.UserHomeApiService;
2122
import org.lowcoder.api.home.UserHomepageView;
2223
import org.lowcoder.api.util.BusinessEventPublisher;
@@ -39,6 +40,7 @@ public class ApplicationController implements ApplicationEndpoints {
3940
private final UserHomeApiService userHomeApiService;
4041
private final ApplicationApiService applicationApiService;
4142
private final BusinessEventPublisher businessEventPublisher;
43+
private final SessionUserService sessionUserService;
4244

4345
@Override
4446
public Mono<ResponseView<ApplicationView>> create(@RequestBody CreateApplicationRequest createApplicationRequest) {
@@ -155,7 +157,7 @@ public Mono<ResponseView<List<MarketplaceApplicationInfoView>>> getMarketplaceAp
155157
@Override
156158
public Mono<ResponseView<List<MarketplaceApplicationInfoView>>> getAgencyProfileApplications(@RequestParam(required = false) Integer applicationType) {
157159
ApplicationType applicationTypeEnum = applicationType == null ? null : ApplicationType.fromValue(applicationType);
158-
return userHomeApiService.getAllMarketplaceApplications(applicationTypeEnum)
160+
return userHomeApiService.getAllAgencyProfileApplications(applicationTypeEnum)
159161
.collectList()
160162
.map(ResponseView::success);
161163
}
@@ -214,7 +216,7 @@ public Mono<ResponseView<Boolean>> setApplicationPublicToAll(@PathVariable Strin
214216
@Override
215217
public Mono<ResponseView<Boolean>> setApplicationPublicToMarketplace(@PathVariable String applicationId,
216218
@RequestBody ApplicationPublicToMarketplaceRequest request) {
217-
return applicationApiService.setApplicationPublicToMarketplace(applicationId, request.publicToMarketplace())
219+
return applicationApiService.setApplicationPublicToMarketplace(applicationId, request)
218220
.map(ResponseView::success);
219221
}
220222

server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/ApplicationEndpoints.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,11 +270,33 @@ public Boolean publicToAll() {
270270
}
271271
}
272272

273-
public record ApplicationPublicToMarketplaceRequest(Boolean publicToMarketplace) {
273+
public record ApplicationPublicToMarketplaceRequest(Boolean publicToMarketplace, String title,
274+
String description, String category, String image) {
274275
@Override
275276
public Boolean publicToMarketplace() {
276277
return BooleanUtils.isTrue(publicToMarketplace);
277278
}
279+
280+
@Override
281+
public String title() {
282+
return title;
283+
}
284+
285+
@Override
286+
public String description() {
287+
return description;
288+
}
289+
290+
@Override
291+
public String category() {
292+
return category;
293+
}
294+
295+
@Override
296+
public String image() {
297+
return image;
298+
}
299+
278300
}
279301

280302
public record ApplicationAsAgencyProfileRequest(Boolean agencyProfile) {

server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/application/view/MarketplaceApplicationInfoView.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,20 @@
22

33
import lombok.Builder;
44
import lombok.Getter;
5+
import lombok.Setter;
56
import org.lowcoder.domain.application.model.ApplicationStatus;
67

78
@Builder
89
@Getter
10+
@Setter
911
public class MarketplaceApplicationInfoView {
1012

13+
// marketplace specific details
14+
private String title;
15+
private String description;
16+
private String category;
17+
private String image;
18+
1119
// org details
1220
private final String orgId;
1321
private final String orgName;

server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/framework/security/SecurityConfig.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,9 @@ SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
108108
ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, CONFIG_URL), // system config
109109
ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, CONFIG_URL + "/deploymentId"), // system config
110110
ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, APPLICATION_URL + "/*/view"), // application view
111+
ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, APPLICATION_URL + "/*/view_marketplace"), // application view
112+
ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, APPLICATION_URL + "/marketplace-apps"), // marketplace apps
113+
111114
ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, USER_URL + "/me"),
112115
ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, USER_URL + "/currentUser"),
113116

@@ -132,6 +135,8 @@ SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
132135
ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, NewUrl.CONFIG_URL + "/deploymentId"),
133136
ServerWebExchangeMatchers.pathMatchers(HttpMethod.HEAD, NewUrl.STATE_URL + "/healthCheck"),
134137
ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, NewUrl.APPLICATION_URL + "/*/view"),
138+
ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, NewUrl.APPLICATION_URL + "/*/view_marketplace"),
139+
ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, NewUrl.APPLICATION_URL + "/marketplace-apps"), // marketplace apps
135140
ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, NewUrl.USER_URL + "/me"),
136141
ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, NewUrl.USER_URL + "/currentUser"),
137142
ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, NewUrl.GROUP_URL + "/list"),
@@ -177,6 +182,8 @@ private CorsConfigurationSource buildCorsConfigurationSource() {
177182
source.registerCorsConfiguration(GROUP_URL + "/list", skipCheckCorsForAll);
178183
source.registerCorsConfiguration(QUERY_URL + "/execute", skipCheckCorsForAll);
179184
source.registerCorsConfiguration(APPLICATION_URL + "/*/view", skipCheckCorsForAll);
185+
source.registerCorsConfiguration(APPLICATION_URL + "/*/view_marketplace", skipCheckCorsForAll);
186+
source.registerCorsConfiguration(APPLICATION_URL + "/marketplace-apps", skipCheckCorsForAll);
180187
source.registerCorsConfiguration(GITHUB_STAR, skipCheckCorsForAll);
181188
source.registerCorsConfiguration(ORGANIZATION_URL + "/*/datasourceTypes", skipCheckCorsForAll);
182189
source.registerCorsConfiguration(DATASOURCE_URL + "/jsDatasourcePlugins", skipCheckCorsForAll);
@@ -186,6 +193,8 @@ private CorsConfigurationSource buildCorsConfigurationSource() {
186193
source.registerCorsConfiguration(NewUrl.GROUP_URL + "/list", skipCheckCorsForAll);
187194
source.registerCorsConfiguration(NewUrl.QUERY_URL + "/execute", skipCheckCorsForAll);
188195
source.registerCorsConfiguration(NewUrl.APPLICATION_URL + "/*/view", skipCheckCorsForAll);
196+
source.registerCorsConfiguration(NewUrl.APPLICATION_URL + "/*/view_marketplace", skipCheckCorsForAll);
197+
source.registerCorsConfiguration(NewUrl.APPLICATION_URL + "/marketplace-apps", skipCheckCorsForAll);
189198
source.registerCorsConfiguration(NewUrl.ORGANIZATION_URL + "/*/datasourceTypes", skipCheckCorsForAll);
190199
source.registerCorsConfiguration(NewUrl.DATASOURCE_URL + "/jsDatasourcePlugins", skipCheckCorsForAll);
191200

server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/home/UserHomeApiServiceImpl.java

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import org.lowcoder.domain.user.service.UserService;
4141
import org.lowcoder.domain.user.service.UserStatusService;
4242
import org.lowcoder.infra.util.NetworkUtils;
43+
import org.lowcoder.sdk.config.CommonConfig;
4344
import org.springframework.beans.factory.annotation.Autowired;
4445
import org.springframework.stereotype.Component;
4546
import org.springframework.web.server.ServerWebExchange;
@@ -80,6 +81,9 @@ public class UserHomeApiServiceImpl implements UserHomeApiService {
8081
@Autowired
8182
private UserApplicationInteractionService userApplicationInteractionService;
8283

84+
@Autowired
85+
private CommonConfig config;
86+
8387
@Override
8488
public Mono<UserProfileView> buildUserProfileView(User user, ServerWebExchange exchange) {
8589

@@ -260,8 +264,13 @@ public Flux<ApplicationInfoView> getAllAuthorisedApplications4CurrentOrgMember(@
260264
@Override
261265
public Flux<MarketplaceApplicationInfoView> getAllMarketplaceApplications(@Nullable ApplicationType applicationType) {
262266

263-
return sessionUserService.getVisitorOrgMemberCache()
264-
.flatMapMany(orgMember -> {
267+
return sessionUserService.isAnonymousUser()
268+
.flatMapMany(isAnonymousUser -> {
269+
270+
if(config.getMarketplace().isPrivateMode() && isAnonymousUser) {
271+
return Mono.empty();
272+
}
273+
265274
// application flux
266275
Flux<Application> applicationFlux = Flux.defer(() -> applicationService.findAllMarketplaceApps())
267276
.filter(application -> isNull(applicationType) || application.getApplicationType() == applicationType.getValue())
@@ -287,12 +296,12 @@ public Flux<MarketplaceApplicationInfoView> getAllMarketplaceApplications(@Nulla
287296

288297
return applicationFlux
289298
.flatMap(application -> Mono.zip(Mono.just(application), userMapMono, orgMapMono))
290-
.map(tuple -> {
299+
.map(tuple2 -> {
291300
// build view
292-
Application application = tuple.getT1();
293-
Map<String, User> userMap = tuple.getT2();
294-
Map<String, Organization> orgMap = tuple.getT3();
295-
return MarketplaceApplicationInfoView.builder()
301+
Application application = tuple2.getT1();
302+
Map<String, User> userMap = tuple2.getT2();
303+
Map<String, Organization> orgMap = tuple2.getT3();
304+
MarketplaceApplicationInfoView marketplaceApplicationInfoView = MarketplaceApplicationInfoView.builder()
296305
.applicationId(application.getId())
297306
.name(application.getName())
298307
.applicationType(application.getApplicationType())
@@ -305,6 +314,17 @@ public Flux<MarketplaceApplicationInfoView> getAllMarketplaceApplications(@Nulla
305314
.createAt(application.getCreatedAt().toEpochMilli())
306315
.createBy(application.getCreatedBy())
307316
.build();
317+
318+
// marketplace specific fields
319+
Map<String, Object> marketplaceMeta = (Map<String, Object>)
320+
((Map<String, Object>)application.getEditingApplicationDSL().get("ui")).get("marketplaceMeta");
321+
marketplaceApplicationInfoView.setTitle((String)marketplaceMeta.get("title"));
322+
marketplaceApplicationInfoView.setCategory((String)marketplaceMeta.get("category"));
323+
marketplaceApplicationInfoView.setDescription((String)marketplaceMeta.get("description"));
324+
marketplaceApplicationInfoView.setImage((String)marketplaceMeta.get("image"));
325+
326+
return marketplaceApplicationInfoView;
327+
308328
});
309329

310330
});

server/api-service/lowcoder-server/src/main/resources/application-lowcoder.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ common:
4444
block-hound-enable: false
4545
js-executor:
4646
host: http://127.0.0.1:6060
47+
marketplace:
48+
private-mode: false
4749

4850
material:
4951
mongodb-grid-fs:

server/api-service/lowcoder-server/src/main/resources/selfhost/ce/application.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ common:
5353
max-query-timeout: ${LOWCODER_MAX_QUERY_TIMEOUT:120}
5454
workspace:
5555
mode: ${LOWCODER_WORKSPACE_MODE:SAAS}
56+
marketplace:
57+
private-mode: ${MARKETPLACE_PRIVATE_MODE:true}
5658

5759
material:
5860
mongodb-grid-fs:

0 commit comments

Comments
 (0)