Skip to content

feat: add application version rollback capability #1926

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

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from
Open
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
@@ -1,6 +1,9 @@
package org.lowcoder.api.application;

import jakarta.annotation.Nonnull;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.lowcoder.api.application.view.ApplicationInfoView;
import org.lowcoder.api.application.view.ApplicationPermissionView;
import org.lowcoder.api.application.view.ApplicationPublishRequest;
Expand All @@ -10,14 +13,11 @@
import org.lowcoder.domain.permission.model.ResourceAction;
import org.lowcoder.domain.permission.model.ResourcePermission;
import org.lowcoder.domain.permission.model.ResourceRole;
import org.springframework.web.bind.annotation.PathVariable;

import jakarta.annotation.Nonnull;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.List;
import java.util.Set;

public interface ApplicationApiService {
Mono<ApplicationView> create(ApplicationEndpoints.CreateApplicationRequest createApplicationRequest);

Expand All @@ -39,6 +39,8 @@ public interface ApplicationApiService {

Mono<ApplicationView> publish(String applicationId, ApplicationPublishRequest applicationPublishRequest);

Mono<ApplicationView> publishWithRollback(String applicationId, ApplicationPublishRequest applicationPublishRequest, Map<String, Object> rollbackDsl);

Mono<Boolean> updateEditState(String applicationId, ApplicationEndpoints.UpdateEditStateRequest updateEditStateRequest);

Mono<Boolean> grantPermission(String applicationId,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,30 @@
package org.lowcoder.api.application;

import com.github.f4b6a3.uuid.UuidCreator;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import static org.lowcoder.domain.application.model.ApplicationStatus.NORMAL;
import static org.lowcoder.domain.permission.model.ResourceAction.EDIT_APPLICATIONS;
import static org.lowcoder.domain.permission.model.ResourceAction.MANAGE_APPLICATIONS;
import static org.lowcoder.domain.permission.model.ResourceAction.PUBLISH_APPLICATIONS;
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.ILLEGAL_APPLICATION_PERMISSION_ID;
import static org.lowcoder.sdk.exception.BizError.INVALID_PARAMETER;
import static org.lowcoder.sdk.exception.BizError.NOT_AUTHORIZED;
import static org.lowcoder.sdk.exception.BizError.NO_PERMISSION_TO_REQUEST_APP;
import static org.lowcoder.sdk.exception.BizError.USER_NOT_SIGNED_IN;
import static org.lowcoder.sdk.util.ExceptionUtils.deferredError;
import static org.lowcoder.sdk.util.ExceptionUtils.ofError;
import static org.lowcoder.sdk.util.ExceptionUtils.ofErrorWithHeaders;

import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
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.collections4.SetUtils;
import org.apache.commons.lang3.ObjectUtils;
Expand All @@ -27,21 +45,26 @@
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.model.*;
import org.lowcoder.domain.application.model.Application;
import org.lowcoder.domain.application.model.ApplicationRequestType;
import org.lowcoder.domain.application.model.ApplicationStatus;
import org.lowcoder.domain.application.model.ApplicationType;
import org.lowcoder.domain.application.model.ApplicationVersion;
import org.lowcoder.domain.application.service.ApplicationHistorySnapshotService;
import org.lowcoder.domain.application.service.ApplicationRecordService;
import org.lowcoder.domain.application.service.ApplicationService;
import org.lowcoder.domain.datasource.model.Datasource;
import org.lowcoder.domain.datasource.service.DatasourceService;
import org.lowcoder.domain.folder.service.FolderElementRelationService;
import org.lowcoder.domain.group.model.Group;
import org.lowcoder.domain.group.model.GroupMember;
import org.lowcoder.domain.interaction.UserApplicationInteractionService;
import org.lowcoder.domain.organization.model.OrgMember;
import org.lowcoder.domain.organization.model.Organization;
import org.lowcoder.domain.organization.service.OrgMemberService;
import org.lowcoder.domain.organization.service.OrganizationService;
import org.lowcoder.domain.permission.model.*;
import org.lowcoder.domain.permission.model.ResourceAction;
import org.lowcoder.domain.permission.model.ResourceHolder;
import org.lowcoder.domain.permission.model.ResourcePermission;
import org.lowcoder.domain.permission.model.ResourceRole;
import org.lowcoder.domain.permission.model.ResourceType;
import org.lowcoder.domain.permission.service.ResourcePermissionService;
import org.lowcoder.domain.permission.solution.SuggestAppAdminSolutionService;
import org.lowcoder.domain.plugin.service.DatasourceMetaInfoService;
Expand All @@ -56,22 +79,18 @@
import org.lowcoder.sdk.util.ExceptionUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.PathVariable;

import com.github.f4b6a3.uuid.UuidCreator;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import reactor.util.function.Tuple2;

import java.time.Duration;
import java.time.Instant;
import java.util.*;
import java.util.stream.Collectors;

import static org.lowcoder.domain.application.model.ApplicationStatus.NORMAL;
import static org.lowcoder.domain.permission.model.ResourceAction.*;
import static org.lowcoder.sdk.exception.BizError.*;
import static org.lowcoder.sdk.util.ExceptionUtils.*;

@RequiredArgsConstructor
@Service
Expand Down Expand Up @@ -398,6 +417,27 @@ public Mono<ApplicationView> publish(String applicationId, ApplicationPublishReq
.build())));
}

