Skip to content

Make App Public To Marketplace Feature #677

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Feb 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ public class Application extends HasIdAndAuditing {
private final Map<String, Object> publishedApplicationDSL;

private final Boolean publicToAll;
private final Boolean publicToMarketplace;

private Map<String, Object> editingApplicationDSL;

@Transient
Expand Down Expand Up @@ -75,13 +77,15 @@ public Application(@JsonProperty("orgId") String organizationId,
@JsonProperty("applicationStatus") ApplicationStatus applicationStatus,
@JsonProperty("publishedApplicationDSL") Map<String, Object> publishedApplicationDSL,
@JsonProperty("publicToAll") Boolean publicToAll,
@JsonProperty("publicToMarketplace") Boolean publicToMarketplace,
@JsonProperty("editingApplicationDSL") Map<String, Object> editingApplicationDSL) {
this.organizationId = organizationId;
this.name = name;
this.applicationType = applicationType;
this.applicationStatus = applicationStatus;
this.publishedApplicationDSL = publishedApplicationDSL;
this.publicToAll = publicToAll;
this.publicToMarketplace = publicToMarketplace;
this.editingApplicationDSL = editingApplicationDSL;
}

Expand All @@ -105,6 +109,10 @@ public boolean isPublicToAll() {
return BooleanUtils.toBooleanDefaultIfNull(publicToAll, false);
}

public boolean isPublicToMarketplace() {
return BooleanUtils.toBooleanDefaultIfNull(publicToMarketplace, false);
}

public ApplicationQuery getQueryByViewModeAndQueryId(boolean isViewMode, String queryId) {
return (isViewMode ? getLiveQueries() : getEditingQueries())
.stream()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ public interface ApplicationRepository extends ReactiveMongoRepository<Applicati
Flux<Application> findByIdIn(List<String> ids);

@Query(fields = "{_id : 1}")
Flux<Application> findByPublicToAllIsTrueAndIdIn(Collection<String> ids);
Flux<Application> findByPublicToAllIsTrueAndPublicToMarketplaceIsAndIdIn(Boolean publicToMarketplace, Collection<String> ids);

Flux<Application> findByPublicToAllIsTrueAndPublicToMarketplaceIsTrue();

}
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ public Flux<Application> findByOrganizationIdWithoutDsl(String organizationId) {
return repository.findByOrganizationId(organizationId);
}

public Flux<Application> findAllMarketplaceApps() {
return repository.findByPublicToAllIsTrueAndPublicToMarketplaceIsTrue();
}

public Mono<Long> countByOrganizationId(String orgId, ApplicationStatus applicationStatus) {
return repository.countByOrganizationIdAndApplicationStatus(orgId, applicationStatus);
}
Expand Down Expand Up @@ -147,11 +151,19 @@ public Mono<Boolean> setApplicationPublicToAll(String applicationId, boolean pub
return mongoUpsertHelper.updateById(application, applicationId);
}

public Mono<Boolean> setApplicationPublicToMarketplace(String applicationId, boolean publicToMarketplace) {
Application application = Application.builder()
.publicToMarketplace(publicToMarketplace)
.build();
return mongoUpsertHelper.updateById(application, applicationId);
}

@NonEmptyMono
@SuppressWarnings("ReactiveStreamsNullableInLambdaInTransform")
public Mono<Set<String>> getPublicApplicationIds(Collection<String> applicationIds) {
return repository.findByPublicToAllIsTrueAndIdIn(applicationIds)
public Mono<Set<String>> getPublicApplicationIds(Collection<String> applicationIds, Boolean isAnonymous) {
return repository.findByPublicToAllIsTrueAndPublicToMarketplaceIsAndIdIn(!isAnonymous, applicationIds)
.map(HasIdAndAuditing::getId)
.collect(Collectors.toSet());

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public enum ResourceAction {
EDIT_APPLICATIONS(ResourceRole.EDITOR, ResourceType.APPLICATION),

SET_APPLICATIONS_PUBLIC(ResourceRole.EDITOR, ResourceType.APPLICATION),
SET_APPLICATIONS_PUBLIC_TO_MARKETPLACE(ResourceRole.EDITOR, ResourceType.APPLICATION),

// datasource action
MANAGE_DATASOURCES(ResourceRole.OWNER, ResourceType.DATASOURCE),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,22 @@ protected Mono<Map<String, List<ResourcePermission>>> getAnonymousUserPermission
}

Set<String> applicationIds = newHashSet(resourceIds);
return Mono.zip(applicationService.getPublicApplicationIds(applicationIds),
return Mono.zip(applicationService.getPublicApplicationIds(applicationIds, Boolean.TRUE),
templateSolution.getTemplateApplicationIds(applicationIds))
.map(tuple -> {
Set<String> publicAppIds = tuple.getT1();
Set<String> templateAppIds = tuple.getT2();
return collectMap(union(publicAppIds, templateAppIds), identity(), this::getAnonymousUserPermission);
});
}

// This is for PTM apps that are public but only available to logged-in users
@Override
protected Mono<Map<String, List<ResourcePermission>>> getNonAnonymousUserPublicResourcePermissions
(Collection<String> resourceIds, ResourceAction resourceAction) {

Set<String> applicationIds = newHashSet(resourceIds);
return Mono.zip(applicationService.getPublicApplicationIds(applicationIds, Boolean.FALSE),
templateSolution.getTemplateApplicationIds(applicationIds))
.map(tuple -> {
Set<String> publicAppIds = tuple.getT1();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.lowcoder.domain.permission.service;

import static org.lowcoder.domain.permission.model.ResourceHolder.USER;
import static org.lowcoder.sdk.constants.Authentication.ANONYMOUS_USER_ID;

import java.util.Collection;
import java.util.Collections;
Expand Down Expand Up @@ -39,6 +38,11 @@ protected Mono<Map<String, List<ResourcePermission>>> getAnonymousUserPermission
return Mono.just(Collections.emptyMap());
}

@Override
protected Mono<Map<String, List<ResourcePermission>>> getNonAnonymousUserPublicResourcePermissions(Collection<String> resourceIds, ResourceAction resourceAction) {
return Mono.just(Collections.emptyMap());
}

@Override
protected Mono<String> getOrgId(String resourceId) {
return datasourceService.getById(resourceId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,16 @@ public Mono<UserPermissionOnResourceStatus> checkUserPermissionStatusOnResource(
return publicResourcePermissionMono;
}

Mono<UserPermissionOnResourceStatus> nonAnonymousPublicResourcePermissionMono = getNonAnonymousUserPublicResourcePermissions(singletonList(resourceId), resourceAction)
.map(it -> it.getOrDefault(resourceId, emptyList()))
.map(it -> {
if (!it.isEmpty()) {
return UserPermissionOnResourceStatus.success(it.get(0));
}
return isAnonymousUser(userId) ? UserPermissionOnResourceStatus.anonymousUser() : UserPermissionOnResourceStatus.notInOrg();
});


Mono<UserPermissionOnResourceStatus> orgUserPermissionMono = getOrgId(resourceId)
.flatMap(orgId -> orgMemberService.getOrgMember(orgId, userId))
.flatMap(orgMember -> {
Expand All @@ -107,13 +117,17 @@ public Mono<UserPermissionOnResourceStatus> checkUserPermissionStatusOnResource(
})
.defaultIfEmpty(UserPermissionOnResourceStatus.notInOrg());

return Mono.zip(publicResourcePermissionMono, orgUserPermissionMono)
return Mono.zip(publicResourcePermissionMono, nonAnonymousPublicResourcePermissionMono, orgUserPermissionMono)
.map(tuple -> {
UserPermissionOnResourceStatus publicResourcePermission = tuple.getT1();
UserPermissionOnResourceStatus orgUserPermission = tuple.getT2();
UserPermissionOnResourceStatus nonAnonymousPublicResourcePermission = tuple.getT2();
UserPermissionOnResourceStatus orgUserPermission = tuple.getT3();
if (orgUserPermission.hasPermission()) {
return orgUserPermission;
}
if(nonAnonymousPublicResourcePermission.hasPermission()) {
return nonAnonymousPublicResourcePermission;
}
if (publicResourcePermission.hasPermission()) {
return publicResourcePermission;
}
Expand All @@ -132,6 +146,9 @@ private ResourcePermission getMaxPermission(List<ResourcePermission> permissions
protected abstract Mono<Map<String, List<ResourcePermission>>> getAnonymousUserPermissions(Collection<String> resourceIds,
ResourceAction resourceAction);

protected abstract Mono<Map<String, List<ResourcePermission>>> getNonAnonymousUserPublicResourcePermissions
(Collection<String> resourceIds, ResourceAction resourceAction);

private Mono<Map<String, List<ResourcePermission>>> getAllMatchingPermissions0(String userId, String orgId, ResourceType resourceType,
Collection<String> resourceIds,
ResourceAction resourceAction) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,8 @@ public Mono<ResourcePermission> checkAndReturnMaxPermission(String userId, Strin
});
}

public Mono<UserPermissionOnResourceStatus> checkUserPermissionStatusOnResource(String userId, String resourceId, ResourceAction resourceAction) {
public Mono<UserPermissionOnResourceStatus> checkUserPermissionStatusOnResource
(String userId, String resourceId, ResourceAction resourceAction) {
ResourceType resourceType = resourceAction.getResourceType();
var resourcePermissionHandler = getResourcePermissionHandler(resourceType);
return resourcePermissionHandler.checkUserPermissionStatusOnResource(userId, resourceId, resourceAction);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ public Mono<ApplicationView> create(CreateApplicationRequest createApplicationRe
createApplicationRequest.applicationType(),
NORMAL,
createApplicationRequest.publishedApplicationDSL(),
false, createApplicationRequest.editingApplicationDSL());
false, false, createApplicationRequest.editingApplicationDSL());

if (StringUtils.isBlank(application.getOrganizationId())) {
return deferredError(INVALID_PARAMETER, "ORG_ID_EMPTY");
Expand Down Expand Up @@ -429,6 +429,7 @@ public Mono<ApplicationPermissionView> getApplicationPermissions(String applicat
.creatorId(creatorId)
.orgName(organization.getName())
.publicToAll(application.isPublicToAll())
.publicToMarketplace(application.isPublicToMarketplace())
.build();
});
});
Expand Down Expand Up @@ -485,6 +486,7 @@ private ApplicationInfoView buildView(Application application, String role, @Nul
.applicationStatus(application.getApplicationStatus())
.folderId(folderId)
.publicToAll(application.isPublicToAll())
.publicToMarketplace(application.isPublicToMarketplace())
.build();
}

Expand All @@ -498,6 +500,12 @@ public Mono<Boolean> setApplicationPublicToAll(String applicationId, boolean pub
.then(applicationService.setApplicationPublicToAll(applicationId, publicToAll));
}

public Mono<Boolean> setApplicationPublicToMarketplace(String applicationId, boolean publicToMarketplace) {
return checkCurrentUserApplicationPermission(applicationId, ResourceAction.SET_APPLICATIONS_PUBLIC_TO_MARKETPLACE)
.then(checkApplicationStatus(applicationId, NORMAL))
.then(applicationService.setApplicationPublicToMarketplace(applicationId, publicToMarketplace));
}

private Map<String, Object> sanitizeDsl(Map<String, Object> applicationDsl) {
if (applicationDsl.get("queries") instanceof List<?> queries) {
List<Map<String, Object>> list = queries.stream().map(this::doSanitizeQuery).toList();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.lowcoder.api.application.view.ApplicationInfoView;
import org.lowcoder.api.application.view.ApplicationPermissionView;
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.UserHomeApiService;
import org.lowcoder.api.home.UserHomepageView;
Expand Down Expand Up @@ -96,6 +97,14 @@ public Mono<ResponseView<ApplicationView>> getPublishedApplication(@PathVariable
.map(ResponseView::success);
}

@Override
public Mono<ResponseView<ApplicationView>> getPublishedMarketPlaceApplication(@PathVariable String applicationId) {
return applicationApiService.getPublishedApplication(applicationId)
.delayUntil(applicationView -> applicationApiService.updateUserApplicationLastViewTime(applicationId))
.delayUntil(applicationView -> businessEventPublisher.publishApplicationCommonEvent(applicationView, VIEW))
.map(ResponseView::success);
}

@Override
public Mono<ResponseView<ApplicationView>> update(@PathVariable String applicationId,
@RequestBody Application newApplication) {
Expand Down Expand Up @@ -127,6 +136,14 @@ public Mono<ResponseView<List<ApplicationInfoView>>> getApplications(@RequestPar
.map(ResponseView::success);
}

@Override
public Mono<ResponseView<List<MarketplaceApplicationInfoView>>> getMarketplaceApplications(@RequestParam(required = false) Integer applicationType) {
ApplicationType applicationTypeEnum = applicationType == null ? null : ApplicationType.fromValue(applicationType);
return userHomeApiService.getAllMarketplaceApplications(applicationTypeEnum)
.collectList()
.map(ResponseView::success);
}

@Override
public Mono<ResponseView<Boolean>> updatePermission(@PathVariable String applicationId,
@PathVariable String permissionId,
Expand Down Expand Up @@ -177,4 +194,11 @@ public Mono<ResponseView<Boolean>> setApplicationPublicToAll(@PathVariable Strin
return applicationApiService.setApplicationPublicToAll(applicationId, request.publicToAll())
.map(ResponseView::success);
}

@Override
public Mono<ResponseView<Boolean>> setApplicationPublicToMarketplace(@PathVariable String applicationId,
@RequestBody ApplicationPublicToMarketplaceRequest request) {
return applicationApiService.setApplicationPublicToMarketplace(applicationId, request.publicToMarketplace())
.map(ResponseView::success);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import org.lowcoder.api.application.view.ApplicationInfoView;
import org.lowcoder.api.application.view.ApplicationPermissionView;
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.UserHomepageView;
import org.lowcoder.domain.application.model.Application;
Expand Down Expand Up @@ -110,6 +111,15 @@ public interface ApplicationEndpoints
@GetMapping("/{applicationId}/view")
public Mono<ResponseView<ApplicationView>> getPublishedApplication(@PathVariable String applicationId);

@Operation(
tags = TAG_APPLICATION_MANAGEMENT,
operationId = "getMarketplaceApplicationDataInViewMode",
summary = "Get Marketplace Application data in view mode",
description = "Retrieve the DSL data of a Lowcoder Application in view-mode by its ID for the marketplace."
)
@GetMapping("/{applicationId}/view_marketplace")
public Mono<ResponseView<ApplicationView>> getPublishedMarketPlaceApplication(@PathVariable String applicationId);

@Operation(
tags = TAG_APPLICATION_MANAGEMENT,
operationId = "updateApplication",
Expand Down Expand Up @@ -149,6 +159,15 @@ public Mono<ResponseView<List<ApplicationInfoView>>> getApplications(@RequestPar
@RequestParam(required = false) ApplicationStatus applicationStatus,
@RequestParam(defaultValue = "true") boolean withContainerSize);

@Operation(
tags = TAG_APPLICATION_MANAGEMENT,
operationId = "listMarketplaceApplications",
summary = "List marketplace Applications",
description = "Retrieve a list of Lowcoder Applications that are published to the marketplace"
)
@GetMapping("/marketplace-apps")
public Mono<ResponseView<List<MarketplaceApplicationInfoView>>> getMarketplaceApplications(@RequestParam(required = false) Integer applicationType);

@Operation(
tags = TAG_APPLICATION_PERMISSIONS,
operationId = "updateApplicationPermissions",
Expand Down Expand Up @@ -202,8 +221,18 @@ public Mono<ResponseView<Boolean>> grantPermission(
public Mono<ResponseView<Boolean>> setApplicationPublicToAll(@PathVariable String applicationId,
@RequestBody ApplicationPublicToAllRequest request);


public record BatchAddPermissionRequest(String role, Set<String> userIds, Set<String> groupIds) {
@Operation(
tags = TAG_APPLICATION_MANAGEMENT,
operationId = "setApplicationAsPublicToMarketplace",
summary = "Set Application as publicly available on marketplace but to only logged in users",
description = "Set a Lowcoder Application identified by its ID as publicly available on marketplace but to only logged in users."
)
@PutMapping("/{applicationId}/public-to-marketplace")
public Mono<ResponseView<Boolean>> setApplicationPublicToMarketplace(@PathVariable String applicationId,
@RequestBody ApplicationPublicToMarketplaceRequest request);


public record BatchAddPermissionRequest(String role, Set<String> userIds, Set<String> groupIds) {
}

public record ApplicationPublicToAllRequest(Boolean publicToAll) {
Expand All @@ -213,6 +242,13 @@ public Boolean publicToAll() {
}
}

public record ApplicationPublicToMarketplaceRequest(Boolean publicToMarketplace) {
@Override
public Boolean publicToMarketplace() {
return BooleanUtils.isTrue(publicToMarketplace);
}
}

public record UpdatePermissionRequest(String role) {
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ public class ApplicationInfoView {

private final boolean publicToAll;

private final boolean publicToMarketplace;

public long getLastViewTime() {
return lastViewTime == null ? 0 : lastViewTime.toEpochMilli();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,13 @@
public class ApplicationPermissionView extends CommonPermissionView {

private boolean publicToAll;
private boolean publicToMarketplace;

public boolean isPublicToAll() {
return publicToAll;
}

public boolean isPublicToMarketplace() {
return publicToMarketplace;
}
}
Loading