From bba242332d8fbeb13cd448ee2ee449f0a5768a40 Mon Sep 17 00:00:00 2001 From: guqing Date: Wed, 9 Oct 2024 12:20:27 +0800 Subject: [PATCH 1/2] refactor: system initialization process to adapt to the new login method Signed-off-by: guqing --- .../halo/app/core/extension/content/Post.java | 1 - .../core/endpoint/console/PluginEndpoint.java | 29 -- .../{ => console}/SystemConfigEndpoint.java | 2 +- .../console/SystemInitializationEndpoint.java | 147 ---------- .../app/infra/DefaultThemeInitializer.java | 65 ----- .../java/run/halo/app/infra/SystemState.java | 35 +++ .../run/halo/app/infra/ValidationUtils.java | 10 +- .../run/halo/app/infra/utils/FileUtils.java | 9 + .../run/halo/app/infra/utils/HaloUtils.java | 15 +- .../run/halo/app/plugin/PluginService.java | 10 +- .../halo/app/plugin/PluginServiceImpl.java | 61 ++-- .../run/halo/app/security/CsrfConfigurer.java | 2 +- ...DefaultServerAuthenticationEntryPoint.java | 4 +- .../InitializeRedirectionWebFilter.java | 14 +- .../security/preauth/SystemSetupEndpoint.java | 274 ++++++++++++++++++ .../halo/app/theme/TemplateEngineManager.java | 19 -- .../halo/app/theme/service/ThemeService.java | 2 + .../app/theme/service/ThemeServiceImpl.java | 48 +++ .../resources/config/i18n/messages.properties | 5 +- .../config/i18n/messages_zh.properties | 3 + .../extensions/role-template-anonymous.yaml | 2 - .../src/main/resources/initial-data.yaml | 239 +++++++++++++++ .../gateway_modules/form_fragments.html | 10 +- .../gateway_modules/input_fragments.html | 5 +- .../main/resources/templates/login_local.html | 4 +- .../src/main/resources/templates/setup.html | 121 ++++++++ .../main/resources/templates/setup.properties | 7 + .../resources/templates/setup_en.properties | 7 + .../resources/templates/setup_es.properties | 7 + .../templates/setup_zh_TW.properties | 7 + .../SystemInitializationEndpointTest.java | 89 ------ .../app/plugin/PluginServiceImplTest.java | 27 -- .../InitializeRedirectionWebFilterTest.java | 67 +++-- .../preauth/SystemSetupEndpointTest.java | 30 ++ 34 files changed, 918 insertions(+), 459 deletions(-) rename application/src/main/java/run/halo/app/core/endpoint/{ => console}/SystemConfigEndpoint.java (99%) delete mode 100644 application/src/main/java/run/halo/app/core/endpoint/console/SystemInitializationEndpoint.java delete mode 100644 application/src/main/java/run/halo/app/infra/DefaultThemeInitializer.java create mode 100644 application/src/main/java/run/halo/app/security/preauth/SystemSetupEndpoint.java create mode 100644 application/src/main/resources/initial-data.yaml create mode 100644 application/src/main/resources/templates/setup.html create mode 100644 application/src/main/resources/templates/setup.properties create mode 100644 application/src/main/resources/templates/setup_en.properties create mode 100644 application/src/main/resources/templates/setup_es.properties create mode 100644 application/src/main/resources/templates/setup_zh_TW.properties delete mode 100644 application/src/test/java/run/halo/app/core/endpoint/console/SystemInitializationEndpointTest.java create mode 100644 application/src/test/java/run/halo/app/security/preauth/SystemSetupEndpointTest.java diff --git a/api/src/main/java/run/halo/app/core/extension/content/Post.java b/api/src/main/java/run/halo/app/core/extension/content/Post.java index 0cc785d71f..8e8096b46b 100644 --- a/api/src/main/java/run/halo/app/core/extension/content/Post.java +++ b/api/src/main/java/run/halo/app/core/extension/content/Post.java @@ -157,7 +157,6 @@ public static class PostSpec { @Data public static class PostStatus { - @Schema(requiredMode = RequiredMode.REQUIRED) private String phase; @Schema diff --git a/application/src/main/java/run/halo/app/core/endpoint/console/PluginEndpoint.java b/application/src/main/java/run/halo/app/core/endpoint/console/PluginEndpoint.java index 728e42a1ba..15dc2c24b0 100644 --- a/application/src/main/java/run/halo/app/core/endpoint/console/PluginEndpoint.java +++ b/application/src/main/java/run/halo/app/core/endpoint/console/PluginEndpoint.java @@ -71,7 +71,6 @@ import run.halo.app.extension.router.SortableRequest; import run.halo.app.infra.ReactiveUrlDataBufferFetcher; import run.halo.app.infra.utils.SettingUtils; -import run.halo.app.plugin.PluginNotFoundException; import run.halo.app.plugin.PluginService; @Slf4j @@ -298,12 +297,6 @@ public RouterFunction endpoint() { .response(responseBuilder() .implementation(ObjectNode.class)) ) - .GET("plugin-presets", this::listPresets, - builder -> builder.operationId("ListPluginPresets") - .description("List all plugin presets in the system.") - .tag(tag) - .response(responseBuilder().implementationArray(Plugin.class)) - ) .GET("plugins/-/bundle.js", this::fetchJsBundle, builder -> builder.operationId("fetchJsBundle") .description("Merge all JS bundles of enabled plugins into one.") @@ -472,10 +465,6 @@ private Mono reload(ServerRequest serverRequest) { return ServerResponse.ok().body(pluginService.reload(name), Plugin.class); } - private Mono listPresets(ServerRequest request) { - return ServerResponse.ok().body(pluginService.getPresets(), Plugin.class); - } - private Mono fetchPluginConfig(ServerRequest request) { final var name = request.pathVariable("name"); return client.fetch(Plugin.class, name) @@ -564,10 +553,6 @@ private Mono install(ServerRequest request) { if (InstallSource.FILE.equals(source)) { return installFromFile(installRequest.getFile(), pluginService::install); } - if (InstallSource.PRESET.equals(source)) { - return installFromPreset(installRequest.getPresetName(), - pluginService::install); - } return Mono.error( new UnsupportedOperationException("Unsupported install source " + source)); })) @@ -586,10 +571,6 @@ private Mono upgrade(ServerRequest request) { return installFromFile(installRequest.getFile(), path -> pluginService.upgrade(pluginName, path)); } - if (InstallSource.PRESET.equals(source)) { - return installFromPreset(installRequest.getPresetName(), - path -> pluginService.upgrade(pluginName, path)); - } return Mono.error( new UnsupportedOperationException("Unsupported install source " + source)); })) @@ -606,16 +587,6 @@ private Mono installFromFile(FilePart filePart, this::deleteFileIfExists); } - private Mono installFromPreset(Mono presetNameMono, - Function> resourceClosure) { - return presetNameMono.flatMap(pluginService::getPreset) - .switchIfEmpty( - Mono.error(() -> new PluginNotFoundException("Plugin preset was not found."))) - .map(pluginPreset -> pluginPreset.getStatus().getLoadLocation()) - .map(Path::of) - .flatMap(resourceClosure); - } - public static class ListRequest extends SortableRequest { public ListRequest(ServerRequest request) { diff --git a/application/src/main/java/run/halo/app/core/endpoint/SystemConfigEndpoint.java b/application/src/main/java/run/halo/app/core/endpoint/console/SystemConfigEndpoint.java similarity index 99% rename from application/src/main/java/run/halo/app/core/endpoint/SystemConfigEndpoint.java rename to application/src/main/java/run/halo/app/core/endpoint/console/SystemConfigEndpoint.java index d6ceb690f1..de6bd4fb07 100644 --- a/application/src/main/java/run/halo/app/core/endpoint/SystemConfigEndpoint.java +++ b/application/src/main/java/run/halo/app/core/endpoint/console/SystemConfigEndpoint.java @@ -1,4 +1,4 @@ -package run.halo.app.core.endpoint; +package run.halo.app.core.endpoint.console; import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder; import static org.springdoc.core.fn.builders.content.Builder.contentBuilder; diff --git a/application/src/main/java/run/halo/app/core/endpoint/console/SystemInitializationEndpoint.java b/application/src/main/java/run/halo/app/core/endpoint/console/SystemInitializationEndpoint.java deleted file mode 100644 index 4bb6b21b37..0000000000 --- a/application/src/main/java/run/halo/app/core/endpoint/console/SystemInitializationEndpoint.java +++ /dev/null @@ -1,147 +0,0 @@ -package run.halo.app.core.endpoint.console; - -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.header.Builder.headerBuilder; -import static org.springdoc.core.fn.builders.requestbody.Builder.requestBodyBuilder; - -import io.swagger.v3.oas.annotations.media.Schema; -import java.net.URI; -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.HttpHeaders; -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.core.extension.endpoint.CustomEndpoint; -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 endpoint() { - var tag = "SystemV1alpha1Console"; - // 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() - .responseCode(HttpStatus.CREATED.value() + "") - .description("System initialization successfully.") - .header(headerBuilder() - .name(HttpHeaders.LOCATION) - .description("Redirect URL.") - ) - ) - ) - .build(); - } - - private Mono 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.created(URI.create("/console")).build()); - } - - private Mono initializeSystem(SystemInitializationRequest requestBody) { - Mono initializeAdminUser = superAdminInitializer.initialize( - SuperAdminInitializer.InitializationParam.builder() - .username(requestBody.getUsername()) - .password(requestBody.getPassword()) - .email(requestBody.getEmail()) - .build()); - - Mono siteSetting = - Mono.defer(() -> client.get(ConfigMap.class, SystemSetting.SYSTEM_CONFIG) - .flatMap(config -> { - Map 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; - } -} diff --git a/application/src/main/java/run/halo/app/infra/DefaultThemeInitializer.java b/application/src/main/java/run/halo/app/infra/DefaultThemeInitializer.java deleted file mode 100644 index 31af2d0a05..0000000000 --- a/application/src/main/java/run/halo/app/infra/DefaultThemeInitializer.java +++ /dev/null @@ -1,65 +0,0 @@ -package run.halo.app.infra; - -import java.io.IOException; -import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.context.event.ApplicationStartedEvent; -import org.springframework.context.ApplicationListener; -import org.springframework.core.io.UrlResource; -import org.springframework.core.io.buffer.DataBufferUtils; -import org.springframework.core.io.buffer.DefaultDataBufferFactory; -import org.springframework.stereotype.Component; -import org.springframework.util.ResourceUtils; -import org.springframework.util.StreamUtils; -import run.halo.app.infra.properties.HaloProperties; -import run.halo.app.infra.properties.ThemeProperties; -import run.halo.app.infra.utils.FileUtils; -import run.halo.app.theme.service.ThemeService; - -@Slf4j -@Component -public class DefaultThemeInitializer implements ApplicationListener { - - private final ThemeService themeService; - - private final ThemeRootGetter themeRoot; - - private final ThemeProperties themeProps; - - public DefaultThemeInitializer(ThemeService themeService, ThemeRootGetter themeRoot, - HaloProperties haloProps) { - this.themeService = themeService; - this.themeRoot = themeRoot; - this.themeProps = haloProps.getTheme(); - } - - @Override - public void onApplicationEvent(ApplicationStartedEvent event) { - if (themeProps.getInitializer().isDisabled()) { - log.debug("Skipped initializing default theme due to disabled"); - return; - } - var themeRoot = this.themeRoot.get(); - var location = themeProps.getInitializer().getLocation(); - try { - // TODO Checking if any themes are installed here in the future might be better? - if (!FileUtils.isEmpty(themeRoot)) { - log.debug("Skipped initializing default theme because there are themes " - + "inside theme root"); - return; - } - log.info("Initializing default theme from {}", location); - var themeUrl = ResourceUtils.getURL(location); - var content = DataBufferUtils.read(new UrlResource(themeUrl), - DefaultDataBufferFactory.sharedInstance, - StreamUtils.BUFFER_SIZE); - var theme = themeService.install(content).block(); - log.info("Initialized default theme: {}", theme); - // Because default active theme is default, we don't need to enabled it manually. - } catch (IOException e) { - // we should skip the initialization error at here - log.warn("Failed to initialize theme from " + location, e); - } - } - - -} diff --git a/application/src/main/java/run/halo/app/infra/SystemState.java b/application/src/main/java/run/halo/app/infra/SystemState.java index 1403a15ccb..03e8a37e02 100644 --- a/application/src/main/java/run/halo/app/infra/SystemState.java +++ b/application/src/main/java/run/halo/app/infra/SystemState.java @@ -3,11 +3,19 @@ import com.fasterxml.jackson.databind.JsonNode; import com.github.fge.jsonpatch.JsonPatchException; import com.github.fge.jsonpatch.mergepatch.JsonMergePatch; +import java.time.Duration; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; +import java.util.function.Consumer; import lombok.Data; +import org.springframework.dao.OptimisticLockingFailureException; import org.springframework.lang.NonNull; +import reactor.core.publisher.Mono; +import reactor.util.retry.Retry; import run.halo.app.extension.ConfigMap; +import run.halo.app.extension.Metadata; +import run.halo.app.extension.ReactiveExtensionClient; import run.halo.app.infra.utils.JsonParseException; import run.halo.app.infra.utils.JsonUtils; @@ -68,6 +76,33 @@ public static void update(@NonNull SystemState systemState, @NonNull ConfigMap c } } + /** + *

Update system state by the given {@link Consumer}.

+ *

if the system state config map does not exist, it will create a new one.

+ */ + public static Mono upsetSystemState(ReactiveExtensionClient client, + Consumer consumer) { + return Mono.defer(() -> client.fetch(ConfigMap.class, SYSTEM_STATES_CONFIGMAP) + .switchIfEmpty(Mono.defer(() -> { + ConfigMap configMap = new ConfigMap(); + configMap.setMetadata(new Metadata()); + configMap.getMetadata().setName(SYSTEM_STATES_CONFIGMAP); + configMap.setData(new HashMap<>()); + return client.create(configMap); + })) + .flatMap(configMap -> { + SystemState systemState = deserialize(configMap); + consumer.accept(systemState); + update(systemState, configMap); + return client.update(configMap); + }) + ) + .retryWhen(Retry.backoff(5, Duration.ofMillis(100)) + .filter(OptimisticLockingFailureException.class::isInstance) + ) + .then(); + } + private static String emptyJsonObject() { return "{}"; } diff --git a/application/src/main/java/run/halo/app/infra/ValidationUtils.java b/application/src/main/java/run/halo/app/infra/ValidationUtils.java index bb52132052..9ce908c365 100644 --- a/application/src/main/java/run/halo/app/infra/ValidationUtils.java +++ b/application/src/main/java/run/halo/app/infra/ValidationUtils.java @@ -6,8 +6,14 @@ @UtilityClass public class ValidationUtils { - public static final Pattern NAME_PATTERN = - Pattern.compile("^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$"); + public static final String NAME_REGEX = + "^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$"; + public static final Pattern NAME_PATTERN = Pattern.compile(NAME_REGEX); + + /** + * No Chinese, no spaces. + */ + public static final String PASSWORD_REGEX = "^(?!.*[\\u4e00-\\u9fa5])(?=\\S+$).+$"; public static final String EMAIL_REGEX = "^[a-zA-Z0-9_+&*-]+(?:\\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,7}$"; diff --git a/application/src/main/java/run/halo/app/infra/utils/FileUtils.java b/application/src/main/java/run/halo/app/infra/utils/FileUtils.java index ca790bfde1..ffaac5cbe7 100644 --- a/application/src/main/java/run/halo/app/infra/utils/FileUtils.java +++ b/application/src/main/java/run/halo/app/infra/utils/FileUtils.java @@ -25,6 +25,7 @@ import java.util.zip.ZipOutputStream; import lombok.extern.slf4j.Slf4j; import org.reactivestreams.Publisher; +import org.springframework.core.io.Resource; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.lang.NonNull; import org.springframework.util.AntPathMatcher; @@ -296,6 +297,14 @@ public static Mono deleteFileSilently(Path file, Scheduler scheduler) { .subscribeOn(scheduler); } + public static void copyResource(Resource resource, Path path) { + try (var inputStream = resource.getInputStream()) { + Files.copy(inputStream, path, REPLACE_EXISTING); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + public static void copy(Path source, Path dest, CopyOption... options) { try { Files.copy(source, dest, options); diff --git a/application/src/main/java/run/halo/app/infra/utils/HaloUtils.java b/application/src/main/java/run/halo/app/infra/utils/HaloUtils.java index 08d9e2cc01..190536bebd 100644 --- a/application/src/main/java/run/halo/app/infra/utils/HaloUtils.java +++ b/application/src/main/java/run/halo/app/infra/utils/HaloUtils.java @@ -5,6 +5,7 @@ import java.nio.charset.StandardCharsets; import java.time.Instant; import java.time.ZoneId; +import lombok.experimental.UtilityClass; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.core.io.ClassPathResource; @@ -14,12 +15,22 @@ import org.springframework.web.reactive.function.server.ServerRequest; /** + * Halo utilities. + * * @author guqing - * @date 2022-04-12 + * @since 2.0.0 */ @Slf4j +@UtilityClass public class HaloUtils { + /** + * Check if the request is an XMLHttpRequest. + */ + public static boolean isXhr(HttpHeaders headers) { + return headers.getOrEmpty("X-Requested-With").contains("XMLHttpRequest"); + } + /** *

Read the file under the classpath as a string.

* @@ -51,7 +62,7 @@ public static String userAgentFrom(ServerRequest request) { // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-CH-UA userAgent = httpHeaders.getFirst("Sec-CH-UA"); } - return StringUtils.defaultString(userAgent, "unknown"); + return StringUtils.defaultIfBlank(userAgent, "unknown"); } public static String getDayText(Instant instant) { diff --git a/application/src/main/java/run/halo/app/plugin/PluginService.java b/application/src/main/java/run/halo/app/plugin/PluginService.java index 193318cd68..3e8bb6e601 100644 --- a/application/src/main/java/run/halo/app/plugin/PluginService.java +++ b/application/src/main/java/run/halo/app/plugin/PluginService.java @@ -10,15 +10,7 @@ public interface PluginService { - Flux getPresets(); - - /** - * Gets a plugin information by preset name from plugin presets. - * - * @param presetName is preset name of plugin. - * @return plugin preset information. - */ - Mono getPreset(String presetName); + Mono installPresetPlugins(); /** * Installs a plugin from a temporary Jar path. diff --git a/application/src/main/java/run/halo/app/plugin/PluginServiceImpl.java b/application/src/main/java/run/halo/app/plugin/PluginServiceImpl.java index 25383e89d4..540dc853b4 100644 --- a/application/src/main/java/run/halo/app/plugin/PluginServiceImpl.java +++ b/application/src/main/java/run/halo/app/plugin/PluginServiceImpl.java @@ -23,6 +23,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.pf4j.DependencyResolver; import org.pf4j.PluginDescriptor; import org.pf4j.PluginWrapper; @@ -36,6 +37,7 @@ import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.core.io.buffer.DefaultDataBufferFactory; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.dao.OptimisticLockingFailureException; import org.springframework.stereotype.Component; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; @@ -110,18 +112,36 @@ void setClock(Clock clock) { } @Override - public Flux getPresets() { - // list presets from classpath - return Flux.defer(() -> getPresetJars() - .map(this::toPath) - .map(path -> new YamlPluginFinder().find(path))); + public Mono installPresetPlugins() { + return getPresetJars() + .flatMap(path -> this.install(path) + .onErrorResume(PluginAlreadyExistsException.class, e -> Mono.empty()) + .flatMap(plugin -> FileUtils.deleteFileSilently(path) + .thenReturn(plugin) + ) + ) + .flatMap(this::enablePlugin) + .subscribeOn(Schedulers.boundedElastic()) + .then(); } - @Override - public Mono getPreset(String presetName) { - return getPresets() - .filter(plugin -> Objects.equals(plugin.getMetadata().getName(), presetName)) - .next(); + private Mono enablePlugin(Plugin plugin) { + plugin.getSpec().setEnabled(true); + return client.update(plugin) + .onErrorResume(OptimisticLockingFailureException.class, + e -> enablePlugin(plugin.getMetadata().getName()) + ); + } + + private Mono enablePlugin(String name) { + return Mono.defer(() -> client.get(Plugin.class, name) + .flatMap(plugin -> { + plugin.getSpec().setEnabled(true); + return client.update(plugin); + }) + ) + .retryWhen(Retry.backoff(8, Duration.ofMillis(100)) + .filter(OptimisticLockingFailureException.class::isInstance)); } @Override @@ -481,24 +501,25 @@ private void satisfiesRequiresVersion(Plugin newPlugin) { } } - private Flux getPresetJars() { + private Flux getPresetJars() { var resolver = new PathMatchingResourcePatternResolver(); try { var resources = resolver.getResources(PRESETS_LOCATION_PATTERN); - return Flux.fromArray(resources); + return Flux.fromArray(resources) + .mapNotNull(resource -> { + var filename = resource.getFilename(); + if (StringUtils.isBlank(filename)) { + return null; + } + var path = tempDir.resolve(filename); + FileUtils.copyResource(resource, path); + return path; + }); } catch (IOException e) { return Flux.error(e); } } - private Path toPath(Resource resource) { - try { - return Path.of(resource.getURI()); - } catch (IOException e) { - throw Exceptions.propagate(e); - } - } - private static void updatePlugin(Plugin oldPlugin, Plugin newPlugin) { var oldMetadata = oldPlugin.getMetadata(); var newMetadata = newPlugin.getMetadata(); diff --git a/application/src/main/java/run/halo/app/security/CsrfConfigurer.java b/application/src/main/java/run/halo/app/security/CsrfConfigurer.java index 0dc3b25e3b..bac91e04bc 100644 --- a/application/src/main/java/run/halo/app/security/CsrfConfigurer.java +++ b/application/src/main/java/run/halo/app/security/CsrfConfigurer.java @@ -18,7 +18,7 @@ public class CsrfConfigurer implements SecurityConfigurer { public void configure(ServerHttpSecurity http) { var csrfMatcher = new AndServerWebExchangeMatcher( CsrfWebFilter.DEFAULT_CSRF_MATCHER, - new NegatedServerWebExchangeMatcher(pathMatchers("/api/**", "/apis/**") + new NegatedServerWebExchangeMatcher(pathMatchers("/api/**", "/apis/**", "/system/setup") )); http.csrf(csrfSpec -> csrfSpec .csrfTokenRepository(withHttpOnlyFalse()) diff --git a/application/src/main/java/run/halo/app/security/DefaultServerAuthenticationEntryPoint.java b/application/src/main/java/run/halo/app/security/DefaultServerAuthenticationEntryPoint.java index 24dfbf387a..dfb6b4fa12 100644 --- a/application/src/main/java/run/halo/app/security/DefaultServerAuthenticationEntryPoint.java +++ b/application/src/main/java/run/halo/app/security/DefaultServerAuthenticationEntryPoint.java @@ -10,6 +10,7 @@ import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher.MatchResult; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; +import run.halo.app.infra.utils.HaloUtils; /** * Default authentication entry point. @@ -22,8 +23,7 @@ public class DefaultServerAuthenticationEntryPoint implements ServerAuthenticationEntryPoint { private final ServerWebExchangeMatcher xhrMatcher = exchange -> { - if (exchange.getRequest().getHeaders().getOrEmpty("X-Requested-With") - .contains("XMLHttpRequest")) { + if (HaloUtils.isXhr(exchange.getRequest().getHeaders())) { return MatchResult.match(); } return MatchResult.notMatch(); diff --git a/application/src/main/java/run/halo/app/security/InitializeRedirectionWebFilter.java b/application/src/main/java/run/halo/app/security/InitializeRedirectionWebFilter.java index 08f2e373c2..d4820d9218 100644 --- a/application/src/main/java/run/halo/app/security/InitializeRedirectionWebFilter.java +++ b/application/src/main/java/run/halo/app/security/InitializeRedirectionWebFilter.java @@ -1,13 +1,17 @@ package run.halo.app.security; +import static org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers.pathMatchers; + import java.net.URI; import lombok.Getter; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; import org.springframework.lang.NonNull; import org.springframework.security.web.server.DefaultServerRedirectStrategy; import org.springframework.security.web.server.ServerRedirectStrategy; -import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher; +import org.springframework.security.web.server.util.matcher.AndServerWebExchangeMatcher; +import org.springframework.security.web.server.util.matcher.MediaTypeServerWebExchangeMatcher; import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher; import org.springframework.stereotype.Component; import org.springframework.util.Assert; @@ -26,9 +30,11 @@ @Component @RequiredArgsConstructor public class InitializeRedirectionWebFilter implements WebFilter { - private final URI location = URI.create("/console"); - private final ServerWebExchangeMatcher redirectMatcher = - new PathPatternParserServerWebExchangeMatcher("/", HttpMethod.GET); + private final URI location = URI.create("/system/setup"); + private final ServerWebExchangeMatcher redirectMatcher = new AndServerWebExchangeMatcher( + pathMatchers(HttpMethod.GET, "/", "/console/**", "/uc/**", "/login", "/signup"), + new MediaTypeServerWebExchangeMatcher(MediaType.TEXT_HTML) + ); private final InitializationStateGetter initializationStateGetter; diff --git a/application/src/main/java/run/halo/app/security/preauth/SystemSetupEndpoint.java b/application/src/main/java/run/halo/app/security/preauth/SystemSetupEndpoint.java new file mode 100644 index 0000000000..afe07f8ff8 --- /dev/null +++ b/application/src/main/java/run/halo/app/security/preauth/SystemSetupEndpoint.java @@ -0,0 +1,274 @@ +package run.halo.app.security.preauth; + +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 static org.springframework.web.reactive.function.server.RequestPredicates.accept; +import static org.springframework.web.reactive.function.server.RequestPredicates.contentType; +import static org.springframework.web.reactive.function.server.RequestPredicates.path; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.time.Instant; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Properties; +import lombok.RequiredArgsConstructor; +import org.springdoc.core.fn.builders.content.Builder; +import org.springdoc.webflux.core.fn.SpringdocRouteBuilder; +import org.springframework.beans.factory.config.PlaceholderConfigurerSupport; +import org.springframework.context.annotation.Bean; +import org.springframework.core.io.ClassPathResource; +import org.springframework.dao.OptimisticLockingFailureException; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.security.util.InMemoryResource; +import org.springframework.stereotype.Component; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.util.PropertyPlaceholderHelper; +import org.springframework.util.StreamUtils; +import org.springframework.validation.BeanPropertyBindingResult; +import org.springframework.validation.BindingResult; +import org.springframework.validation.Validator; +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 reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Schedulers; +import reactor.util.retry.Retry; +import run.halo.app.extension.ConfigMap; +import run.halo.app.extension.ReactiveExtensionClient; +import run.halo.app.extension.Unstructured; +import run.halo.app.infra.InitializationStateGetter; +import run.halo.app.infra.SystemConfigurableEnvironmentFetcher; +import run.halo.app.infra.SystemSetting; +import run.halo.app.infra.SystemState; +import run.halo.app.infra.ValidationUtils; +import run.halo.app.infra.exception.RequestBodyValidationException; +import run.halo.app.infra.utils.HaloUtils; +import run.halo.app.infra.utils.JsonUtils; +import run.halo.app.infra.utils.YamlUnstructuredLoader; +import run.halo.app.plugin.PluginService; +import run.halo.app.security.SuperAdminInitializer; +import run.halo.app.theme.service.ThemeService; + +@Component +@RequiredArgsConstructor +public class SystemSetupEndpoint { + static final String SETUP_TEMPLATE = "setup"; + static final PropertyPlaceholderHelper PROPERTY_PLACEHOLDER_HELPER = + new PropertyPlaceholderHelper( + PlaceholderConfigurerSupport.DEFAULT_PLACEHOLDER_PREFIX, + PlaceholderConfigurerSupport.DEFAULT_PLACEHOLDER_SUFFIX + ); + + private final InitializationStateGetter initializationStateGetter; + private final SystemConfigurableEnvironmentFetcher systemConfigFetcher; + private final SuperAdminInitializer superAdminInitializer; + private final ReactiveExtensionClient client; + private final PluginService pluginService; + private final ThemeService themeService; + private final Validator validator; + + @Bean + RouterFunction setupPageRouter() { + final var tag = "System"; + return SpringdocRouteBuilder.route() + .GET(path("/system/setup").and(accept(MediaType.TEXT_HTML)), this::setupPage, + builder -> builder.operationId("JumpToSetupPage") + .description("Jump to setup page") + .tag(tag) + .response(responseBuilder() + .content(Builder.contentBuilder() + .mediaType(MediaType.TEXT_HTML_VALUE)) + .implementation(String.class) + ) + ) + .POST("/system/setup", contentType(MediaType.APPLICATION_FORM_URLENCODED), this::setup, + builder -> builder + .operationId("SetupSystem") + .description("Setup system") + .tag(tag) + .requestBody(requestBodyBuilder() + .implementation(SetupRequest.class) + .content(Builder.contentBuilder() + .mediaType(MediaType.APPLICATION_FORM_URLENCODED_VALUE) + ) + ) + .response(responseBuilder() + .responseCode(String.valueOf(HttpStatus.NO_CONTENT.value())) + .implementation(Void.class) + ) + ) + .build(); + } + + private Mono setup(ServerRequest request) { + return request.formData() + .map(SetupRequest::new) + .filterWhen(body -> initializationStateGetter.userInitialized() + .map(initialized -> !initialized) + ) + .flatMap(body -> { + var bindingResult = body.toBindingResult(); + validator.validate(body, bindingResult); + if (bindingResult.hasErrors()) { + return handleValidationErrors(bindingResult, request); + } + return doInitialization(body) + .then(Mono.defer(() -> handleSetupSuccessfully(request))); + }); + } + + private static Mono handleSetupSuccessfully(ServerRequest request) { + if (isHtmlRequest(request)) { + return redirectToConsole(); + } + return ServerResponse.noContent().build(); + } + + private Mono handleValidationErrors(BindingResult bindingResult, + ServerRequest request) { + if (isHtmlRequest(request)) { + return ServerResponse.status(HttpStatus.BAD_REQUEST) + .render(SETUP_TEMPLATE, bindingResult.getModel()); + } + return Mono.error(new RequestBodyValidationException(bindingResult)); + } + + private static boolean isHtmlRequest(ServerRequest request) { + return request.headers().accept().contains(MediaType.TEXT_HTML) + && !HaloUtils.isXhr(request.headers().asHttpHeaders()); + } + + private static Mono redirectToConsole() { + return ServerResponse.temporaryRedirect(URI.create("/console")).build(); + } + + private Mono doInitialization(SetupRequest body) { + var superUserMono = superAdminInitializer.initialize( + SuperAdminInitializer.InitializationParam.builder() + .username(body.getUsername()) + .password(body.getPassword()) + .email(body.getEmail()) + .build() + ) + .subscribeOn(Schedulers.boundedElastic()); + + var basicConfigMono = Mono.defer(() -> systemConfigFetcher.getConfigMap() + .flatMap(configMap -> { + mergeToBasicConfig(body, configMap); + return client.update(configMap); + }) + ) + .retryWhen(Retry.backoff(5, Duration.ofMillis(100)) + .filter(t -> t instanceof OptimisticLockingFailureException) + ) + .subscribeOn(Schedulers.boundedElastic()) + .then(); + return Mono.when(superUserMono, basicConfigMono, + initializeNecessaryData(body.getUsername()), + pluginService.installPresetPlugins(), + themeService.installPresetTheme() + ) + .then(SystemState.upsetSystemState(client, state -> state.setIsSetup(true))); + } + + private Mono initializeNecessaryData(String username) { + return loadPresetExtensions(username) + .concatMap(client::create) + .subscribeOn(Schedulers.boundedElastic()) + .then(); + } + + private static void mergeToBasicConfig(SetupRequest body, ConfigMap configMap) { + Map data = configMap.getData(); + if (data == null) { + data = new LinkedHashMap<>(); + configMap.setData(data); + } + String basic = data.getOrDefault(SystemSetting.Basic.GROUP, "{}"); + var basicSetting = JsonUtils.jsonToObject(basic, SystemSetting.Basic.class); + basicSetting.setTitle(body.getSiteTitle()); + data.put(SystemSetting.Basic.GROUP, JsonUtils.objectToJson(basicSetting)); + } + + private Mono setupPage(ServerRequest request) { + return initializationStateGetter.userInitialized() + .flatMap(initialized -> { + if (initialized) { + return redirectToConsole(); + } + var body = new SetupRequest(new LinkedMultiValueMap<>()); + var bindingResult = body.toBindingResult(); + return ServerResponse.ok().render(SETUP_TEMPLATE, bindingResult.getModel()); + }); + } + + record SetupRequest(MultiValueMap formData) { + + @Schema(requiredMode = REQUIRED, minLength = 4, maxLength = 63) + @NotBlank + @Size(min = 4, max = 63) + @Pattern(regexp = ValidationUtils.NAME_REGEX, + message = "{validation.error.username.pattern}") + public String getUsername() { + return formData.getFirst("username"); + } + + @Schema(requiredMode = REQUIRED, minLength = 5, maxLength = 257) + @NotBlank + @Pattern(regexp = ValidationUtils.PASSWORD_REGEX, + message = "{validation.error.password.pattern}") + @Size(min = 5, max = 257) + public String getPassword() { + return formData.getFirst("password"); + } + + @Email + public String getEmail() { + return formData.getFirst("email"); + } + + @NotBlank + @Size(max = 80) + public String getSiteTitle() { + return formData.getFirst("siteTitle"); + } + + public BindingResult toBindingResult() { + return new BeanPropertyBindingResult(this, "form"); + } + } + + Flux loadPresetExtensions(String username) { + return Mono.fromCallable( + () -> { + // read initial-data.yaml to string + var classPathResource = new ClassPathResource("initial-data.yaml"); + String rawContent = StreamUtils.copyToString(classPathResource.getInputStream(), + StandardCharsets.UTF_8); + // build properties + var properties = new Properties(); + properties.setProperty("username", username); + properties.setProperty("timestamp", Instant.now().toString()); + // replace placeholders + var processedContent = + PROPERTY_PLACEHOLDER_HELPER.replacePlaceholders(rawContent, properties); + // load yaml to unstructured + var stringResource = new InMemoryResource(processedContent); + var loader = new YamlUnstructuredLoader(stringResource); + return loader.load(); + }) + .flatMapMany(Flux::fromIterable) + .subscribeOn(Schedulers.boundedElastic()); + } +} \ No newline at end of file diff --git a/application/src/main/java/run/halo/app/theme/TemplateEngineManager.java b/application/src/main/java/run/halo/app/theme/TemplateEngineManager.java index 36ac57da3b..9b9487d726 100644 --- a/application/src/main/java/run/halo/app/theme/TemplateEngineManager.java +++ b/application/src/main/java/run/halo/app/theme/TemplateEngineManager.java @@ -1,13 +1,10 @@ package run.halo.app.theme; -import java.io.FileNotFoundException; -import java.nio.file.Path; import lombok.NonNull; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafProperties; import org.springframework.stereotype.Component; import org.springframework.util.ConcurrentLruCache; -import org.springframework.util.ResourceUtils; import org.thymeleaf.TemplateEngine; import org.thymeleaf.dialect.IDialect; import org.thymeleaf.spring6.ISpringWebFluxTemplateEngine; @@ -17,7 +14,6 @@ import org.thymeleaf.templateresolver.ITemplateResolver; import reactor.core.publisher.Mono; import run.halo.app.infra.ExternalUrlSupplier; -import run.halo.app.infra.exception.NotFoundException; import run.halo.app.plugin.HaloPluginManager; import run.halo.app.theme.dialect.HaloProcessorDialect; import run.halo.app.theme.engine.HaloTemplateEngine; @@ -71,24 +67,9 @@ public TemplateEngineManager(ThymeleafProperties thymeleafProperties, public ISpringWebFluxTemplateEngine getTemplateEngine(ThemeContext theme) { CacheKey cacheKey = buildCacheKey(theme); - // cache not exists, will create new engine - if (!engineCache.contains(cacheKey)) { - // before this, check if theme exists - if (!fileExists(theme.getPath())) { - throw new NotFoundException("Theme not found."); - } - } return engineCache.get(cacheKey); } - private boolean fileExists(Path path) { - try { - return ResourceUtils.getFile(path.toUri()).exists(); - } catch (FileNotFoundException e) { - return false; - } - } - public Mono clearCache(String themeName) { return themeResolver.getThemeContext(themeName) .doOnNext(themeContext -> { diff --git a/application/src/main/java/run/halo/app/theme/service/ThemeService.java b/application/src/main/java/run/halo/app/theme/service/ThemeService.java index 007b4d3e58..7a6fa547a7 100644 --- a/application/src/main/java/run/halo/app/theme/service/ThemeService.java +++ b/application/src/main/java/run/halo/app/theme/service/ThemeService.java @@ -8,6 +8,8 @@ public interface ThemeService { + Mono installPresetTheme(); + Mono install(Publisher content); Mono upgrade(String themeName, Publisher content); diff --git a/application/src/main/java/run/halo/app/theme/service/ThemeServiceImpl.java b/application/src/main/java/run/halo/app/theme/service/ThemeServiceImpl.java index c1abe453de..37a7b142d5 100644 --- a/application/src/main/java/run/halo/app/theme/service/ThemeServiceImpl.java +++ b/application/src/main/java/run/halo/app/theme/service/ThemeServiceImpl.java @@ -8,6 +8,8 @@ import static run.halo.app.theme.service.ThemeUtils.locateThemeManifest; import static run.halo.app.theme.service.ThemeUtils.unzipThemeTo; +import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; import java.time.Duration; import java.util.HashMap; @@ -18,11 +20,17 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.reactivestreams.Publisher; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.UrlResource; import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.core.io.buffer.DataBufferUtils; +import org.springframework.core.io.buffer.DefaultDataBufferFactory; import org.springframework.dao.OptimisticLockingFailureException; import org.springframework.retry.RetryException; import org.springframework.stereotype.Service; import org.springframework.util.Assert; +import org.springframework.util.ResourceUtils; +import org.springframework.util.StreamUtils; import org.springframework.web.server.ServerErrorException; import org.springframework.web.server.ServerWebInputException; import reactor.core.publisher.Flux; @@ -41,6 +49,8 @@ import run.halo.app.infra.ThemeRootGetter; import run.halo.app.infra.exception.ThemeUpgradeException; import run.halo.app.infra.exception.UnsatisfiedAttributeValueException; +import run.halo.app.infra.properties.HaloProperties; +import run.halo.app.infra.utils.FileUtils; import run.halo.app.infra.utils.SettingUtils; import run.halo.app.infra.utils.VersionUtils; @@ -53,10 +63,48 @@ public class ThemeServiceImpl implements ThemeService { private final ThemeRootGetter themeRoot; + private final HaloProperties haloProperties; + private final SystemVersionSupplier systemVersionSupplier; private final Scheduler scheduler = Schedulers.boundedElastic(); + @Override + public Mono installPresetTheme() { + var themeProps = haloProperties.getTheme(); + var location = themeProps.getInitializer().getLocation(); + return createThemeTempPath() + .flatMap(tempPath -> Mono.usingWhen(copyPresetThemeToPath(location, tempPath), + path -> { + var content = DataBufferUtils.read(new FileSystemResource(path), + DefaultDataBufferFactory.sharedInstance, + StreamUtils.BUFFER_SIZE); + return install(content); + }, path -> deleteRecursivelyAndSilently(tempPath, scheduler) + )) + .onErrorResume(IOException.class, e -> { + log.warn("Failed to initialize theme from {}", location, e); + return Mono.empty(); + }) + .then(); + } + + private Mono copyPresetThemeToPath(String location, Path tempDir) { + return Mono.fromCallable( + () -> { + var themeUrl = ResourceUtils.getURL(location); + var resource = new UrlResource(themeUrl); + var tempThemePath = tempDir.resolve("theme.zip"); + FileUtils.copyResource(resource, tempThemePath); + return tempThemePath; + }); + } + + private static Mono createThemeTempPath() { + return Mono.fromCallable(() -> Files.createTempDirectory("halo-theme-preset")) + .subscribeOn(Schedulers.boundedElastic()); + } + @Override public Mono install(Publisher content) { var themeRoot = this.themeRoot.get(); diff --git a/application/src/main/resources/config/i18n/messages.properties b/application/src/main/resources/config/i18n/messages.properties index b053fc49c9..1123fcc8fc 100644 --- a/application/src/main/resources/config/i18n/messages.properties +++ b/application/src/main/resources/config/i18n/messages.properties @@ -86,4 +86,7 @@ problemDetail.comment.waitingForApproval=Comment is awaiting approval. title.visibility.identification.private=(Private) signup.error.confirm-password-not-match=The confirmation password does not match the password. -signup.error.email-code.invalid=Invalid email code. \ No newline at end of file +signup.error.email-code.invalid=Invalid email code. + +validation.error.username.pattern=The username can only be lowercase and can only contain letters, numbers, hyphens, and dots, starting and ending with characters. +validation.error.password.pattern=The password cannot contain Chinese characters and spaces. \ No newline at end of file diff --git a/application/src/main/resources/config/i18n/messages_zh.properties b/application/src/main/resources/config/i18n/messages_zh.properties index 8655c96a08..3b5883143e 100644 --- a/application/src/main/resources/config/i18n/messages_zh.properties +++ b/application/src/main/resources/config/i18n/messages_zh.properties @@ -60,3 +60,6 @@ problemDetail.comment.waitingForApproval=评论审核中。 title.visibility.identification.private=(私有) signup.error.confirm-password-not-match=确认密码与密码不匹配。 signup.error.email-code.invalid=邮箱验证码无效。 + +validation.error.username.pattern=用户名只能小写且只能包含字母、数字、中划线和点,以字符开头和结尾 +validation.error.password.pattern=密码不能包含中文和空格 \ No newline at end of file diff --git a/application/src/main/resources/extensions/role-template-anonymous.yaml b/application/src/main/resources/extensions/role-template-anonymous.yaml index 8f2ffce10e..0e606e55f4 100644 --- a/application/src/main/resources/extensions/role-template-anonymous.yaml +++ b/application/src/main/resources/extensions/role-template-anonymous.yaml @@ -23,8 +23,6 @@ rules: verbs: [ "create" ] - nonResourceURLs: [ "/actuator/globalinfo", "/actuator/health", "/actuator/health/*", "/login/public-key" ] verbs: [ "get" ] - - nonResourceURLs: [ "/apis/api.console.halo.run/v1alpha1/system/initialize" ] - verbs: [ "create" ] --- apiVersion: v1alpha1 kind: "Role" diff --git a/application/src/main/resources/initial-data.yaml b/application/src/main/resources/initial-data.yaml new file mode 100644 index 0000000000..66112e3ac5 --- /dev/null +++ b/application/src/main/resources/initial-data.yaml @@ -0,0 +1,239 @@ +# 提供了 timestamp、username 变量,用于初始化数据时填充时间戳和用户名 +# 初始化文章关联的分类、标签数据 +apiVersion: content.halo.run/v1alpha1 +kind: Category +metadata: + name: 76514a40-6ef1-4ed9-b58a-e26945bde3ca +spec: + displayName: 默认分类 + slug: default + description: 这是你的默认分类,如不需要,删除即可。 + cover: "" + template: "" + priority: 0 + children: [ ] +status: + permalink: "/categories/default" + +--- +apiVersion: content.halo.run/v1alpha1 +kind: Tag +metadata: + name: c33ceabb-d8f1-4711-8991-bb8f5c92ad7c +spec: + displayName: Halo + slug: halo + color: "#ffffff" + cover: "" +status: + permalink: "/tags/halo" + +--- +# 文章关联的内容 +apiVersion: content.halo.run/v1alpha1 +kind: Snapshot +metadata: + name: fb5cd6bd-998d-4ccc-984d-5cc23b0a09f9 + annotations: + content.halo.run/keep-raw: "true" +spec: + subjectRef: + group: content.halo.run + version: v1alpha1 + kind: Post + name: 5152aea5-c2e8-4717-8bba-2263d46e19d5 + rawType: HTML + rawPatch:

Hello + Halo

如果你看到了这一篇文章,那么证明你已经安装成功了,感谢使用 Halo + 进行创作,希望能够使用愉快。

相关链接

在使用过程中,有任何问题都可以通过以上链接找寻答案,或者联系我们。

这是一篇自动生成的文章,请删除这篇文章之后开始你的创作吧!

+ contentPatch:

Hello + Halo

如果你看到了这一篇文章,那么证明你已经安装成功了,感谢使用 Halo + 进行创作,希望能够使用愉快。

相关链接

在使用过程中,有任何问题都可以通过以上链接找寻答案,或者联系我们。

这是一篇自动生成的文章,请删除这篇文章之后开始你的创作吧!

+ lastModifyTime: "${timestamp}" + owner: "${username}" + contributors: + - "${username}" + +--- +# 初始化文章数据 +apiVersion: content.halo.run/v1alpha1 +kind: Post +metadata: + name: 5152aea5-c2e8-4717-8bba-2263d46e19d5 +spec: + title: Hello Halo + slug: hello-halo + releaseSnapshot: fb5cd6bd-998d-4ccc-984d-5cc23b0a09f9 + headSnapshot: fb5cd6bd-998d-4ccc-984d-5cc23b0a09f9 + baseSnapshot: fb5cd6bd-998d-4ccc-984d-5cc23b0a09f9 + owner: "${username}" + template: "" + cover: "" + deleted: false + publish: true + publishTime: "${timestamp}" + pinned: false + allowComment: true + visible: PUBLIC + priority: 0 + excerpt: + autoGenerate: false + raw: 如果你看到了这一篇文章,那么证明你已经安装成功了,感谢使用 Halo 进行创作,希望能够使用愉快。 + categories: + - 76514a40-6ef1-4ed9-b58a-e26945bde3ca + tags: + - c33ceabb-d8f1-4711-8991-bb8f5c92ad7c + htmlMetas: [ ] +status: + permalink: /archives/hello-halo + +--- +# 自定义页面关联的内容 +apiVersion: content.halo.run/v1alpha1 +kind: Snapshot +metadata: + name: c3f73cc2-194e-4cd8-9092-7386aa50a0e5 + annotations: + content.halo.run/keep-raw: "true" +spec: + subjectRef: + group: content.halo.run + version: v1alpha1 + kind: SinglePage + name: 373a5f79-f44f-441a-9df1-85a4f553ece8 + rawType: HTML + rawPatch:

关于页面

这是一个自定义页面,你可以在后台的 页面 + -> 自定义页面 + 找到它,你可以用于新建关于页面、联系我们页面等等。

这是一篇自动生成的页面,你可以在后台删除它。

+ contentPatch:

关于页面

这是一个自定义页面,你可以在后台的 页面 + -> 自定义页面 + 找到它,你可以用于新建关于页面、联系我们页面等等。

这是一篇自动生成的页面,你可以在后台删除它。

+ lastModifyTime: "${timestamp}" + owner: "${username}" + contributors: + - "${username}" + +--- +# 初始化自定义页面数据 +apiVersion: content.halo.run/v1alpha1 +kind: SinglePage +metadata: + name: 373a5f79-f44f-441a-9df1-85a4f553ece8 +spec: + title: 关于 + slug: about + template: "" + cover: "" + owner: "${username}" + deleted: false + publish: true + baseSnapshot: c3f73cc2-194e-4cd8-9092-7386aa50a0e5 + headSnapshot: c3f73cc2-194e-4cd8-9092-7386aa50a0e5 + releaseSnapshot: c3f73cc2-194e-4cd8-9092-7386aa50a0e5 + pinned: false + allowComment: true + visible: PUBLIC + version: 1 + priority: 0 + excerpt: + autoGenerate: false + raw: 这是一个自定义页面,你可以在后台的 页面 -> 自定义页面 找到它,你可以用于新建关于页面、联系我们页面等等。 + htmlMetas: [ ] +status: + permalink: "/about" + +--- +# 首页菜单项 +apiVersion: v1alpha1 +kind: MenuItem +metadata: + name: 88c3f10b-321c-4092-86a8-70db00251b74 +spec: + displayName: 首页 + href: / + children: [ ] + priority: 0 +--- +# 关联到文章作为菜单 +apiVersion: v1alpha1 +kind: MenuItem +metadata: + name: c4c814d1-0c2c-456b-8c96-4864965fee94 +spec: + displayName: "Hello Halo" + href: "/archives/hello-halo" + children: [ ] + priority: 1 + targetRef: + group: content.halo.run + version: v1alpha1 + kind: Post + name: 5152aea5-c2e8-4717-8bba-2263d46e19d5 +--- +# 关联到标签作为菜单 +apiVersion: v1alpha1 +kind: MenuItem +metadata: + name: 35869bd3-33b5-448b-91ee-cf6517a59644 +spec: + displayName: "Halo" + href: "/tags/halo" + children: [ ] + priority: 2 + targetRef: + group: content.halo.run + version: v1alpha1 + kind: Tag + name: c33ceabb-d8f1-4711-8991-bb8f5c92ad7c +--- +# 关联到自定义页面作为菜单 +apiVersion: v1alpha1 +kind: MenuItem +metadata: + name: b0d041fa-dc99-48f6-a193-8604003379cf +spec: + displayName: "关于" + href: "/about" + children: [ ] + priority: 3 + targetRef: + group: content.halo.run + version: v1alpha1 + kind: SinglePage + name: 373a5f79-f44f-441a-9df1-85a4f553ece8 +--- +apiVersion: v1alpha1 +kind: Menu +metadata: + name: primary +spec: + displayName: 主菜单 + menuItems: + - 88c3f10b-321c-4092-86a8-70db00251b74 + - c4c814d1-0c2c-456b-8c96-4864965fee94 + - 35869bd3-33b5-448b-91ee-cf6517a59644 + - b0d041fa-dc99-48f6-a193-8604003379cf diff --git a/application/src/main/resources/templates/gateway_modules/form_fragments.html b/application/src/main/resources/templates/gateway_modules/form_fragments.html index 1533e1cfa1..269771c092 100644 --- a/application/src/main/resources/templates/gateway_modules/form_fragments.html +++ b/application/src/main/resources/templates/gateway_modules/form_fragments.html @@ -134,7 +134,7 @@

@@ -142,7 +142,7 @@

@@ -254,13 +254,13 @@
@@ -294,4 +294,4 @@
- + \ No newline at end of file diff --git a/application/src/main/resources/templates/gateway_modules/input_fragments.html b/application/src/main/resources/templates/gateway_modules/input_fragments.html index ffffb5cd6a..9a21e3cdce 100644 --- a/application/src/main/resources/templates/gateway_modules/input_fragments.html +++ b/application/src/main/resources/templates/gateway_modules/input_fragments.html @@ -1,4 +1,4 @@ -
+
@@ -28,4 +29,4 @@
-
+
\ No newline at end of file diff --git a/application/src/main/resources/templates/login_local.html b/application/src/main/resources/templates/login_local.html index 3ae24d4001..a6021c6f11 100644 --- a/application/src/main/resources/templates/login_local.html +++ b/application/src/main/resources/templates/login_local.html @@ -50,7 +50,7 @@ - + \ No newline at end of file diff --git a/application/src/main/resources/templates/setup.html b/application/src/main/resources/templates/setup.html new file mode 100644 index 0000000000..3bc8788b61 --- /dev/null +++ b/application/src/main/resources/templates/setup.html @@ -0,0 +1,121 @@ + + + + + + +
+
+ +
+

+ +
+
+ +
+ +
+

+
+ +
+ +
+ +
+

+
+ +
+ +
+ +
+

+
+ +
+ + +

+
+ +
+ + +
+ +
+ +
+
+
+ +
+
+ + +
+ \ No newline at end of file diff --git a/application/src/main/resources/templates/setup.properties b/application/src/main/resources/templates/setup.properties new file mode 100644 index 0000000000..139b4c5d1d --- /dev/null +++ b/application/src/main/resources/templates/setup.properties @@ -0,0 +1,7 @@ +title=系统初始化 +form.siteTitle.label=站点标题 +form.username.label=用户名 +form.email.label=电子邮箱 +form.password.label=密码 +form.confirmPassword.label=确认密码 +form.submit=初始化 \ No newline at end of file diff --git a/application/src/main/resources/templates/setup_en.properties b/application/src/main/resources/templates/setup_en.properties new file mode 100644 index 0000000000..f65c17eee1 --- /dev/null +++ b/application/src/main/resources/templates/setup_en.properties @@ -0,0 +1,7 @@ +title=Setup +form.siteTitle.label=Site title +form.username.label=Username +form.email.label=Email +form.password.label=Password +form.confirmPassword.label=Confirm Password +form.submit=Setup diff --git a/application/src/main/resources/templates/setup_es.properties b/application/src/main/resources/templates/setup_es.properties new file mode 100644 index 0000000000..f4041620a8 --- /dev/null +++ b/application/src/main/resources/templates/setup_es.properties @@ -0,0 +1,7 @@ +title=Configuración +form.siteTitle.label=Título del Sitio +form.username.label=Nombre de Usuario +form.email.label=Correo Electrónico +form.password.label=Contraseña +form.confirmPassword.label=Confirmar Contraseña +form.submit=Configurar \ No newline at end of file diff --git a/application/src/main/resources/templates/setup_zh_TW.properties b/application/src/main/resources/templates/setup_zh_TW.properties new file mode 100644 index 0000000000..1f2e3a147b --- /dev/null +++ b/application/src/main/resources/templates/setup_zh_TW.properties @@ -0,0 +1,7 @@ +title=系統初始化 +form.siteTitle.label=站點標題 +form.username.label=使用者名稱 +form.email.label=電子郵件 +form.password.label=密碼 +form.confirmPassword.label=確認密碼 +form.submit=初始化 \ No newline at end of file diff --git a/application/src/test/java/run/halo/app/core/endpoint/console/SystemInitializationEndpointTest.java b/application/src/test/java/run/halo/app/core/endpoint/console/SystemInitializationEndpointTest.java deleted file mode 100644 index cd2238e863..0000000000 --- a/application/src/test/java/run/halo/app/core/endpoint/console/SystemInitializationEndpointTest.java +++ /dev/null @@ -1,89 +0,0 @@ -package run.halo.app.core.endpoint.console; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.springframework.test.web.reactive.server.WebTestClient.bindToRouterFunction; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.test.web.reactive.server.WebTestClient; -import reactor.core.publisher.Mono; -import run.halo.app.core.endpoint.console.SystemInitializationEndpoint.SystemInitializationRequest; -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.security.SuperAdminInitializer; -import run.halo.app.security.SuperAdminInitializer.InitializationParam; - -/** - * Tests for {@link SystemInitializationEndpoint}. - * - * @author guqing - * @since 2.9.0 - */ -@ExtendWith(MockitoExtension.class) -class SystemInitializationEndpointTest { - - @Mock - InitializationStateGetter initializationStateGetter; - - @Mock - SuperAdminInitializer superAdminInitializer; - - @Mock - ReactiveExtensionClient client; - - @InjectMocks - SystemInitializationEndpoint initializationEndpoint; - - WebTestClient webTestClient; - - @BeforeEach - void setUp() { - webTestClient = bindToRouterFunction(initializationEndpoint.endpoint()).build(); - } - - @Test - void initializeWithoutRequestBody() { - webTestClient.post() - .uri("/system/initialize") - .exchange() - .expectStatus() - .isBadRequest(); - } - - @Test - void initializeWithRequestBody() { - var initialization = new SystemInitializationRequest(); - initialization.setUsername("faker"); - initialization.setPassword("openfaker"); - initialization.setEmail("faker@halo.run"); - initialization.setSiteTitle("Fake Site"); - - when(initializationStateGetter.userInitialized()).thenReturn(Mono.just(false)); - when(superAdminInitializer.initialize(any(InitializationParam.class))) - .thenReturn(Mono.empty()); - - var configMap = new ConfigMap(); - when(client.get(ConfigMap.class, SystemSetting.SYSTEM_CONFIG)) - .thenReturn(Mono.just(configMap)); - when(client.update(configMap)).thenReturn(Mono.just(configMap)); - - webTestClient.post().uri("/system/initialize") - .bodyValue(initialization) - .exchange() - .expectStatus().isCreated() - .expectHeader().location("/console"); - - verify(initializationStateGetter).userInitialized(); - verify(superAdminInitializer).initialize(any()); - verify(client).get(ConfigMap.class, SystemSetting.SYSTEM_CONFIG); - verify(client).update(configMap); - } -} diff --git a/application/src/test/java/run/halo/app/plugin/PluginServiceImplTest.java b/application/src/test/java/run/halo/app/plugin/PluginServiceImplTest.java index bb38bd0fd9..94734dea6b 100644 --- a/application/src/test/java/run/halo/app/plugin/PluginServiceImplTest.java +++ b/application/src/test/java/run/halo/app/plugin/PluginServiceImplTest.java @@ -77,33 +77,6 @@ class PluginServiceImplTest { @InjectMocks PluginServiceImpl pluginService; - @Test - void getPresetsTest() { - var presets = pluginService.getPresets(); - StepVerifier.create(presets) - .assertNext(plugin -> { - assertEquals("fake-plugin", plugin.getMetadata().getName()); - assertEquals("0.0.2", plugin.getSpec().getVersion()); - assertEquals(Plugin.Phase.PENDING, plugin.getStatus().getPhase()); - }) - .verifyComplete(); - } - - @Test - void getPresetIfNotFound() { - var plugin = pluginService.getPreset("not-found-plugin"); - StepVerifier.create(plugin) - .verifyComplete(); - } - - @Test - void getPresetIfFound() { - var plugin = pluginService.getPreset("fake-plugin"); - StepVerifier.create(plugin) - .expectNextCount(1) - .verifyComplete(); - } - @Nested class InstallUpdateReloadTest { diff --git a/application/src/test/java/run/halo/app/security/InitializeRedirectionWebFilterTest.java b/application/src/test/java/run/halo/app/security/InitializeRedirectionWebFilterTest.java index 9d7a1d81bd..2e9d839918 100644 --- a/application/src/test/java/run/halo/app/security/InitializeRedirectionWebFilterTest.java +++ b/application/src/test/java/run/halo/app/security/InitializeRedirectionWebFilterTest.java @@ -2,6 +2,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -14,6 +15,7 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.MediaType; import org.springframework.mock.http.server.reactive.MockServerHttpRequest; import org.springframework.mock.web.server.MockServerWebExchange; import org.springframework.security.web.server.ServerRedirectStrategy; @@ -50,49 +52,57 @@ void shouldRedirectWhenSystemNotInitialized() { when(initializationStateGetter.userInitialized()).thenReturn(Mono.just(false)); WebFilterChain chain = mock(WebFilterChain.class); + var paths = new String[] {"/", "/console/test", "/uc/test", "/login", "/signup"}; + for (String path : paths) { + MockServerHttpRequest request = MockServerHttpRequest.get(path) + .accept(MediaType.TEXT_HTML).build(); + MockServerWebExchange exchange = MockServerWebExchange.from(request); - MockServerHttpRequest request = MockServerHttpRequest.get("/").build(); - MockServerWebExchange exchange = MockServerWebExchange.from(request); + when(serverRedirectStrategy.sendRedirect(any(), any())).thenReturn(Mono.empty().then()); - when(serverRedirectStrategy.sendRedirect(any(), any())).thenReturn(Mono.empty().then()); + Mono result = filter.filter(exchange, chain); - Mono result = filter.filter(exchange, chain); + StepVerifier.create(result) + .expectNextCount(0) + .expectComplete() + .verify(); - StepVerifier.create(result) - .expectNextCount(0) - .expectComplete() - .verify(); - - verify(serverRedirectStrategy).sendRedirect(eq(exchange), eq(URI.create("/console"))); - verify(chain, never()).filter(eq(exchange)); + verify(serverRedirectStrategy).sendRedirect(eq(exchange), + eq(URI.create("/system/setup"))); + verify(chain, never()).filter(eq(exchange)); + } } @Test void shouldNotRedirectWhenSystemInitialized() { - when(initializationStateGetter.userInitialized()).thenReturn(Mono.just(true)); + lenient().when(initializationStateGetter.userInitialized()).thenReturn(Mono.just(true)); WebFilterChain chain = mock(WebFilterChain.class); - MockServerHttpRequest request = MockServerHttpRequest.get("/").build(); - MockServerWebExchange exchange = MockServerWebExchange.from(request); - when(chain.filter(any())).thenReturn(Mono.empty().then()); - Mono result = filter.filter(exchange, chain); - - StepVerifier.create(result) - .expectNextCount(0) - .expectComplete() - .verify(); - - verify(serverRedirectStrategy, never()).sendRedirect(eq(exchange), - eq(URI.create("/console"))); - verify(chain).filter(eq(exchange)); + var paths = new String[] {"/test", "/apis/test", "system/setup", "/logout"}; + for (String path : paths) { + MockServerHttpRequest request = MockServerHttpRequest.get(path) + .accept(MediaType.TEXT_HTML).build(); + MockServerWebExchange exchange = MockServerWebExchange.from(request); + when(chain.filter(any())).thenReturn(Mono.empty().then()); + Mono result = filter.filter(exchange, chain); + + StepVerifier.create(result) + .expectNextCount(0) + .expectComplete() + .verify(); + + verify(serverRedirectStrategy, never()).sendRedirect(eq(exchange), any()); + verify(chain).filter(eq(exchange)); + } } @Test - void shouldNotRedirectWhenNotHomePage() { + void shouldNotRedirectTest() { WebFilterChain chain = mock(WebFilterChain.class); - MockServerHttpRequest request = MockServerHttpRequest.get("/test").build(); + MockServerHttpRequest request = MockServerHttpRequest.get("/test") + .accept(MediaType.TEXT_HTML).build(); MockServerWebExchange exchange = MockServerWebExchange.from(request); when(chain.filter(any())).thenReturn(Mono.empty().then()); Mono result = filter.filter(exchange, chain); @@ -102,8 +112,7 @@ void shouldNotRedirectWhenNotHomePage() { .expectComplete() .verify(); - verify(serverRedirectStrategy, never()).sendRedirect(eq(exchange), - eq(URI.create("/console"))); + verify(serverRedirectStrategy, never()).sendRedirect(eq(exchange), any()); verify(chain).filter(eq(exchange)); } } diff --git a/application/src/test/java/run/halo/app/security/preauth/SystemSetupEndpointTest.java b/application/src/test/java/run/halo/app/security/preauth/SystemSetupEndpointTest.java new file mode 100644 index 0000000000..178d636723 --- /dev/null +++ b/application/src/test/java/run/halo/app/security/preauth/SystemSetupEndpointTest.java @@ -0,0 +1,30 @@ +package run.halo.app.security.preauth; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Properties; +import org.junit.jupiter.api.Test; + +/** + * Tests for {@link SystemSetupEndpoint}. + * + * @author guqing + * @since 2.20.0 + */ +class SystemSetupEndpointTest { + + @Test + void placeholderTest() { + var properties = new Properties(); + properties.setProperty("username", "guqing"); + properties.setProperty("timestamp", "2024-09-30"); + var str = SystemSetupEndpoint.PROPERTY_PLACEHOLDER_HELPER.replacePlaceholders(""" + ${username} + ${timestamp} + """, properties); + assertThat(str).isEqualTo(""" + guqing + 2024-09-30 + """); + } +} \ No newline at end of file From 5df755d4a8c6a5dc3061743525fdf0a68c02629f Mon Sep 17 00:00:00 2001 From: Ryan Wang Date: Wed, 9 Oct 2024 12:20:54 +0800 Subject: [PATCH 2/2] Refine setup page --- api-docs/openapi/v3_0/aggregated.json | 128 ++++------- .../v3_0/apis_console.api_v1alpha1.json | 93 +------- .../v3_0/apis_extension.api_v1alpha1.json | 14 +- .../openapi/v3_0/apis_uc.api_v1alpha1.json | 88 +++++++- e2e/testsuite.yaml | 18 +- ui/console-src/router/guards/check-states.ts | 37 ---- ui/console-src/router/index.ts | 2 - ui/console-src/router/routes.config.ts | 18 -- ui/console-src/views/system/Setup.vue | 135 ------------ .../views/system/SetupInitialData.vue | 202 ------------------ .../views/system/setup-data/category.json | 16 -- .../views/system/setup-data/menu-items.json | 62 ------ .../views/system/setup-data/menu.json | 14 -- .../views/system/setup-data/post.json | 35 --- .../views/system/setup-data/singlePage.json | 31 --- .../views/system/setup-data/tag.json | 13 -- .../api-client/src/.openapi-generator/FILES | 2 +- ui/packages/api-client/src/api.ts | 1 + .../src/api/plugin-v1alpha1-console-api.ts | 66 ------ .../src/api/system-v1alpha1-console-api.ts | 89 -------- .../api/user-connection-v1alpha1-uc-api.ts | 149 +++++++++++++ ui/packages/api-client/src/models/index.ts | 1 - .../api-client/src/models/post-status.ts | 2 +- .../src/models/single-page-status.ts | 2 +- .../models/system-initialization-request.ts | 48 ----- ui/src/locales/en.yaml | 19 -- ui/src/locales/es.yaml | 19 -- ui/src/locales/zh-CN.yaml | 19 -- ui/src/locales/zh-TW.yaml | 19 -- 29 files changed, 290 insertions(+), 1052 deletions(-) delete mode 100644 ui/console-src/router/guards/check-states.ts delete mode 100644 ui/console-src/views/system/Setup.vue delete mode 100644 ui/console-src/views/system/SetupInitialData.vue delete mode 100644 ui/console-src/views/system/setup-data/category.json delete mode 100644 ui/console-src/views/system/setup-data/menu-items.json delete mode 100644 ui/console-src/views/system/setup-data/menu.json delete mode 100644 ui/console-src/views/system/setup-data/post.json delete mode 100644 ui/console-src/views/system/setup-data/singlePage.json delete mode 100644 ui/console-src/views/system/setup-data/tag.json create mode 100644 ui/packages/api-client/src/api/user-connection-v1alpha1-uc-api.ts delete mode 100644 ui/packages/api-client/src/models/system-initialization-request.ts diff --git a/api-docs/openapi/v3_0/aggregated.json b/api-docs/openapi/v3_0/aggregated.json index 79fafe85cc..bd88ac814e 100644 --- a/api-docs/openapi/v3_0/aggregated.json +++ b/api-docs/openapi/v3_0/aggregated.json @@ -2651,30 +2651,6 @@ ] } }, - "/apis/api.console.halo.run/v1alpha1/plugin-presets": { - "get": { - "description": "List all plugin presets in the system.", - "operationId": "ListPluginPresets", - "responses": { - "default": { - "content": { - "*/*": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Plugin" - } - } - } - }, - "description": "default response" - } - }, - "tags": [ - "PluginV1alpha1Console" - ] - } - }, "/apis/api.console.halo.run/v1alpha1/plugins": { "get": { "description": "List plugins using query criteria and sort params", @@ -4309,38 +4285,6 @@ ] } }, - "/apis/api.console.halo.run/v1alpha1/system/initialize": { - "post": { - "description": "Initialize system", - "operationId": "initialize", - "requestBody": { - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/SystemInitializationRequest" - } - } - } - }, - "responses": { - "201": { - "description": "System initialization successfully.", - "headers": { - "Location": { - "description": "Redirect URL.", - "schema": { - "type": "string" - }, - "style": "simple" - } - } - } - }, - "tags": [ - "SystemV1alpha1Console" - ] - } - }, "/apis/api.console.halo.run/v1alpha1/tags": { "get": { "description": "List Post Tags.", @@ -14722,6 +14666,41 @@ ] } }, + "/apis/uc.api.auth.halo.run/v1alpha1/user-connections/{registerId}/disconnect": { + "put": { + "description": "Disconnect my connection from a third-party platform.", + "operationId": "DisconnectMyConnection", + "parameters": [ + { + "description": "The registration ID of the third-party platform.", + "in": "path", + "name": "registerId", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "default": { + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserConnection" + } + } + } + }, + "description": "default response" + } + }, + "tags": [ + "UserConnectionV1alpha1Uc" + ] + } + }, "/apis/uc.api.content.halo.run/v1alpha1/posts": { "get": { "description": "List posts owned by the current user.", @@ -20712,19 +20691,16 @@ }, "visible": { "type": "string", - "default": "PUBLIC", "enum": [ "PUBLIC", "INTERNAL", "PRIVATE" - ] + ], + "default": "PUBLIC" } } }, "PostStatus": { - "required": [ - "phase" - ], "type": "object", "properties": { "commentsCount": { @@ -22560,19 +22536,16 @@ }, "visible": { "type": "string", - "default": "PUBLIC", "enum": [ "PUBLIC", "INTERNAL", "PRIVATE" - ] + ], + "default": "PUBLIC" } } }, "SinglePageStatus": { - "required": [ - "phase" - ], "type": "object", "properties": { "commentsCount": { @@ -22972,29 +22945,6 @@ }, "description": "The subscriber to be notified" }, - "SystemInitializationRequest": { - "required": [ - "password", - "username" - ], - "type": "object", - "properties": { - "email": { - "type": "string" - }, - "password": { - "minLength": 3, - "type": "string" - }, - "siteTitle": { - "type": "string" - }, - "username": { - "minLength": 1, - "type": "string" - } - } - }, "Tag": { "required": [ "apiVersion", diff --git a/api-docs/openapi/v3_0/apis_console.api_v1alpha1.json b/api-docs/openapi/v3_0/apis_console.api_v1alpha1.json index 8f2e91c2cb..df4144e8b8 100644 --- a/api-docs/openapi/v3_0/apis_console.api_v1alpha1.json +++ b/api-docs/openapi/v3_0/apis_console.api_v1alpha1.json @@ -518,30 +518,6 @@ ] } }, - "/apis/api.console.halo.run/v1alpha1/plugin-presets": { - "get": { - "description": "List all plugin presets in the system.", - "operationId": "ListPluginPresets", - "responses": { - "default": { - "content": { - "*/*": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Plugin" - } - } - } - }, - "description": "default response" - } - }, - "tags": [ - "PluginV1alpha1Console" - ] - } - }, "/apis/api.console.halo.run/v1alpha1/plugins": { "get": { "description": "List plugins using query criteria and sort params", @@ -2176,38 +2152,6 @@ ] } }, - "/apis/api.console.halo.run/v1alpha1/system/initialize": { - "post": { - "description": "Initialize system", - "operationId": "initialize", - "requestBody": { - "content": { - "*/*": { - "schema": { - "$ref": "#/components/schemas/SystemInitializationRequest" - } - } - } - }, - "responses": { - "201": { - "description": "System initialization successfully.", - "headers": { - "Location": { - "description": "Redirect URL.", - "schema": { - "type": "string" - }, - "style": "simple" - } - } - } - }, - "tags": [ - "SystemV1alpha1Console" - ] - } - }, "/apis/api.console.halo.run/v1alpha1/tags": { "get": { "description": "List Post Tags.", @@ -5476,19 +5420,16 @@ }, "visible": { "type": "string", - "default": "PUBLIC", "enum": [ "PUBLIC", "INTERNAL", "PRIVATE" - ] + ], + "default": "PUBLIC" } } }, "PostStatus": { - "required": [ - "phase" - ], "type": "object", "properties": { "commentsCount": { @@ -6008,19 +5949,16 @@ }, "visible": { "type": "string", - "default": "PUBLIC", "enum": [ "PUBLIC", "INTERNAL", "PRIVATE" - ] + ], + "default": "PUBLIC" } } }, "SinglePageStatus": { - "required": [ - "phase" - ], "type": "object", "properties": { "commentsCount": { @@ -6090,29 +6028,6 @@ } } }, - "SystemInitializationRequest": { - "required": [ - "password", - "username" - ], - "type": "object", - "properties": { - "email": { - "type": "string" - }, - "password": { - "minLength": 3, - "type": "string" - }, - "siteTitle": { - "type": "string" - }, - "username": { - "minLength": 1, - "type": "string" - } - } - }, "Tag": { "required": [ "apiVersion", diff --git a/api-docs/openapi/v3_0/apis_extension.api_v1alpha1.json b/api-docs/openapi/v3_0/apis_extension.api_v1alpha1.json index c3607a4242..2d9cecbfd2 100644 --- a/api-docs/openapi/v3_0/apis_extension.api_v1alpha1.json +++ b/api-docs/openapi/v3_0/apis_extension.api_v1alpha1.json @@ -11300,19 +11300,16 @@ }, "visible": { "type": "string", - "default": "PUBLIC", "enum": [ "PUBLIC", "INTERNAL", "PRIVATE" - ] + ], + "default": "PUBLIC" } } }, "PostStatus": { - "required": [ - "phase" - ], "type": "object", "properties": { "commentsCount": { @@ -12487,19 +12484,16 @@ }, "visible": { "type": "string", - "default": "PUBLIC", "enum": [ "PUBLIC", "INTERNAL", "PRIVATE" - ] + ], + "default": "PUBLIC" } } }, "SinglePageStatus": { - "required": [ - "phase" - ], "type": "object", "properties": { "commentsCount": { diff --git a/api-docs/openapi/v3_0/apis_uc.api_v1alpha1.json b/api-docs/openapi/v3_0/apis_uc.api_v1alpha1.json index 88e7c7c5fa..84820d4197 100644 --- a/api-docs/openapi/v3_0/apis_uc.api_v1alpha1.json +++ b/api-docs/openapi/v3_0/apis_uc.api_v1alpha1.json @@ -17,6 +17,41 @@ } ], "paths": { + "/apis/uc.api.auth.halo.run/v1alpha1/user-connections/{registerId}/disconnect": { + "put": { + "description": "Disconnect my connection from a third-party platform.", + "operationId": "DisconnectMyConnection", + "parameters": [ + { + "description": "The registration ID of the third-party platform.", + "in": "path", + "name": "registerId", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "default": { + "content": { + "*/*": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserConnection" + } + } + } + }, + "description": "default response" + } + }, + "tags": [ + "UserConnectionV1alpha1Uc" + ] + } + }, "/apis/uc.api.content.halo.run/v1alpha1/posts": { "get": { "description": "List posts owned by the current user.", @@ -1853,19 +1888,16 @@ }, "visible": { "type": "string", - "default": "PUBLIC", "enum": [ "PUBLIC", "INTERNAL", "PRIVATE" - ] + ], + "default": "PUBLIC" } } }, "PostStatus": { - "required": [ - "phase" - ], "type": "object", "properties": { "commentsCount": { @@ -2262,6 +2294,52 @@ } } }, + "UserConnection": { + "required": [ + "apiVersion", + "kind", + "metadata", + "spec" + ], + "type": "object", + "properties": { + "apiVersion": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "metadata": { + "$ref": "#/components/schemas/Metadata" + }, + "spec": { + "$ref": "#/components/schemas/UserConnectionSpec" + } + } + }, + "UserConnectionSpec": { + "required": [ + "providerUserId", + "registrationId", + "username" + ], + "type": "object", + "properties": { + "providerUserId": { + "type": "string" + }, + "registrationId": { + "type": "string" + }, + "updatedAt": { + "type": "string", + "format": "date-time" + }, + "username": { + "type": "string" + } + } + }, "UserDevice": { "required": [ "active", diff --git a/e2e/testsuite.yaml b/e2e/testsuite.yaml index 32f2dc0c10..05c17f9c34 100644 --- a/e2e/testsuite.yaml +++ b/e2e/testsuite.yaml @@ -8,22 +8,18 @@ param: notificationName: "{{randAlpha 6}}" auth: "Basic YWRtaW46MTIzNDU2" items: -- name: init +- name: setup request: - api: /api.console.halo.run/v1alpha1/system/initialize + api: | + {{default "http://halo:8090" (env "SERVER")}}/system/setup method: POST header: - Content-Type: application/json + Content-Type: application/x-www-form-urlencoded + Accept: application/json body: | - { - "siteTitle": "testing", - "username": "admin", - "password": "123456", - "email": "testing@halo.com", - "password_confirm": "123456" - } + siteTitle=testing&username={{.param.userName}}&password=123456&email=testing@halo.run expect: - statusCode: 201 + statusCode: 204 - name: createPost request: api: /api.console.halo.run/v1alpha1/posts diff --git a/ui/console-src/router/guards/check-states.ts b/ui/console-src/router/guards/check-states.ts deleted file mode 100644 index 4bfad5b019..0000000000 --- a/ui/console-src/router/guards/check-states.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { useGlobalInfoStore } from "@/stores/global-info"; -import { useUserStore } from "@/stores/user"; -import type { Router } from "vue-router"; - -export function setupCheckStatesGuard(router: Router) { - router.beforeEach(async (to, _, next) => { - const userStore = useUserStore(); - const { globalInfo } = useGlobalInfoStore(); - const { userInitialized, dataInitialized } = globalInfo || {}; - - if (to.name === "Setup" && userInitialized) { - next({ name: "Dashboard" }); - return; - } - - if (to.name === "SetupInitialData" && dataInitialized) { - next({ name: "Dashboard" }); - return; - } - - if (userInitialized === false && to.name !== "Setup") { - next({ name: "Setup" }); - return; - } - - if ( - dataInitialized === false && - !userStore.isAnonymous && - to.name !== "SetupInitialData" - ) { - next({ name: "SetupInitialData" }); - return; - } - - next(); - }); -} diff --git a/ui/console-src/router/index.ts b/ui/console-src/router/index.ts index d8afd9b5c9..b441c31f1f 100644 --- a/ui/console-src/router/index.ts +++ b/ui/console-src/router/index.ts @@ -6,7 +6,6 @@ import { type RouteLocationNormalizedLoaded, } from "vue-router"; import { setupAuthCheckGuard } from "./guards/auth-check"; -import { setupCheckStatesGuard } from "./guards/check-states"; import { setupPermissionGuard } from "./guards/permission"; const router = createRouter({ @@ -22,7 +21,6 @@ const router = createRouter({ }, }); -setupCheckStatesGuard(router); setupAuthCheckGuard(router); setupPermissionGuard(router); diff --git a/ui/console-src/router/routes.config.ts b/ui/console-src/router/routes.config.ts index d5fadd287b..e2fd53bf69 100644 --- a/ui/console-src/router/routes.config.ts +++ b/ui/console-src/router/routes.config.ts @@ -1,8 +1,6 @@ import Forbidden from "@/views/exceptions/Forbidden.vue"; import NotFound from "@/views/exceptions/NotFound.vue"; import BasicLayout from "@console/layouts/BasicLayout.vue"; -import Setup from "@console/views/system/Setup.vue"; -import SetupInitialData from "@console/views/system/SetupInitialData.vue"; import type { RouteRecordRaw } from "vue-router"; export const routes: Array = [ @@ -22,22 +20,6 @@ export const routes: Array = [ }, ], }, - { - path: "/setup", - component: Setup, - name: "Setup", - meta: { - title: "core.setup.title", - }, - }, - { - path: "/setup-initial-data", - name: "SetupInitialData", - component: SetupInitialData, - meta: { - title: "core.setup.title", - }, - }, ]; export default routes; diff --git a/ui/console-src/views/system/Setup.vue b/ui/console-src/views/system/Setup.vue deleted file mode 100644 index d1da4df5ae..0000000000 --- a/ui/console-src/views/system/Setup.vue +++ /dev/null @@ -1,135 +0,0 @@ - - - diff --git a/ui/console-src/views/system/SetupInitialData.vue b/ui/console-src/views/system/SetupInitialData.vue deleted file mode 100644 index 7af6125e2e..0000000000 --- a/ui/console-src/views/system/SetupInitialData.vue +++ /dev/null @@ -1,202 +0,0 @@ - - - diff --git a/ui/console-src/views/system/setup-data/category.json b/ui/console-src/views/system/setup-data/category.json deleted file mode 100644 index d65a2fab61..0000000000 --- a/ui/console-src/views/system/setup-data/category.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "spec": { - "displayName": "默认分类", - "slug": "default", - "description": "这是你的默认分类,如不需要,删除即可。", - "cover": "", - "template": "", - "priority": 0, - "children": [] - }, - "apiVersion": "content.halo.run/v1alpha1", - "kind": "Category", - "metadata": { - "name": "76514a40-6ef1-4ed9-b58a-e26945bde3ca" - } -} diff --git a/ui/console-src/views/system/setup-data/menu-items.json b/ui/console-src/views/system/setup-data/menu-items.json deleted file mode 100644 index 603f19dbd9..0000000000 --- a/ui/console-src/views/system/setup-data/menu-items.json +++ /dev/null @@ -1,62 +0,0 @@ -[ - { - "spec": { - "displayName": "首页", - "href": "/", - "children": [], - "priority": 0 - }, - "apiVersion": "v1alpha1", - "kind": "MenuItem", - "metadata": { "name": "88c3f10b-321c-4092-86a8-70db00251b74" } - }, - { - "spec": { - "children": [], - "priority": 1, - "targetRef": { - "group": "content.halo.run", - "version": "v1alpha1", - "kind": "Post", - "name": "5152aea5-c2e8-4717-8bba-2263d46e19d5" - } - }, - "apiVersion": "v1alpha1", - "kind": "MenuItem", - "metadata": { "name": "c4c814d1-0c2c-456b-8c96-4864965fee94" } - }, - { - "spec": { - "displayName": "", - "href": "", - "children": [], - "priority": 2, - "targetRef": { - "group": "content.halo.run", - "version": "v1alpha1", - "kind": "Tag", - "name": "c33ceabb-d8f1-4711-8991-bb8f5c92ad7c" - } - }, - "apiVersion": "v1alpha1", - "kind": "MenuItem", - "metadata": { "name": "35869bd3-33b5-448b-91ee-cf6517a59644" } - }, - { - "spec": { - "displayName": "", - "href": "", - "children": [], - "priority": 3, - "targetRef": { - "group": "content.halo.run", - "version": "v1alpha1", - "kind": "SinglePage", - "name": "373a5f79-f44f-441a-9df1-85a4f553ece8" - } - }, - "apiVersion": "v1alpha1", - "kind": "MenuItem", - "metadata": { "name": "b0d041fa-dc99-48f6-a193-8604003379cf" } - } -] diff --git a/ui/console-src/views/system/setup-data/menu.json b/ui/console-src/views/system/setup-data/menu.json deleted file mode 100644 index c1526b53a3..0000000000 --- a/ui/console-src/views/system/setup-data/menu.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "spec": { - "displayName": "主菜单", - "menuItems": [ - "88c3f10b-321c-4092-86a8-70db00251b74", - "c4c814d1-0c2c-456b-8c96-4864965fee94", - "35869bd3-33b5-448b-91ee-cf6517a59644", - "b0d041fa-dc99-48f6-a193-8604003379cf" - ] - }, - "apiVersion": "v1alpha1", - "kind": "Menu", - "metadata": { "name": "primary" } -} diff --git a/ui/console-src/views/system/setup-data/post.json b/ui/console-src/views/system/setup-data/post.json deleted file mode 100644 index d3f275546a..0000000000 --- a/ui/console-src/views/system/setup-data/post.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "post": { - "spec": { - "title": "Hello Halo", - "slug": "hello-halo", - "template": "", - "cover": "", - "deleted": false, - "publish": false, - "publishTime": "", - "pinned": false, - "allowComment": true, - "visible": "PUBLIC", - "version": 1, - "priority": 0, - "excerpt": { - "autoGenerate": false, - "raw": "如果你看到了这一篇文章,那么证明你已经安装成功了,感谢使用 Halo 进行创作,希望能够使用愉快。" - }, - "categories": ["76514a40-6ef1-4ed9-b58a-e26945bde3ca"], - "tags": ["c33ceabb-d8f1-4711-8991-bb8f5c92ad7c"], - "htmlMetas": [] - }, - "apiVersion": "content.halo.run/v1alpha1", - "kind": "Post", - "metadata": { - "name": "5152aea5-c2e8-4717-8bba-2263d46e19d5" - } - }, - "content": { - "raw": "

Hello Halo

如果你看到了这一篇文章,那么证明你已经安装成功了,感谢使用 Halo 进行创作,希望能够使用愉快。

相关链接

在使用过程中,有任何问题都可以通过以上链接找寻答案,或者联系我们。

这是一篇自动生成的文章,请删除这篇文章之后开始你的创作吧!

", - "content": "

Hello Halo

如果你看到了这一篇文章,那么证明你已经安装成功了,感谢使用 Halo 进行创作,希望能够使用愉快。

相关链接

在使用过程中,有任何问题都可以通过以上链接找寻答案,或者联系我们。

这是一篇自动生成的文章,请删除这篇文章之后开始你的创作吧!

", - "rawType": "HTML" - } -} diff --git a/ui/console-src/views/system/setup-data/singlePage.json b/ui/console-src/views/system/setup-data/singlePage.json deleted file mode 100644 index 3c605d8eb5..0000000000 --- a/ui/console-src/views/system/setup-data/singlePage.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "page": { - "spec": { - "title": "关于", - "slug": "about", - "template": "", - "cover": "", - "deleted": false, - "publish": false, - "publishTime": "", - "pinned": false, - "allowComment": true, - "visible": "PUBLIC", - "version": 1, - "priority": 0, - "excerpt": { - "autoGenerate": false, - "raw": "这是一个自定义页面,你可以在后台的 页面 -> 自定义页面 找到它,你可以用于新建关于页面、联系我们页面等等。" - }, - "htmlMetas": [] - }, - "apiVersion": "content.halo.run/v1alpha1", - "kind": "SinglePage", - "metadata": { "name": "373a5f79-f44f-441a-9df1-85a4f553ece8" } - }, - "content": { - "raw": "

关于页面

这是一个自定义页面,你可以在后台的 页面 -> 自定义页面 找到它,你可以用于新建关于页面、联系我们页面等等。

这是一篇自动生成的页面,你可以在后台删除它。

", - "content": "

关于页面

这是一个自定义页面,你可以在后台的 页面 -> 自定义页面 找到它,你可以用于新建关于页面、联系我们页面等等。

这是一篇自动生成的页面,你可以在后台删除它。

", - "rawType": "HTML" - } -} diff --git a/ui/console-src/views/system/setup-data/tag.json b/ui/console-src/views/system/setup-data/tag.json deleted file mode 100644 index c82a115616..0000000000 --- a/ui/console-src/views/system/setup-data/tag.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "spec": { - "displayName": "Halo", - "slug": "halo", - "color": "#ffffff", - "cover": "" - }, - "apiVersion": "content.halo.run/v1alpha1", - "kind": "Tag", - "metadata": { - "name": "c33ceabb-d8f1-4711-8991-bb8f5c92ad7c" - } -} diff --git a/ui/packages/api-client/src/.openapi-generator/FILES b/ui/packages/api-client/src/.openapi-generator/FILES index 4aa1855785..90d1b4d591 100644 --- a/ui/packages/api-client/src/.openapi-generator/FILES +++ b/ui/packages/api-client/src/.openapi-generator/FILES @@ -76,6 +76,7 @@ api/thumbnail-v1alpha1-api.ts api/thumbnail-v1alpha1-public-api.ts api/two-factor-auth-v1alpha1-uc-api.ts api/user-connection-v1alpha1-api.ts +api/user-connection-v1alpha1-uc-api.ts api/user-v1alpha1-api.ts api/user-v1alpha1-console-api.ts base.ts @@ -303,7 +304,6 @@ models/subscription-list.ts models/subscription-spec.ts models/subscription-subscriber.ts models/subscription.ts -models/system-initialization-request.ts models/tag-list.ts models/tag-spec.ts models/tag-status.ts diff --git a/ui/packages/api-client/src/api.ts b/ui/packages/api-client/src/api.ts index 37b68fa218..3a4af9afb5 100644 --- a/ui/packages/api-client/src/api.ts +++ b/ui/packages/api-client/src/api.ts @@ -88,6 +88,7 @@ export * from './api/thumbnail-v1alpha1-api'; export * from './api/thumbnail-v1alpha1-public-api'; export * from './api/two-factor-auth-v1alpha1-uc-api'; export * from './api/user-connection-v1alpha1-api'; +export * from './api/user-connection-v1alpha1-uc-api'; export * from './api/user-v1alpha1-api'; export * from './api/user-v1alpha1-console-api'; diff --git a/ui/packages/api-client/src/api/plugin-v1alpha1-console-api.ts b/ui/packages/api-client/src/api/plugin-v1alpha1-console-api.ts index dcf3317b41..eed35161d6 100644 --- a/ui/packages/api-client/src/api/plugin-v1alpha1-console-api.ts +++ b/ui/packages/api-client/src/api/plugin-v1alpha1-console-api.ts @@ -384,43 +384,6 @@ export const PluginV1alpha1ConsoleApiAxiosParamCreator = function (configuration options: localVarRequestOptions, }; }, - /** - * List all plugin presets in the system. - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - listPluginPresets: async (options: RawAxiosRequestConfig = {}): Promise => { - const localVarPath = `/apis/api.console.halo.run/v1alpha1/plugin-presets`; - // use dummy base URL string because the URL constructor only accepts absolute URLs. - const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); - let baseOptions; - if (configuration) { - baseOptions = configuration.baseOptions; - } - - const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; - const localVarHeaderParameter = {} as any; - const localVarQueryParameter = {} as any; - - // authentication basicAuth required - // http basic authentication required - setBasicAuthToObject(localVarRequestOptions, configuration) - - // authentication bearerAuth required - // http bearer authentication required - await setBearerAuthToObject(localVarHeaderParameter, configuration) - - - - setSearchParams(localVarUrlObj, localVarQueryParameter); - let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; - localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; - - return { - url: toPathString(localVarUrlObj), - options: localVarRequestOptions, - }; - }, /** * List plugins using query criteria and sort params * @param {number} [page] Page number. Default is 0. @@ -884,17 +847,6 @@ export const PluginV1alpha1ConsoleApiFp = function(configuration?: Configuration const localVarOperationServerBasePath = operationServerMap['PluginV1alpha1ConsoleApi.installPluginFromUri']?.[localVarOperationServerIndex]?.url; return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); }, - /** - * List all plugin presets in the system. - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - async listPluginPresets(options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { - const localVarAxiosArgs = await localVarAxiosParamCreator.listPluginPresets(options); - const localVarOperationServerIndex = configuration?.serverIndex ?? 0; - const localVarOperationServerBasePath = operationServerMap['PluginV1alpha1ConsoleApi.listPluginPresets']?.[localVarOperationServerIndex]?.url; - return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); - }, /** * List plugins using query criteria and sort params * @param {number} [page] Page number. Default is 0. @@ -1072,14 +1024,6 @@ export const PluginV1alpha1ConsoleApiFactory = function (configuration?: Configu installPluginFromUri(requestParameters: PluginV1alpha1ConsoleApiInstallPluginFromUriRequest, options?: RawAxiosRequestConfig): AxiosPromise { return localVarFp.installPluginFromUri(requestParameters.installFromUriRequest, options).then((request) => request(axios, basePath)); }, - /** - * List all plugin presets in the system. - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - listPluginPresets(options?: RawAxiosRequestConfig): AxiosPromise> { - return localVarFp.listPluginPresets(options).then((request) => request(axios, basePath)); - }, /** * List plugins using query criteria and sort params * @param {PluginV1alpha1ConsoleApiListPluginsRequest} requestParameters Request parameters. @@ -1527,16 +1471,6 @@ export class PluginV1alpha1ConsoleApi extends BaseAPI { return PluginV1alpha1ConsoleApiFp(this.configuration).installPluginFromUri(requestParameters.installFromUriRequest, options).then((request) => request(this.axios, this.basePath)); } - /** - * List all plugin presets in the system. - * @param {*} [options] Override http request option. - * @throws {RequiredError} - * @memberof PluginV1alpha1ConsoleApi - */ - public listPluginPresets(options?: RawAxiosRequestConfig) { - return PluginV1alpha1ConsoleApiFp(this.configuration).listPluginPresets(options).then((request) => request(this.axios, this.basePath)); - } - /** * List plugins using query criteria and sort params * @param {PluginV1alpha1ConsoleApiListPluginsRequest} requestParameters Request parameters. diff --git a/ui/packages/api-client/src/api/system-v1alpha1-console-api.ts b/ui/packages/api-client/src/api/system-v1alpha1-console-api.ts index 098ce7f1af..b923088d23 100644 --- a/ui/packages/api-client/src/api/system-v1alpha1-console-api.ts +++ b/ui/packages/api-client/src/api/system-v1alpha1-console-api.ts @@ -23,8 +23,6 @@ import { DUMMY_BASE_URL, assertParamExists, setApiKeyToObject, setBasicAuthToObj import { BASE_PATH, COLLECTION_FORMATS, RequestArgs, BaseAPI, RequiredError, operationServerMap } from '../base'; // @ts-ignore import { DashboardStats } from '../models'; -// @ts-ignore -import { SystemInitializationRequest } from '../models'; /** * SystemV1alpha1ConsoleApi - axios parameter creator * @export @@ -63,47 +61,6 @@ export const SystemV1alpha1ConsoleApiAxiosParamCreator = function (configuration let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; - return { - url: toPathString(localVarUrlObj), - options: localVarRequestOptions, - }; - }, - /** - * Initialize system - * @param {SystemInitializationRequest} [systemInitializationRequest] - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - initialize: async (systemInitializationRequest?: SystemInitializationRequest, options: RawAxiosRequestConfig = {}): Promise => { - const localVarPath = `/apis/api.console.halo.run/v1alpha1/system/initialize`; - // use dummy base URL string because the URL constructor only accepts absolute URLs. - const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); - let baseOptions; - if (configuration) { - baseOptions = configuration.baseOptions; - } - - const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; - const localVarHeaderParameter = {} as any; - const localVarQueryParameter = {} as any; - - // authentication basicAuth required - // http basic authentication required - setBasicAuthToObject(localVarRequestOptions, configuration) - - // authentication bearerAuth required - // http bearer authentication required - await setBearerAuthToObject(localVarHeaderParameter, configuration) - - - - localVarHeaderParameter['Content-Type'] = 'application/json'; - - setSearchParams(localVarUrlObj, localVarQueryParameter); - let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; - localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; - localVarRequestOptions.data = serializeDataIfNeeded(systemInitializationRequest, localVarRequestOptions, configuration) - return { url: toPathString(localVarUrlObj), options: localVarRequestOptions, @@ -130,18 +87,6 @@ export const SystemV1alpha1ConsoleApiFp = function(configuration?: Configuration const localVarOperationServerBasePath = operationServerMap['SystemV1alpha1ConsoleApi.getStats']?.[localVarOperationServerIndex]?.url; return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); }, - /** - * Initialize system - * @param {SystemInitializationRequest} [systemInitializationRequest] - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - async initialize(systemInitializationRequest?: SystemInitializationRequest, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.initialize(systemInitializationRequest, options); - const localVarOperationServerIndex = configuration?.serverIndex ?? 0; - const localVarOperationServerBasePath = operationServerMap['SystemV1alpha1ConsoleApi.initialize']?.[localVarOperationServerIndex]?.url; - return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); - }, } }; @@ -160,32 +105,9 @@ export const SystemV1alpha1ConsoleApiFactory = function (configuration?: Configu getStats(options?: RawAxiosRequestConfig): AxiosPromise { return localVarFp.getStats(options).then((request) => request(axios, basePath)); }, - /** - * Initialize system - * @param {SystemV1alpha1ConsoleApiInitializeRequest} requestParameters Request parameters. - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - initialize(requestParameters: SystemV1alpha1ConsoleApiInitializeRequest = {}, options?: RawAxiosRequestConfig): AxiosPromise { - return localVarFp.initialize(requestParameters.systemInitializationRequest, options).then((request) => request(axios, basePath)); - }, }; }; -/** - * Request parameters for initialize operation in SystemV1alpha1ConsoleApi. - * @export - * @interface SystemV1alpha1ConsoleApiInitializeRequest - */ -export interface SystemV1alpha1ConsoleApiInitializeRequest { - /** - * - * @type {SystemInitializationRequest} - * @memberof SystemV1alpha1ConsoleApiInitialize - */ - readonly systemInitializationRequest?: SystemInitializationRequest -} - /** * SystemV1alpha1ConsoleApi - object-oriented interface * @export @@ -202,16 +124,5 @@ export class SystemV1alpha1ConsoleApi extends BaseAPI { public getStats(options?: RawAxiosRequestConfig) { return SystemV1alpha1ConsoleApiFp(this.configuration).getStats(options).then((request) => request(this.axios, this.basePath)); } - - /** - * Initialize system - * @param {SystemV1alpha1ConsoleApiInitializeRequest} requestParameters Request parameters. - * @param {*} [options] Override http request option. - * @throws {RequiredError} - * @memberof SystemV1alpha1ConsoleApi - */ - public initialize(requestParameters: SystemV1alpha1ConsoleApiInitializeRequest = {}, options?: RawAxiosRequestConfig) { - return SystemV1alpha1ConsoleApiFp(this.configuration).initialize(requestParameters.systemInitializationRequest, options).then((request) => request(this.axios, this.basePath)); - } } diff --git a/ui/packages/api-client/src/api/user-connection-v1alpha1-uc-api.ts b/ui/packages/api-client/src/api/user-connection-v1alpha1-uc-api.ts new file mode 100644 index 0000000000..635be3a93f --- /dev/null +++ b/ui/packages/api-client/src/api/user-connection-v1alpha1-uc-api.ts @@ -0,0 +1,149 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Halo + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 2.20.0-SNAPSHOT + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +import type { Configuration } from '../configuration'; +import type { AxiosPromise, AxiosInstance, RawAxiosRequestConfig } from 'axios'; +import globalAxios from 'axios'; +// Some imports not used depending on template conditions +// @ts-ignore +import { DUMMY_BASE_URL, assertParamExists, setApiKeyToObject, setBasicAuthToObject, setBearerAuthToObject, setOAuthToObject, setSearchParams, serializeDataIfNeeded, toPathString, createRequestFunction } from '../common'; +// @ts-ignore +import { BASE_PATH, COLLECTION_FORMATS, RequestArgs, BaseAPI, RequiredError, operationServerMap } from '../base'; +// @ts-ignore +import { UserConnection } from '../models'; +/** + * UserConnectionV1alpha1UcApi - axios parameter creator + * @export + */ +export const UserConnectionV1alpha1UcApiAxiosParamCreator = function (configuration?: Configuration) { + return { + /** + * Disconnect my connection from a third-party platform. + * @param {string} registerId The registration ID of the third-party platform. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + disconnectMyConnection: async (registerId: string, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'registerId' is not null or undefined + assertParamExists('disconnectMyConnection', 'registerId', registerId) + const localVarPath = `/apis/uc.api.auth.halo.run/v1alpha1/user-connections/{registerId}/disconnect` + .replace(`{${"registerId"}}`, encodeURIComponent(String(registerId))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'PUT', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication basicAuth required + // http basic authentication required + setBasicAuthToObject(localVarRequestOptions, configuration) + + // authentication bearerAuth required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + } +}; + +/** + * UserConnectionV1alpha1UcApi - functional programming interface + * @export + */ +export const UserConnectionV1alpha1UcApiFp = function(configuration?: Configuration) { + const localVarAxiosParamCreator = UserConnectionV1alpha1UcApiAxiosParamCreator(configuration) + return { + /** + * Disconnect my connection from a third-party platform. + * @param {string} registerId The registration ID of the third-party platform. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async disconnectMyConnection(registerId: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + const localVarAxiosArgs = await localVarAxiosParamCreator.disconnectMyConnection(registerId, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['UserConnectionV1alpha1UcApi.disconnectMyConnection']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + } +}; + +/** + * UserConnectionV1alpha1UcApi - factory interface + * @export + */ +export const UserConnectionV1alpha1UcApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { + const localVarFp = UserConnectionV1alpha1UcApiFp(configuration) + return { + /** + * Disconnect my connection from a third-party platform. + * @param {UserConnectionV1alpha1UcApiDisconnectMyConnectionRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + disconnectMyConnection(requestParameters: UserConnectionV1alpha1UcApiDisconnectMyConnectionRequest, options?: RawAxiosRequestConfig): AxiosPromise> { + return localVarFp.disconnectMyConnection(requestParameters.registerId, options).then((request) => request(axios, basePath)); + }, + }; +}; + +/** + * Request parameters for disconnectMyConnection operation in UserConnectionV1alpha1UcApi. + * @export + * @interface UserConnectionV1alpha1UcApiDisconnectMyConnectionRequest + */ +export interface UserConnectionV1alpha1UcApiDisconnectMyConnectionRequest { + /** + * The registration ID of the third-party platform. + * @type {string} + * @memberof UserConnectionV1alpha1UcApiDisconnectMyConnection + */ + readonly registerId: string +} + +/** + * UserConnectionV1alpha1UcApi - object-oriented interface + * @export + * @class UserConnectionV1alpha1UcApi + * @extends {BaseAPI} + */ +export class UserConnectionV1alpha1UcApi extends BaseAPI { + /** + * Disconnect my connection from a third-party platform. + * @param {UserConnectionV1alpha1UcApiDisconnectMyConnectionRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof UserConnectionV1alpha1UcApi + */ + public disconnectMyConnection(requestParameters: UserConnectionV1alpha1UcApiDisconnectMyConnectionRequest, options?: RawAxiosRequestConfig) { + return UserConnectionV1alpha1UcApiFp(this.configuration).disconnectMyConnection(requestParameters.registerId, options).then((request) => request(this.axios, this.basePath)); + } +} + diff --git a/ui/packages/api-client/src/models/index.ts b/ui/packages/api-client/src/models/index.ts index b8ae0a1a86..901db6c75e 100644 --- a/ui/packages/api-client/src/models/index.ts +++ b/ui/packages/api-client/src/models/index.ts @@ -217,7 +217,6 @@ export * from './subscription'; export * from './subscription-list'; export * from './subscription-spec'; export * from './subscription-subscriber'; -export * from './system-initialization-request'; export * from './tag'; export * from './tag-list'; export * from './tag-spec'; diff --git a/ui/packages/api-client/src/models/post-status.ts b/ui/packages/api-client/src/models/post-status.ts index 41bec331f0..407f82ff14 100644 --- a/ui/packages/api-client/src/models/post-status.ts +++ b/ui/packages/api-client/src/models/post-status.ts @@ -82,6 +82,6 @@ export interface PostStatus { * @type {string} * @memberof PostStatus */ - 'phase': string; + 'phase'?: string; } diff --git a/ui/packages/api-client/src/models/single-page-status.ts b/ui/packages/api-client/src/models/single-page-status.ts index fff6d27d0e..d74e35b6bb 100644 --- a/ui/packages/api-client/src/models/single-page-status.ts +++ b/ui/packages/api-client/src/models/single-page-status.ts @@ -82,6 +82,6 @@ export interface SinglePageStatus { * @type {string} * @memberof SinglePageStatus */ - 'phase': string; + 'phase'?: string; } diff --git a/ui/packages/api-client/src/models/system-initialization-request.ts b/ui/packages/api-client/src/models/system-initialization-request.ts deleted file mode 100644 index f974ee3a5e..0000000000 --- a/ui/packages/api-client/src/models/system-initialization-request.ts +++ /dev/null @@ -1,48 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -/** - * Halo - * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) - * - * The version of the OpenAPI document: 2.20.0-SNAPSHOT - * - * - * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). - * https://openapi-generator.tech - * Do not edit the class manually. - */ - - - -/** - * - * @export - * @interface SystemInitializationRequest - */ -export interface SystemInitializationRequest { - /** - * - * @type {string} - * @memberof SystemInitializationRequest - */ - 'email'?: string; - /** - * - * @type {string} - * @memberof SystemInitializationRequest - */ - 'password': string; - /** - * - * @type {string} - * @memberof SystemInitializationRequest - */ - 'siteTitle'?: string; - /** - * - * @type {string} - * @memberof SystemInitializationRequest - */ - 'username': string; -} - diff --git a/ui/src/locales/en.yaml b/ui/src/locales/en.yaml index 0b374b33bb..aa46cc72f8 100644 --- a/ui/src/locales/en.yaml +++ b/ui/src/locales/en.yaml @@ -1470,25 +1470,6 @@ core: message: Unauthorized access to this page actions: home: Back to home - setup: - title: Setup - operations: - submit: - button: Setup - toast_success: Setup successfully - setup_initial_data: - loading: Initializing data, please wait... - fields: - site_title: - label: Site title - email: - label: Email - username: - label: Username - password: - label: Password - confirm_password: - label: Confirm password rbac: Attachments Management: Attachments Attachment Manage: Attachment Manage diff --git a/ui/src/locales/es.yaml b/ui/src/locales/es.yaml index b74a7dce1a..b849af5b2b 100644 --- a/ui/src/locales/es.yaml +++ b/ui/src/locales/es.yaml @@ -1104,25 +1104,6 @@ core: message: Acceso no autorizado a esta página actions: home: Ir a la página de inicio - setup: - title: Configuración - operations: - submit: - button: Configurar - toast_success: Configuración exitosa - setup_initial_data: - loading: Inicializando datos, por favor espera... - fields: - site_title: - label: Título del Sitio - email: - label: Correo Electrónico - username: - label: Nombre de Usuario - password: - label: Contraseña - confirm_password: - label: Confirmar Contraseña rbac: Attachments Management: Gestión de archivos adjuntos Attachment Manage: Gestor de adjuntos diff --git a/ui/src/locales/zh-CN.yaml b/ui/src/locales/zh-CN.yaml index 6c2341d6c7..145875729f 100644 --- a/ui/src/locales/zh-CN.yaml +++ b/ui/src/locales/zh-CN.yaml @@ -1369,25 +1369,6 @@ core: message: 没有权限访问此页面 actions: home: 返回首页 - setup: - title: 系统初始化 - operations: - submit: - button: 初始化 - toast_success: 初始化成功 - setup_initial_data: - loading: 正在初始化数据,请稍后... - fields: - site_title: - label: 站点名称 - email: - label: 邮箱 - username: - label: 用户名 - password: - label: 密码 - confirm_password: - label: 确认密码 rbac: Attachments Management: 附件 Attachment Manage: 附件管理 diff --git a/ui/src/locales/zh-TW.yaml b/ui/src/locales/zh-TW.yaml index 652fc6725f..4f3c2d022a 100644 --- a/ui/src/locales/zh-TW.yaml +++ b/ui/src/locales/zh-TW.yaml @@ -1345,25 +1345,6 @@ core: message: 沒有權限訪問此頁面 actions: home: 返回首頁 - setup: - title: 系統初始化 - operations: - submit: - button: 初始化 - toast_success: 初始化成功 - setup_initial_data: - loading: 正在初始化資料,請稍後... - fields: - site_title: - label: 站點名稱 - email: - label: 電子郵箱 - username: - label: 用戶名 - password: - label: 密碼 - confirm_password: - label: 確認密碼 rbac: Attachments Management: 附件 Attachment Manage: 附件管理