@Override
public Mono<ApplicationView> publishWithRollback(String applicationId, ApplicationPublishRequest applicationPublishRequest, Map<String, Object> rollbackDsl) {
return checkApplicationStatus(applicationId, NORMAL)
.then(sessionUserService.getVisitorId())
.flatMap(userId -> resourcePermissionService.checkAndReturnMaxPermission(userId,
applicationId, PUBLISH_APPLICATIONS))
.delayUntil(__ -> applicationService.findById(applicationId)
.map(application -> ApplicationVersion.builder()
.tag(applicationPublishRequest.tag())
.commitMessage(applicationPublishRequest.commitMessage())
.applicationId(application.getId())
.applicationDSL(rollbackDsl) // Use the rollback DSL instead of current editing DSL
.build())
.flatMap(applicationRecordService::insert))
.flatMap(permission -> applicationService.findById(applicationId)
.flatMap(applicationUpdated -> buildView(applicationUpdated, permission.getResourceRole().getValue()).map(appInfoView -> ApplicationView.builder()
.applicationInfoView(appInfoView)
.applicationDSL(rollbackDsl) // Return the rollback DSL in the response
.build())));
}

@Override
public Mono<Boolean> updateEditState(String applicationId, ApplicationEndpoints.UpdateEditStateRequest updateEditStateRequest) {
return checkApplicationStatus(applicationId, NORMAL)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,24 @@
package org.lowcoder.api.application;

import lombok.RequiredArgsConstructor;
import org.lowcoder.api.application.view.*;
import static org.apache.commons.collections4.SetUtils.emptyIfNull;
import static org.lowcoder.plugin.api.event.LowcoderEvent.EventType.APPLICATION_CREATE;
import static org.lowcoder.plugin.api.event.LowcoderEvent.EventType.APPLICATION_DELETE;
import static org.lowcoder.plugin.api.event.LowcoderEvent.EventType.APPLICATION_RECYCLED;
import static org.lowcoder.plugin.api.event.LowcoderEvent.EventType.APPLICATION_RESTORE;
import static org.lowcoder.plugin.api.event.LowcoderEvent.EventType.APPLICATION_UPDATE;
import static org.lowcoder.plugin.api.event.LowcoderEvent.EventType.APPLICATION_VIEW;
import static org.lowcoder.sdk.exception.BizError.INVALID_PARAMETER;
import static org.lowcoder.sdk.util.ExceptionUtils.ofError;

import java.util.List;
import java.util.Map;
import java.util.Objects;

import org.lowcoder.api.application.view.ApplicationInfoView;
import org.lowcoder.api.application.view.ApplicationPermissionView;
import org.lowcoder.api.application.view.ApplicationPublishRequest;
import org.lowcoder.api.application.view.ApplicationView;
import org.lowcoder.api.application.view.MarketplaceApplicationInfoView;
import org.lowcoder.api.framework.view.PageResponseView;
import org.lowcoder.api.framework.view.ResponseView;
import org.lowcoder.api.home.UserHomeApiService;
Expand All @@ -18,17 +35,10 @@
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.Mono;

import java.util.List;
import java.util.Objects;

import static org.apache.commons.collections4.SetUtils.emptyIfNull;
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 lombok.RequiredArgsConstructor;
import reactor.core.publisher.Flux;
import java.util.Map;
import reactor.core.publisher.Mono;

@RequiredArgsConstructor
@RestController
Expand Down Expand Up @@ -157,14 +167,32 @@ public Mono<ResponseView<ApplicationView>> publish(@PathVariable String applicat
})
.switchIfEmpty(Mono.just("1.0.0"))
.delayUntil(newtag -> {
ApplicationPublishRequest req = Objects.requireNonNullElse(applicationPublishRequest, new ApplicationPublishRequest("", newtag));
ApplicationPublishRequest req = Objects.requireNonNullElse(applicationPublishRequest, new ApplicationPublishRequest("", newtag, null));
return businessEventPublisher.publishApplicationPublishEvent(appId, req).then(Mono.defer(() -> {
if(newtag.equals(req.tag())) {
return businessEventPublisher.publishApplicationVersionChangeEvent(appId, newtag);
} else return Mono.empty();
}));
})
.flatMap(newtag -> applicationApiService.publish(appId, Objects.requireNonNullElse(applicationPublishRequest, new ApplicationPublishRequest("", newtag))))
.flatMap(newtag -> {
ApplicationPublishRequest req = Objects.requireNonNullElse(applicationPublishRequest, new ApplicationPublishRequest("", newtag, null));

// If rollback is requested, get the DSL from the specified version
if (req.rollbackToVersionId() != null && !req.rollbackToVersionId().isEmpty()) {
return applicationRecordService.getById(req.rollbackToVersionId())
.flatMap(rollbackVersion -> {
// Create a new request with the rollback DSL
ApplicationPublishRequest rollbackRequest = new ApplicationPublishRequest(
req.commitMessage(),
req.tag(),
req.rollbackToVersionId()
);
return applicationApiService.publishWithRollback(appId, rollbackRequest, rollbackVersion.getApplicationDSL());
});
} else {
return applicationApiService.publish(appId, req);
}
})
.map(ResponseView::success));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
package org.lowcoder.api.application;

