Skip to content

Commit 8faa3bb

Browse files
authored
Merge branch 'dev' into subscription-handling
2 parents 19ebdc8 + a28726f commit 8faa3bb

File tree

15 files changed

+104
-24
lines changed

15 files changed

+104
-24
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ And we mean it... Day by day!
9090
## 💻 Deployment Options
9191
[![Deploy to AWS using Stitch](https://img.shields.io/badge/deploy_with-Stitch-%23E369F7?logo=amazonaws&color=%23E369F7)](https://deploy.stitch.tech/lowcoder/lowcoder)
9292

93-
[![Deploy in minutes on Elest.io](https://raw.githubusercontent.com/elestio-examples/element/main/deploy-on-elestio.png)](https://elest.io/open-source/lowcoder)
93+
[![Deploy to Elestio](https://img.shields.io/badge/Deploy_to-Elestio-%23E369F7?color=orange)](https://elest.io/open-source/lowcoder)
9494

9595
You can access Lowcoder from [cloud-hosted version](https://app.lowcoder.cloud/) at any time, or use the following resources for self-host Lowcoder on different platforms:
9696
- [Docker](https://docs.lowcoder.cloud/lowcoder-documentation/setup-and-run/self-hosting)
@@ -110,4 +110,4 @@ Like ... [@Darkjamin](https://github.com/Darkjamin), [@spacegoats-io](https://g
110110

111111
## Intro Video
112112

113-
[![Watch the video](https://i.ytimg.com/vi/s4ltAqS0hzM/maxresdefault.jpg?sqp=-oaymwEmCIAKENAF8quKqQMa8AEB-AH-CYAC0AWKAgwIABABGD0gSShyMA8=&rs=AOn4CLAlPOIFdtauythoBKNPXhi6XGwlDQ)](https://youtu.be/s4ltAqS0hzM?feature=shared)
113+
[![Watch the video](https://i.ytimg.com/vi/s4ltAqS0hzM/maxresdefault.jpg?sqp=-oaymwEmCIAKENAF8quKqQMa8AEB-AH-CYAC0AWKAgwIABABGD0gSShyMA8=&rs=AOn4CLAlPOIFdtauythoBKNPXhi6XGwlDQ)](https://youtu.be/s4ltAqS0hzM?feature=shared)

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ public class Application extends HasIdAndAuditing {
5252
private Boolean publicToMarketplace;
5353
@Setter
5454
private Boolean agencyProfile;
55+
@Getter
56+
@Setter
57+
private String editingUserId;
5558

5659
public Application(
5760
@JsonProperty("orgId") String organizationId,
@@ -63,7 +66,8 @@ public Application(
6366
@JsonProperty("editingApplicationDSL") Map<String, Object> editingApplicationDSL,
6467
@JsonProperty("publicToAll") Boolean publicToAll,
6568
@JsonProperty("publicToMarketplace") Boolean publicToMarketplace,
66-
@JsonProperty("agencyProfile") Boolean agencyProfile
69+
@JsonProperty("agencyProfile") Boolean agencyProfile,
70+
@JsonProperty("editingUserId") String editingUserId
6771
) {
6872
this.gid = gid;
6973
this.organizationId = organizationId;
@@ -75,6 +79,7 @@ public Application(
7579
this.publicToMarketplace = publicToMarketplace;
7680
this.agencyProfile = agencyProfile;
7781
this.editingApplicationDSL = editingApplicationDSL;
82+
this.editingUserId = editingUserId;
7883
}
7984

8085
@Transient

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,5 @@ public interface ApplicationHistorySnapshotService {
1818

1919
Mono<ApplicationHistorySnapshot> getHistorySnapshotDetail(String historySnapshotId);
2020

21+
Mono<ApplicationHistorySnapshot> getLastSnapshotByApp(String applicationId);
2122
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ public interface ApplicationService {
2323

2424
Mono<Application> publish(String applicationId);
2525

26+
Mono<Boolean> updateEditState(String applicationId, Boolean editingFinished);
27+
2628
Mono<Application> create(Application newApplication, String visitorId);
2729

2830
Flux<Application> findByOrganizationIdWithDsl(String organizationId);

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,16 @@ public Mono<Application> publish(String applicationId) {
9393
});
9494
}
9595

96+
@Override
97+
public Mono<Boolean> updateEditState(String applicationId, Boolean editingFinished) {
98+
return findById(applicationId)
99+
.flatMap(newApplication -> {
100+
Application application = Application.builder().editingUserId("").build();
101+
if(editingFinished) return mongoUpsertHelper.updateById(application, applicationId);
102+
return Mono.just(true);
103+
});
104+
}
105+
96106
@Override
97107
public Mono<Application> create(Application newApplication, String visitorId) {
98108
return repository.save(newApplication)

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import static org.lowcoder.sdk.util.ExceptionUtils.deferredError;
55
import static org.lowcoder.sdk.util.ExceptionUtils.ofException;
66

7+
import java.time.Instant;
78
import java.util.List;
89
import java.util.Map;
910

@@ -57,4 +58,13 @@ public Mono<ApplicationHistorySnapshot> getHistorySnapshotDetail(String historyS
5758
return repository.findById(historySnapshotId)
5859
.switchIfEmpty(deferredError(INVALID_HISTORY_SNAPSHOT, "INVALID_HISTORY_SNAPSHOT", historySnapshotId));
5960
}
61+
62+
@Override
63+
public Mono<ApplicationHistorySnapshot> getLastSnapshotByApp(String applicationId) {
64+
ApplicationHistorySnapshot _default = new ApplicationHistorySnapshot();
65+
_default.setCreatedAt(Instant.ofEpochMilli(0));
66+
_default.setCreatedBy("");
67+
return repository.findAllByApplicationId(applicationId, PageRequest.of(0, 1).withSort(Direction.DESC, "createdAt"))
68+
.switchIfEmpty(Mono.just(_default)).next();
69+
}
6070
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ public interface ApplicationApiService {
3535

3636
Mono<ApplicationView> publish(String applicationId);
3737

38+
Mono<Boolean> updateEditState(String applicationId, ApplicationEndpoints.UpdateEditStateRequest updateEditStateRequest);
39+
3840
Mono<Boolean> grantPermission(String applicationId,
3941
Set<String> userIds,
4042
Set<String> groupIds, ResourceRole role);

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

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,8 @@
2121
import org.lowcoder.api.permission.PermissionHelper;
2222
import org.lowcoder.api.permission.view.PermissionItemView;
2323
import org.lowcoder.api.usermanagement.OrgDevChecker;
24-
import org.lowcoder.domain.application.model.Application;
25-
import org.lowcoder.domain.application.model.ApplicationRequestType;
26-
import org.lowcoder.domain.application.model.ApplicationStatus;
27-
import org.lowcoder.domain.application.model.ApplicationType;
24+
import org.lowcoder.domain.application.model.*;
25+
import org.lowcoder.domain.application.service.ApplicationHistorySnapshotService;
2826
import org.lowcoder.domain.application.service.ApplicationService;
2927
import org.lowcoder.domain.datasource.model.Datasource;
3028
import org.lowcoder.domain.datasource.service.DatasourceService;
@@ -45,7 +43,6 @@
4543
import org.lowcoder.sdk.exception.BizException;
4644
import org.lowcoder.sdk.plugin.common.QueryExecutor;
4745
import org.lowcoder.sdk.util.ExceptionUtils;
48-
import org.springframework.context.annotation.Lazy;
4946
import org.springframework.http.HttpHeaders;
5047
import org.springframework.stereotype.Service;
5148
import reactor.core.publisher.Flux;
@@ -58,7 +55,6 @@
5855
import java.util.List;
5956
import java.util.Map;
6057
import java.util.Set;
61-
import java.util.concurrent.TimeUnit;
6258
import java.util.stream.Collectors;
6359

6460
import static org.lowcoder.domain.application.model.ApplicationStatus.NORMAL;
@@ -94,6 +90,7 @@ public class ApplicationApiServiceImpl implements ApplicationApiService {
9490
private final TemplateService templateService;
9591
private final PermissionHelper permissionHelper;
9692
private final DatasourceService datasourceService;
93+
private final ApplicationHistorySnapshotService applicationHistorySnapshotService;
9794

9895
@Override
9996
public Mono<ApplicationView> create(CreateApplicationRequest createApplicationRequest) {
@@ -105,7 +102,7 @@ public Mono<ApplicationView> create(CreateApplicationRequest createApplicationRe
105102
NORMAL,
106103
createApplicationRequest.publishedApplicationDSL(),
107104
createApplicationRequest.editingApplicationDSL(),
108-
false, false, false);
105+
false, false, false, "");
109106

110107
if (StringUtils.isBlank(application.getOrganizationId())) {
111108
return deferredError(INVALID_PARAMETER, "ORG_ID_EMPTY");
@@ -259,19 +256,28 @@ public Mono<ApplicationView> getEditingApplication(String applicationId) {
259256
.delayUntil(application -> checkApplicationStatus(application, NORMAL)))
260257
.zipWhen(tuple -> applicationService.getAllDependentModulesFromApplication(tuple.getT2(), false), TupleUtils::merge)
261258
.zipWhen(tuple -> organizationService.getOrgCommonSettings(tuple.getT2().getOrganizationId()), TupleUtils::merge)
262-
.map(tuple -> {
263-
ResourcePermission permission = tuple.getT1();
264-
Application application = tuple.getT2();
265-
List<Application> dependentModules = tuple.getT3();
266-
Map<String, Object> commonSettings = tuple.getT4();
259+
.zipWhen(tuple -> sessionUserService.getVisitorId().zipWith(applicationHistorySnapshotService.getLastSnapshotByApp(applicationId)))
260+
.flatMap(tuple -> {
261+
ResourcePermission permission = tuple.getT1().getT1();
262+
Application application = tuple.getT1().getT2();
263+
List<Application> dependentModules = tuple.getT1().getT3();
264+
Map<String, Object> commonSettings = tuple.getT1().getT4();
265+
String visitorId = tuple.getT2().getT1();
266+
ApplicationHistorySnapshot lastSnapshot = tuple.getT2().getT2();
267+
268+
if(!visitorId.equals(application.getEditingUserId()) && lastSnapshot.getCreatedAt().compareTo(Instant.now().minusSeconds(300)) < 0) {
269+
application.setEditingUserId(visitorId);
270+
}
271+
267272
Map<String, Map<String, Object>> dependentModuleDsl = dependentModules.stream()
268273
.collect(Collectors.toMap(Application::getId, Application::getLiveApplicationDsl, (a, b) -> b));
269-
return ApplicationView.builder()
274+
return applicationService.updateById(applicationId, application).map(__ ->
275+
ApplicationView.builder()
270276
.applicationInfoView(buildView(application, permission.getResourceRole().getValue()))
271277
.applicationDSL(application.getEditingApplicationDSL())
272278
.moduleDSL(dependentModuleDsl)
273279
.orgCommonSettings(commonSettings)
274-
.build();
280+
.build());
275281
});
276282
}
277283

@@ -365,6 +371,15 @@ public Mono<ApplicationView> publish(String applicationId) {
365371
.build()));
366372
}
367373

374+
@Override
375+
public Mono<Boolean> updateEditState(String applicationId, ApplicationEndpoints.UpdateEditStateRequest updateEditStateRequest) {
376+
return checkApplicationStatus(applicationId, NORMAL)
377+
.then(sessionUserService.getVisitorId())
378+
.flatMap(userId -> resourcePermissionService.checkAndReturnMaxPermission(userId,
379+
applicationId, EDIT_APPLICATIONS))
380+
.flatMap(permission -> applicationService.updateEditState(applicationId, updateEditStateRequest.editingFinished()));
381+
}
382+
368383
@Override
369384
public Mono<Boolean> grantPermission(String applicationId,
370385
Set<String> userIds,
@@ -540,6 +555,7 @@ private ApplicationInfoView buildView(Application application, String role, @Nul
540555
.publicToAll(application.isPublicToAll())
541556
.publicToMarketplace(application.isPublicToMarketplace())
542557
.agencyProfile(application.agencyProfile())
558+
.editingUserId(application.getEditingUserId())
543559
.build();
544560
}
545561

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,13 @@ public Mono<ResponseView<ApplicationView>> publish(@PathVariable String applicat
142142
.map(ResponseView::success);
143143
}
144144

145+
@Override
146+
public Mono<ResponseView<Boolean>> updateEditState(@PathVariable String applicationId, @RequestBody UpdateEditStateRequest updateEditStateRequest) {
147+
String appId = gidService.convertApplicationIdToObjectId(applicationId);
148+
return applicationApiService.updateEditState(appId, updateEditStateRequest)
149+
.map(ResponseView::success);
150+
}
151+
145152
@Override
146153
public Mono<ResponseView<UserHomepageView>> getUserHomePage(@RequestParam(required = false, defaultValue = "0") int applicationType) {
147154
ApplicationType type = ApplicationType.fromValue(applicationType);

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,16 @@ public Mono<ResponseView<ApplicationView>> update(@PathVariable String applicati
137137
@PostMapping("/{applicationId}/publish")
138138
public Mono<ResponseView<ApplicationView>> publish(@PathVariable String applicationId);
139139

140+
@Operation(
141+
tags = TAG_APPLICATION_MANAGEMENT,
142+
operationId = "updateApplicationEditingState",
143+
summary = "Update Application editing state",
144+
description = "Update the editing state of a specific Lowcoder Application identified by its ID."
145+
)
146+
@PutMapping("/editState/{applicationId}")
147+
public Mono<ResponseView<Boolean>> updateEditState(@PathVariable String applicationId,
148+
@RequestBody UpdateEditStateRequest updateEditStateRequest);
149+
140150
@Operation(
141151
tags = TAG_APPLICATION_MANAGEMENT,
142152
operationId = "getUserHomepageApplication",
@@ -285,5 +295,7 @@ public record CreateApplicationRequest(@JsonProperty("orgId") String organizatio
285295
Map<String, Object> editingApplicationDSL,
286296
@Nullable String folderId) {
287297
}
298+
public record UpdateEditStateRequest(Boolean editingFinished) {
299+
}
288300

289301
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ public class ApplicationInfoView {
3838
private final boolean publicToMarketplace;
3939
private final boolean agencyProfile;
4040

41+
private final String editingUserId;
42+
4143
public long getLastViewTime() {
4244
return lastViewTime == null ? 0 : lastViewTime.toEpochMilli();
4345
}

server/api-service/lowcoder-server/src/test/java/org/lowcoder/api/application/ApplicationApiServiceIntegrationTest.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.lowcoder.api.application;
22

33

4+
import jakarta.persistence.Tuple;
45
import lombok.extern.slf4j.Slf4j;
56
import org.junit.jupiter.api.Assertions;
67
import org.junit.jupiter.api.BeforeEach;
@@ -26,6 +27,7 @@
2627
import org.springframework.test.context.ActiveProfiles;
2728
import reactor.core.publisher.Mono;
2829
import reactor.test.StepVerifier;
30+
import reactor.util.function.Tuple2;
2931

3032
import java.util.Map;
3133
import java.util.Set;
@@ -69,7 +71,6 @@ public void testCreateApplicationSuccess() {
6971
//
7072
Mono<ApplicationView> applicationViewMono = datasourceMono.map(datasource -> new CreateApplicationRequest(
7173
"org01",
72-
"",
7374
"app05",
7475
ApplicationType.APPLICATION.getValue(),
7576
Map.of("comp", "table"),
@@ -105,7 +106,6 @@ public void testUpdateApplicationFailedDueToLackOfDatasourcePermissions() {
105106
//
106107
Mono<ApplicationView> applicationViewMono = datasourceMono.map(datasource -> new CreateApplicationRequest(
107108
"org01",
108-
"",
109109
"app03",
110110
ApplicationType.APPLICATION.getValue(),
111111
Map.of("comp", "table"),
@@ -127,4 +127,17 @@ public void testUpdateApplicationFailedDueToLackOfDatasourcePermissions() {
127127
&& bizException.getMessageKey().equals("APPLICATION_EDIT_ERROR_LACK_OF_DATASOURCE_PERMISSIONS"))
128128
.verify();
129129
}
130+
131+
@Test
132+
@WithMockUser
133+
public void testUpdateEditingStateSuccess() {
134+
Mono<ApplicationView> applicationViewMono = applicationApiService.create(new CreateApplicationRequest("org01", "app1", ApplicationType.APPLICATION.getValue(), Map.of("comp", "table"), Map.of("comp", "list"), null));
135+
Mono<ApplicationView> updateEditStateMono = applicationViewMono.delayUntil(app -> applicationApiService.updateEditState(app.getApplicationInfoView().getApplicationId(), new ApplicationEndpoints.UpdateEditStateRequest(true)));
136+
Mono<ApplicationView> app = updateEditStateMono.flatMap(applicationView -> applicationApiService.getEditingApplication(applicationView.getApplicationInfoView().getApplicationId()));
137+
StepVerifier.create(app)
138+
.assertNext(application -> {
139+
Assertions.assertEquals("user01", application.getApplicationInfoView().getEditingUserId());
140+
})
141+
.verifyComplete();
142+
}
130143
}

server/api-service/lowcoder-server/src/test/java/org/lowcoder/api/application/ApplicationApiServiceTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ public void testDeleteNormalApplicationWithError() {
125125

126126
private Mono<ApplicationView> createApplication(String name, String folderId) {
127127
CreateApplicationRequest createApplicationRequest =
128-
new CreateApplicationRequest("org01", "", name, ApplicationType.APPLICATION.getValue(),
128+
new CreateApplicationRequest("org01", name, ApplicationType.APPLICATION.getValue(),
129129
Map.of("comp", "table"), Map.of("comp", "list"), folderId);
130130
return applicationApiService.create(createApplicationRequest);
131131
}

server/api-service/lowcoder-server/src/test/java/org/lowcoder/api/common/InitData.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,6 @@ public class InitData {
3131
public void init() {
3232
try {
3333
execute();
34-
} catch (DuplicateKeyException ignored) {
35-
3634
} catch (Exception e) {
3735
throw new RuntimeException(e);
3836
}

server/api-service/lowcoder-server/src/test/java/org/lowcoder/api/common/json/group.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@
66
"name": "Develops",
77
"organizationId": "org01",
88
"allUsersGroup": false,
9-
"type": "dev"
9+
"type": "dev",
10+
"gid": "01916999-a508-7b61-bba9-e27f2427396a"
1011
},
1112
{
1213
"id": "group01",
1314
"name": "group01",
1415
"organizationId": "org01",
15-
"allUsersGroup": false
16+
"allUsersGroup": false,
17+
"gid": "01916999-d8ee-786b-a00f-22b52fe59224"
1618
}
1719
]
1820
}

0 commit comments

Comments
 (0)