forked from halo-dev/halo
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: improve the system initialization process (halo-dev#4306)
* refactor: improve the system initialization process * Sync api-client Signed-off-by: Ryan Wang <[email protected]> * feat: add initialized state to global info * Refine setup page ui Signed-off-by: Ryan Wang <[email protected]> * refactor: improve the system initialization process * Refine setup page ui Signed-off-by: Ryan Wang <[email protected]> * Refine setup page ui Signed-off-by: Ryan Wang <[email protected]> * fix: update with initialize state * Refactor setup Signed-off-by: Ryan Wang <[email protected]> * refactor: initialization state * Refactor router guards Signed-off-by: Ryan Wang <[email protected]> * Refine i18n Signed-off-by: Ryan Wang <[email protected]> * Refactor init data Signed-off-by: Ryan Wang <[email protected]> * Refactor init data Signed-off-by: Ryan Wang <[email protected]> * Update console/src/views/system/Setup.vue Co-authored-by: Takagi <[email protected]> * refactor: initialization interface --------- Signed-off-by: Ryan Wang <[email protected]> Co-authored-by: Ryan Wang <[email protected]> Co-authored-by: Takagi <[email protected]>
- Loading branch information
1 parent
1172f4a
commit 5690de3
Showing
48 changed files
with
1,272 additions
and
521 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
136 changes: 136 additions & 0 deletions
136
...tion/src/main/java/run/halo/app/core/extension/endpoint/SystemInitializationEndpoint.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
package run.halo.app.core.extension.endpoint; | ||
|
||
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; | ||
import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder; | ||
import static org.springdoc.core.fn.builders.requestbody.Builder.requestBodyBuilder; | ||
|
||
import io.swagger.v3.oas.annotations.media.Schema; | ||
import java.time.Duration; | ||
import java.util.LinkedHashMap; | ||
import java.util.Map; | ||
import lombok.Data; | ||
import lombok.RequiredArgsConstructor; | ||
import org.apache.commons.lang3.StringUtils; | ||
import org.springdoc.webflux.core.fn.SpringdocRouteBuilder; | ||
import org.springframework.dao.OptimisticLockingFailureException; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.stereotype.Component; | ||
import org.springframework.web.reactive.function.server.RouterFunction; | ||
import org.springframework.web.reactive.function.server.ServerRequest; | ||
import org.springframework.web.reactive.function.server.ServerResponse; | ||
import org.springframework.web.server.ResponseStatusException; | ||
import org.springframework.web.server.ServerWebInputException; | ||
import reactor.core.publisher.Mono; | ||
import reactor.util.retry.Retry; | ||
import run.halo.app.extension.ConfigMap; | ||
import run.halo.app.extension.ReactiveExtensionClient; | ||
import run.halo.app.infra.InitializationStateGetter; | ||
import run.halo.app.infra.SystemSetting; | ||
import run.halo.app.infra.ValidationUtils; | ||
import run.halo.app.infra.exception.UnsatisfiedAttributeValueException; | ||
import run.halo.app.infra.utils.JsonUtils; | ||
import run.halo.app.security.SuperAdminInitializer; | ||
|
||
/** | ||
* System initialization endpoint. | ||
* | ||
* @author guqing | ||
* @since 2.9.0 | ||
*/ | ||
@Component | ||
@RequiredArgsConstructor | ||
public class SystemInitializationEndpoint implements CustomEndpoint { | ||
|
||
private final ReactiveExtensionClient client; | ||
private final SuperAdminInitializer superAdminInitializer; | ||
private final InitializationStateGetter initializationStateSupplier; | ||
|
||
@Override | ||
public RouterFunction<ServerResponse> endpoint() { | ||
var tag = "api.console.halo.run/v1alpha1/System"; | ||
// define a non-resource api | ||
return SpringdocRouteBuilder.route() | ||
.POST("/system/initialize", this::initialize, | ||
builder -> builder.operationId("initialize") | ||
.description("Initialize system") | ||
.tag(tag) | ||
.requestBody(requestBodyBuilder() | ||
.implementation(SystemInitializationRequest.class)) | ||
.response(responseBuilder().implementation(Boolean.class)) | ||
) | ||
.build(); | ||
} | ||
|
||
private Mono<ServerResponse> initialize(ServerRequest request) { | ||
return request.bodyToMono(SystemInitializationRequest.class) | ||
.switchIfEmpty( | ||
Mono.error(new ServerWebInputException("Request body must not be empty")) | ||
) | ||
.doOnNext(requestBody -> { | ||
if (!ValidationUtils.validateName(requestBody.getUsername())) { | ||
throw new UnsatisfiedAttributeValueException( | ||
"The username does not meet the specifications", | ||
"problemDetail.user.username.unsatisfied", null); | ||
} | ||
if (StringUtils.isBlank(requestBody.getPassword())) { | ||
throw new UnsatisfiedAttributeValueException( | ||
"The password does not meet the specifications", | ||
"problemDetail.user.password.unsatisfied", null); | ||
} | ||
}) | ||
.flatMap(requestBody -> initializationStateSupplier.userInitialized() | ||
.flatMap(result -> { | ||
if (result) { | ||
return Mono.error(new ResponseStatusException(HttpStatus.CONFLICT, | ||
"System has been initialized")); | ||
} | ||
return initializeSystem(requestBody); | ||
}) | ||
) | ||
.then(ServerResponse.ok().bodyValue(true)); | ||
} | ||
|
||
private Mono<Void> initializeSystem(SystemInitializationRequest requestBody) { | ||
Mono<Void> initializeAdminUser = superAdminInitializer.initialize( | ||
SuperAdminInitializer.InitializationParam.builder() | ||
.username(requestBody.getUsername()) | ||
.password(requestBody.getPassword()) | ||
.email(requestBody.getEmail()) | ||
.build()); | ||
|
||
Mono<Void> siteSetting = | ||
Mono.defer(() -> client.get(ConfigMap.class, SystemSetting.SYSTEM_CONFIG) | ||
.flatMap(config -> { | ||
Map<String, String> data = config.getData(); | ||
if (data == null) { | ||
data = new LinkedHashMap<>(); | ||
config.setData(data); | ||
} | ||
String basic = data.getOrDefault(SystemSetting.Basic.GROUP, "{}"); | ||
SystemSetting.Basic basicSetting = | ||
JsonUtils.jsonToObject(basic, SystemSetting.Basic.class); | ||
basicSetting.setTitle(requestBody.getSiteTitle()); | ||
data.put(SystemSetting.Basic.GROUP, JsonUtils.objectToJson(basicSetting)); | ||
return client.update(config); | ||
})) | ||
.retryWhen(Retry.backoff(5, Duration.ofMillis(100)) | ||
.filter(t -> t instanceof OptimisticLockingFailureException) | ||
) | ||
.then(); | ||
return Mono.when(initializeAdminUser, siteSetting); | ||
} | ||
|
||
@Data | ||
public static class SystemInitializationRequest { | ||
|
||
@Schema(requiredMode = REQUIRED, minLength = 1) | ||
private String username; | ||
|
||
@Schema(requiredMode = REQUIRED, minLength = 3) | ||
private String password; | ||
|
||
private String email; | ||
|
||
private String siteTitle; | ||
} | ||
} |
65 changes: 65 additions & 0 deletions
65
application/src/main/java/run/halo/app/infra/DefaultInitializationStateGetter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
package run.halo.app.infra; | ||
|
||
import static org.apache.commons.lang3.BooleanUtils.isTrue; | ||
|
||
import java.util.concurrent.atomic.AtomicBoolean; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Component; | ||
import reactor.core.publisher.Mono; | ||
import run.halo.app.core.extension.User; | ||
import run.halo.app.extension.ConfigMap; | ||
import run.halo.app.extension.MetadataUtil; | ||
import run.halo.app.extension.ReactiveExtensionClient; | ||
|
||
/** | ||
* <p>A cache that caches system setup state.</p> | ||
* when setUp state changed, the cache will be updated. | ||
* | ||
* @author guqing | ||
* @since 2.5.2 | ||
*/ | ||
@Component | ||
@RequiredArgsConstructor | ||
public class DefaultInitializationStateGetter implements InitializationStateGetter { | ||
private final ReactiveExtensionClient client; | ||
private final AtomicBoolean userInitialized = new AtomicBoolean(false); | ||
private final AtomicBoolean dataInitialized = new AtomicBoolean(false); | ||
|
||
@Override | ||
public Mono<Boolean> userInitialized() { | ||
// If user is initialized, return true directly. | ||
if (userInitialized.get()) { | ||
return Mono.just(true); | ||
} | ||
return hasUser() | ||
.doOnNext(userInitialized::set); | ||
} | ||
|
||
@Override | ||
public Mono<Boolean> dataInitialized() { | ||
if (dataInitialized.get()) { | ||
return Mono.just(true); | ||
} | ||
return client.fetch(ConfigMap.class, SystemState.SYSTEM_STATES_CONFIGMAP) | ||
.map(config -> { | ||
SystemState systemState = SystemState.deserialize(config); | ||
return isTrue(systemState.getIsSetup()); | ||
}) | ||
.defaultIfEmpty(false) | ||
.doOnNext(dataInitialized::set); | ||
} | ||
|
||
private Mono<Boolean> hasUser() { | ||
return client.list(User.class, | ||
user -> { | ||
var labels = MetadataUtil.nullSafeLabels(user); | ||
return isNotTrue(labels.get("halo.run/hidden-user")); | ||
}, null, 1, 10) | ||
.map(result -> result.getTotal() > 0) | ||
.defaultIfEmpty(false); | ||
} | ||
|
||
static boolean isNotTrue(String value) { | ||
return !Boolean.parseBoolean(value); | ||
} | ||
} |
26 changes: 26 additions & 0 deletions
26
application/src/main/java/run/halo/app/infra/InitializationStateGetter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package run.halo.app.infra; | ||
|
||
import reactor.core.publisher.Mono; | ||
|
||
/** | ||
* <p>A interface that get system initialization state.</p> | ||
* | ||
* @author guqing | ||
* @since 2.9.0 | ||
*/ | ||
public interface InitializationStateGetter { | ||
|
||
/** | ||
* Check if system user is initialized. | ||
* | ||
* @return <code>true</code> if system user is initialized, <code>false</code> otherwise. | ||
*/ | ||
Mono<Boolean> userInitialized(); | ||
|
||
/** | ||
* Check if system basic data is initialized. | ||
* | ||
* @return <code>true</code> if system basic data is initialized, <code>false</code> otherwise. | ||
*/ | ||
Mono<Boolean> dataInitialized(); | ||
} |
Oops, something went wrong.