import java.util.List;
import java.util.Map;

import org.lowcoder.api.application.view.ApplicationRecordMetaView;
import org.lowcoder.domain.application.model.ApplicationCombineId;
import reactor.core.publisher.Mono;

import java.util.List;
import java.util.Map;
import reactor.core.publisher.Mono;

public interface ApplicationRecordApiService {
Mono<Map<String, Object>> getRecordDSLFromApplicationCombineId(ApplicationCombineId applicationCombineId);

Mono<Void> delete(String id);

Mono<List<ApplicationRecordMetaView>> getByApplicationId(String applicationId);

Mono<Map<String, Object>> getVersionDsl(String applicationRecordId);
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
package org.lowcoder.api.application;

import lombok.RequiredArgsConstructor;
import org.lowcoder.api.home.SessionUserService;
import static org.lowcoder.api.util.ViewBuilder.multiBuild;
import static org.lowcoder.sdk.exception.BizError.APPLICATION_AND_ORG_NOT_MATCH;
import static org.lowcoder.sdk.util.ExceptionUtils.ofError;

import java.util.List;
import java.util.Map;

import org.lowcoder.api.application.view.ApplicationRecordMetaView;
import org.lowcoder.api.home.SessionUserService;
import org.lowcoder.api.usermanagement.OrgDevChecker;
import org.lowcoder.domain.application.model.ApplicationVersion;
import org.lowcoder.domain.organization.model.OrgMember;
import org.lowcoder.domain.application.model.Application;
import org.lowcoder.domain.application.model.ApplicationCombineId;
import org.lowcoder.domain.application.model.ApplicationVersion;
import org.lowcoder.domain.application.service.ApplicationRecordService;
import org.lowcoder.domain.application.service.ApplicationService;
import org.lowcoder.domain.organization.model.OrgMember;
import org.lowcoder.domain.user.service.UserService;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;

import java.util.List;
import java.util.Map;

import static org.lowcoder.api.util.ViewBuilder.multiBuild;
import static org.lowcoder.sdk.exception.BizError.APPLICATION_AND_ORG_NOT_MATCH;
import static org.lowcoder.sdk.util.ExceptionUtils.ofError;
import lombok.RequiredArgsConstructor;
import reactor.core.publisher.Mono;

@RequiredArgsConstructor
@Service
Expand Down Expand Up @@ -60,6 +61,13 @@ public Mono<List<ApplicationRecordMetaView>> getByApplicationId(String applicati
));
}

@Override
public Mono<Map<String, Object>> getVersionDsl(String applicationRecordId) {
return checkApplicationRecordViewPermission(new ApplicationCombineId(null, applicationRecordId))
.then(applicationRecordService.getById(applicationRecordId))
.map(ApplicationVersion::getApplicationDSL);
}


Mono<Void> checkApplicationRecordManagementPermission(String applicationRecordId) {
return orgDevChecker.checkCurrentOrgDev()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
package org.lowcoder.api.application;

import lombok.RequiredArgsConstructor;
import org.lowcoder.api.framework.view.ResponseView;
import org.lowcoder.api.application.ApplicationRecordApiService;
import java.util.List;
import java.util.Map;

import org.lowcoder.api.application.view.ApplicationRecordMetaView;
import org.lowcoder.api.framework.view.ResponseView;
import org.lowcoder.domain.application.model.ApplicationCombineId;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;

import java.util.List;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import reactor.core.publisher.Mono;

@RequiredArgsConstructor
@RestController
Expand All @@ -38,4 +38,9 @@ public Mono<ResponseView<Map<String, Object>>> dslById(@RequestParam(name = "app
.map(ResponseView::success);
}

@Override
public Mono<ResponseView<Map<String, Object>>> getVersionDsl(@PathVariable String applicationRecordId) {
return applicationRecordApiService.getVersionDsl(applicationRecordId)
.map(ResponseView::success);
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
package org.lowcoder.api.application;

import io.swagger.v3.oas.annotations.Operation;
import org.lowcoder.api.framework.view.ResponseView;
import org.lowcoder.api.application.view.ApplicationRecordMetaView;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Mono;
import static org.lowcoder.infra.constant.NewUrl.APPLICATION_RECORD_URL;

import java.util.List;
import java.util.Map;

import static org.lowcoder.infra.constant.NewUrl.APPLICATION_RECORD_URL;
import org.lowcoder.api.application.view.ApplicationRecordMetaView;
import org.lowcoder.api.framework.view.ResponseView;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import io.swagger.v3.oas.annotations.Operation;
import reactor.core.publisher.Mono;

@RestController
@RequestMapping(value = APPLICATION_RECORD_URL)
Expand Down Expand Up @@ -45,4 +51,13 @@ public interface ApplicationRecordEndpoints
public Mono<ResponseView<Map<String, Object>>> dslById(@RequestParam(name = "applicationId") String applicationId,
@RequestParam(name = "applicationRecordId") String applicationRecordId);

@Operation(
tags = TAG_APPLICATION_RECORDS,
operationId = "getVersionDsl",
summary = "Get DSL from specific version",
description = "Retrieve the DSL data from a specific application version for rollback preview"
)
@GetMapping("/{applicationRecordId}/dsl")
public Mono<ResponseView<Map<String, Object>>> getVersionDsl(@PathVariable String applicationRecordId);

}
Loading
Loading
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy