From 0c8ccecdf317ecba4a0d0cf23ba99a61b2db4701 Mon Sep 17 00:00:00 2001 From: John Niang Date: Tue, 15 Nov 2022 18:50:18 +0800 Subject: [PATCH] Initialize default theme when Halo starts up for the first time (#2704) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #### What type of PR is this? /kind feature /area core /milestone 2.0 #### What this PR does / why we need it: 1. Initialize default theme when we detect the theme root has no themes here. This process won't stop Halo starting up if error occurs. 2. Refactor ThemeEndpoint with ThemeService to make it reusable. Default theme configuration is as following: ```yaml halo: theme: initializer: disabled: false location: classpath:themes/theme-earth.zip ``` #### Which issue(s) this PR fixes: Fixes https://github.com/halo-dev/halo/issues/2700 #### Special notes for your reviewer: Steps to test: 1. Delete all themes at console if installed 2. Restart Halo and check the log 4. Check the theme root folder `~/halo-next/themes` 5. Try to access index page and you will see the default theme #### Does this PR introduce a user-facing change? ```release-note 在首次启动 Halo 时初始化默认主题 ``` --- .gitignore | 1 + .../core/extension/theme/ThemeEndpoint.java | 183 ++-------------- .../core/extension/theme/ThemeService.java | 15 ++ .../extension/theme/ThemeServiceImpl.java | 198 ++++++++++++++++++ .../app/core/extension/theme/ThemeUtils.java | 40 ++-- .../app/infra/DefaultThemeInitializer.java | 65 ++++++ .../app/infra/DefaultThemeRootGetter.java | 21 ++ .../run/halo/app/infra/ThemeRootGetter.java | 13 ++ .../app/infra/properties/HaloProperties.java | 3 + .../app/infra/properties/ThemeProperties.java | 21 ++ .../run/halo/app/theme/ThemePathPolicy.java | 2 + .../system-configurable-configmap.yaml | 2 +- src/main/resources/themes/theme-earth.zip | Bin 0 -> 113407 bytes .../extension/theme/ThemeEndpointTest.java | 93 ++++---- .../extension/theme/ThemeServiceImplTest.java | 182 ++++++++++++++++ .../security/SuperAdminInitializerTest.java | 3 +- src/test/resources/themes/default/theme.yaml | 15 ++ .../i18n/default.properties | 1 + .../i18n/en.properties | 1 + .../templates/index.html | 12 ++ .../templates/timezone.html | 1 + src/test/resources/themes/other/theme.yaml | 15 ++ 22 files changed, 654 insertions(+), 233 deletions(-) create mode 100644 src/main/java/run/halo/app/core/extension/theme/ThemeService.java create mode 100644 src/main/java/run/halo/app/core/extension/theme/ThemeServiceImpl.java create mode 100644 src/main/java/run/halo/app/infra/DefaultThemeInitializer.java create mode 100644 src/main/java/run/halo/app/infra/DefaultThemeRootGetter.java create mode 100644 src/main/java/run/halo/app/infra/ThemeRootGetter.java create mode 100644 src/main/java/run/halo/app/infra/properties/ThemeProperties.java create mode 100644 src/main/resources/themes/theme-earth.zip create mode 100644 src/test/java/run/halo/app/core/extension/theme/ThemeServiceImplTest.java create mode 100644 src/test/resources/themes/default/theme.yaml create mode 100644 src/test/resources/themes/invalid-missing-manifest/i18n/default.properties create mode 100644 src/test/resources/themes/invalid-missing-manifest/i18n/en.properties create mode 100644 src/test/resources/themes/invalid-missing-manifest/templates/index.html create mode 100644 src/test/resources/themes/invalid-missing-manifest/templates/timezone.html create mode 100644 src/test/resources/themes/other/theme.yaml diff --git a/.gitignore b/.gitignore index eadd96ccc..b7e9268cb 100755 --- a/.gitignore +++ b/.gitignore @@ -72,4 +72,5 @@ application-local.properties ### Zip file for test !src/test/resources/themes/*.zip +!src/main/resources/themes/*.zip src/main/resources/console/ diff --git a/src/main/java/run/halo/app/core/extension/theme/ThemeEndpoint.java b/src/main/java/run/halo/app/core/extension/theme/ThemeEndpoint.java index ce4156489..3ef66df43 100644 --- a/src/main/java/run/halo/app/core/extension/theme/ThemeEndpoint.java +++ b/src/main/java/run/halo/app/core/extension/theme/ThemeEndpoint.java @@ -1,20 +1,13 @@ package run.halo.app.core.extension.theme; -import static java.nio.file.Files.createTempDirectory; import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder; import static org.springdoc.core.fn.builders.content.Builder.contentBuilder; import static org.springdoc.core.fn.builders.parameter.Builder.parameterBuilder; import static org.springdoc.core.fn.builders.requestbody.Builder.requestBodyBuilder; import static org.springdoc.core.fn.builders.schema.Builder.schemaBuilder; -import static org.springframework.util.FileSystemUtils.copyRecursively; import static org.springframework.web.reactive.function.server.RequestPredicates.contentType; -import static reactor.core.scheduler.Schedulers.boundedElastic; import static run.halo.app.core.extension.theme.ThemeUtils.loadThemeManifest; -import static run.halo.app.core.extension.theme.ThemeUtils.locateThemeManifest; -import static run.halo.app.core.extension.theme.ThemeUtils.unzipThemeTo; import static run.halo.app.infra.utils.DataBufferUtils.toInputStream; -import static run.halo.app.infra.utils.FileUtils.deleteRecursivelyAndSilently; -import static run.halo.app.infra.utils.FileUtils.unzip; import io.swagger.v3.oas.annotations.enums.ParameterIn; import io.swagger.v3.oas.annotations.media.Schema; @@ -22,12 +15,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.time.Duration; import java.util.List; -import java.util.Objects; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Predicate; -import java.util.zip.ZipInputStream; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springdoc.webflux.core.fn.SpringdocRouteBuilder; @@ -35,32 +23,24 @@ import org.springframework.http.MediaType; import org.springframework.http.codec.multipart.FilePart; import org.springframework.http.codec.multipart.Part; import org.springframework.lang.NonNull; -import org.springframework.retry.RetryException; import org.springframework.stereotype.Component; -import org.springframework.util.Assert; import org.springframework.util.MultiValueMap; import org.springframework.web.reactive.function.BodyExtractors; 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.ServerErrorException; import org.springframework.web.server.ServerWebInputException; import reactor.core.Exceptions; -import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import reactor.util.retry.Retry; import run.halo.app.core.extension.Setting; import run.halo.app.core.extension.Theme; import run.halo.app.core.extension.endpoint.CustomEndpoint; -import run.halo.app.extension.ConfigMap; import run.halo.app.extension.ListResult; import run.halo.app.extension.ReactiveExtensionClient; import run.halo.app.extension.Unstructured; import run.halo.app.extension.router.IListRequest; import run.halo.app.extension.router.QueryParamBuildUtil; -import run.halo.app.infra.exception.ThemeInstallationException; -import run.halo.app.infra.properties.HaloProperties; -import run.halo.app.theme.ThemePathPolicy; +import run.halo.app.infra.ThemeRootGetter; /** * Endpoint for managing themes. @@ -73,13 +53,16 @@ import run.halo.app.theme.ThemePathPolicy; public class ThemeEndpoint implements CustomEndpoint { private final ReactiveExtensionClient client; - private final HaloProperties haloProperties; - private final ThemePathPolicy themePathPolicy; - public ThemeEndpoint(ReactiveExtensionClient client, HaloProperties haloProperties) { + private final ThemeRootGetter themeRoot; + + private final ThemeService themeService; + + public ThemeEndpoint(ReactiveExtensionClient client, ThemeRootGetter themeRoot, + ThemeService themeService) { this.client = client; - this.haloProperties = haloProperties; - this.themePathPolicy = new ThemePathPolicy(haloProperties.getWorkDir()); + this.themeRoot = themeRoot; + this.themeService = themeService; } @Override @@ -147,6 +130,7 @@ public class ThemeEndpoint implements CustomEndpoint { } } + // TODO Extract the method into ThemeService Mono listThemes(ServerRequest request) { MultiValueMap queryParams = request.queryParams(); ThemeQuery query = new ThemeQuery(queryParams); @@ -188,69 +172,24 @@ public class ThemeEndpoint implements CustomEndpoint { } private Mono upgrade(ServerRequest request) { - var themeNameInPath = request.pathVariable("name"); - final var tempDir = new AtomicReference(); - final var tempThemeRoot = new AtomicReference(); // validate the theme first - return client.fetch(Theme.class, themeNameInPath) - .switchIfEmpty(Mono.error(() -> new ServerWebInputException( - "The given theme with name " + themeNameInPath + " does not exist"))) - .then(request.multipartData()) + var themeNameInPath = request.pathVariable("name"); + return request.multipartData() .map(UpgradeRequest::new) .map(UpgradeRequest::getFile) - .publishOn(boundedElastic()) .flatMap(file -> { - try (var zis = new ZipInputStream(toInputStream(file.content()))) { - tempDir.set(createTempDirectory("halo-theme-")); - unzip(zis, tempDir.get()); - return locateThemeManifest(tempDir.get()); + try (var inputStream = toInputStream(file.content())) { + return themeService.upgrade(themeNameInPath, inputStream); } catch (IOException e) { - return Mono.error(Exceptions.propagate(e)); + return Mono.error(e); } }) - .switchIfEmpty(Mono.error(() -> new ThemeInstallationException( - "Missing theme manifest file: theme.yaml or theme.yml"))) - .doOnNext(themeManifest -> { - if (log.isDebugEnabled()) { - log.debug("Found theme manifest file: {}", themeManifest); - } - tempThemeRoot.set(themeManifest.getParent()); - }) - .map(ThemeUtils::loadThemeManifest) - .doOnNext(newTheme -> { - if (!Objects.equals(themeNameInPath, newTheme.getMetadata().getName())) { - if (log.isDebugEnabled()) { - log.error("Want theme name: {}, but provided: {}", themeNameInPath, - newTheme.getMetadata().getName()); - } - throw new ServerWebInputException("please make sure the theme name is correct"); - } - }) - .flatMap(newTheme -> { - // Remove the theme before upgrading - return deleteThemeAndWaitForComplete(newTheme.getMetadata().getName()) - .thenReturn(newTheme); - }) - .doOnNext(newTheme -> { - // prepare the theme - var themePath = getThemeWorkDir().resolve(newTheme.getMetadata().getName()); - try { - copyRecursively(tempThemeRoot.get(), themePath); - } catch (IOException e) { - throw Exceptions.propagate(e); - } - }) - .flatMap(this::persistent) .flatMap(updatedTheme -> ServerResponse.ok() - .bodyValue(updatedTheme)) - .doFinally(signalType -> { - // clear the temporary folder - deleteRecursivelyAndSilently(tempDir.get()); - }); + .bodyValue(updatedTheme)); } Mono> listUninstalled(ThemeQuery query) { - Path path = themePathPolicy.themesDir(); + Path path = themeRoot.get(); return ThemeUtils.listAllThemesFromThemeDir(path) .collectList() .flatMap(this::filterUnInstalledThemes) @@ -272,6 +211,7 @@ public class ThemeEndpoint implements CustomEndpoint { ); } + // TODO Extract the method into ThemeService Mono reloadSetting(ServerRequest request) { String name = request.pathVariable("name"); return client.fetch(Theme.class, name) @@ -296,7 +236,7 @@ public class ThemeEndpoint implements CustomEndpoint { .orElse(Mono.just(theme)); }) .flatMap(themeToUse -> { - Path themePath = themePathPolicy.generate(themeToUse); + Path themePath = themeRoot.get().resolve(themeToUse.getMetadata().getName()); Path themeManifestPath = ThemeUtils.resolveThemeManifest(themePath); if (themeManifestPath == null) { return Mono.error(new IllegalArgumentException( @@ -322,85 +262,22 @@ public class ThemeEndpoint implements CustomEndpoint { .flatMap(file -> { try { var is = toInputStream(file.content()); - var themeWorkDir = getThemeWorkDir(); - if (log.isDebugEnabled()) { - log.debug("Transferring {} into {}", file.filename(), themeWorkDir); - } - return unzipThemeTo(is, themeWorkDir); + return themeService.install(is); } catch (IOException e) { return Mono.error(Exceptions.propagate(e)); } }) - .flatMap(this::persistent) .flatMap(theme -> ServerResponse.ok() .contentType(MediaType.APPLICATION_JSON) .bodyValue(theme)); } - /** - * Creates theme manifest and related unstructured resources. - * TODO: In case of failure in saving midway, the problem of data consistency needs to be - * solved. - * - * @param themeManifest the theme custom model - * @return a theme custom model - * @see Theme - */ - public Mono persistent(Unstructured themeManifest) { - Assert.state(StringUtils.equals(Theme.KIND, themeManifest.getKind()), - "Theme manifest kind must be Theme."); - return client.create(themeManifest) - .map(theme -> Unstructured.OBJECT_MAPPER.convertValue(theme, Theme.class)) - .flatMap(theme -> { - var unstructureds = ThemeUtils.loadThemeResources(getThemePath(theme)); - if (unstructureds.stream() - .filter(hasSettingsYaml(theme)) - .count() > 1) { - return Mono.error(new IllegalStateException( - "Theme must only have one settings.yaml or settings.yml.")); - } - if (unstructureds.stream() - .filter(hasConfigYaml(theme)) - .count() > 1) { - return Mono.error(new IllegalStateException( - "Theme must only have one config.yaml or config.yml.")); - } - return Flux.fromIterable(unstructureds) - .flatMap(unstructured -> { - var spec = theme.getSpec(); - String name = unstructured.getMetadata().getName(); - - boolean isThemeSetting = unstructured.getKind().equals(Setting.KIND) - && StringUtils.equals(spec.getSettingName(), name); - - boolean isThemeConfig = unstructured.getKind().equals(ConfigMap.KIND) - && StringUtils.equals(spec.getConfigMapName(), name); - if (isThemeSetting || isThemeConfig) { - return client.create(unstructured); - } - return Mono.empty(); - }) - .then(Mono.just(theme)); - }); - } - private Path getThemePath(Theme theme) { return getThemeWorkDir().resolve(theme.getMetadata().getName()); } - private Predicate hasSettingsYaml(Theme theme) { - return unstructured -> Setting.KIND.equals(unstructured.getKind()) - && theme.getSpec().getSettingName().equals(unstructured.getMetadata().getName()); - } - - private Predicate hasConfigYaml(Theme theme) { - return unstructured -> ConfigMap.KIND.equals(unstructured.getKind()) - && theme.getSpec().getConfigMapName().equals(unstructured.getMetadata().getName()); - } - private Path getThemeWorkDir() { - Path themePath = haloProperties.getWorkDir() - .resolve("themes"); + Path themePath = themeRoot.get(); if (Files.notExists(themePath)) { try { Files.createDirectories(themePath); @@ -425,22 +302,4 @@ public class ThemeEndpoint implements CustomEndpoint { return Mono.just(file); } - Mono deleteThemeAndWaitForComplete(String themeName) { - return client.fetch(Theme.class, themeName) - .flatMap(client::delete) - .flatMap(deletingTheme -> waitForThemeDeleted(themeName) - .thenReturn(deletingTheme)); - } - - Mono waitForThemeDeleted(String themeName) { - return client.fetch(Theme.class, themeName) - .doOnNext(theme -> { - throw new RetryException("Re-check if the theme is deleted successfully"); - }) - .retryWhen(Retry.fixedDelay(20, Duration.ofMillis(100)) - .filter(t -> t instanceof RetryException)) - .onErrorMap(Exceptions::isRetryExhausted, - throwable -> new ServerErrorException("Wait timeout for theme deleted", throwable)) - .then(); - } } diff --git a/src/main/java/run/halo/app/core/extension/theme/ThemeService.java b/src/main/java/run/halo/app/core/extension/theme/ThemeService.java new file mode 100644 index 000000000..6aafe5679 --- /dev/null +++ b/src/main/java/run/halo/app/core/extension/theme/ThemeService.java @@ -0,0 +1,15 @@ +package run.halo.app.core.extension.theme; + +import java.io.InputStream; +import reactor.core.publisher.Mono; +import run.halo.app.core.extension.Theme; + +public interface ThemeService { + + Mono install(InputStream is); + + Mono upgrade(String themeName, InputStream is); + + // TODO Migrate other useful methods in ThemeEndpoint in the future. + +} diff --git a/src/main/java/run/halo/app/core/extension/theme/ThemeServiceImpl.java b/src/main/java/run/halo/app/core/extension/theme/ThemeServiceImpl.java new file mode 100644 index 000000000..1e6aecb5b --- /dev/null +++ b/src/main/java/run/halo/app/core/extension/theme/ThemeServiceImpl.java @@ -0,0 +1,198 @@ +package run.halo.app.core.extension.theme; + +import static java.nio.file.Files.createTempDirectory; +import static org.springframework.util.FileSystemUtils.copyRecursively; +import static run.halo.app.core.extension.theme.ThemeUtils.locateThemeManifest; +import static run.halo.app.infra.utils.FileUtils.deleteRecursivelyAndSilently; +import static run.halo.app.infra.utils.FileUtils.unzip; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; +import java.time.Duration; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Predicate; +import java.util.zip.ZipInputStream; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.retry.RetryException; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; +import org.springframework.web.server.ServerErrorException; +import org.springframework.web.server.ServerWebInputException; +import reactor.core.Exceptions; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.core.scheduler.Schedulers; +import reactor.util.retry.Retry; +import run.halo.app.core.extension.Setting; +import run.halo.app.core.extension.Theme; +import run.halo.app.extension.ConfigMap; +import run.halo.app.extension.ReactiveExtensionClient; +import run.halo.app.extension.Unstructured; +import run.halo.app.infra.ThemeRootGetter; +import run.halo.app.infra.exception.ThemeInstallationException; + +@Slf4j +@Service +public class ThemeServiceImpl implements ThemeService { + + private final ReactiveExtensionClient client; + + private final ThemeRootGetter themeRoot; + + public ThemeServiceImpl(ReactiveExtensionClient client, ThemeRootGetter themeRoot) { + this.client = client; + this.themeRoot = themeRoot; + } + + @Override + public Mono install(InputStream is) { + var themeRoot = this.themeRoot.get(); + return ThemeUtils.unzipThemeTo(is, themeRoot) + .flatMap(this::persistent); + } + + @Override + public Mono upgrade(String themeName, InputStream is) { + var tempDir = new AtomicReference(); + var tempThemeRoot = new AtomicReference(); + return client.fetch(Theme.class, themeName) + .switchIfEmpty(Mono.error(() -> new ServerWebInputException( + "The given theme with name " + themeName + " did not exist"))) + .publishOn(Schedulers.boundedElastic()) + .doFirst(() -> { + try { + tempDir.set(createTempDirectory("halo-theme-")); + } catch (IOException e) { + throw Exceptions.propagate(e); + } + }) + .flatMap(oldTheme -> { + try (var zis = new ZipInputStream(is)) { + unzip(zis, tempDir.get()); + return locateThemeManifest(tempDir.get()) + .switchIfEmpty(Mono.error(() -> new ThemeInstallationException( + "Missing theme manifest file: theme.yaml or theme.yml"))); + } catch (IOException e) { + return Mono.error(e); + } + }) + .doOnNext(themeManifest -> { + if (log.isDebugEnabled()) { + log.debug("Found theme manifest file: {}", themeManifest); + } + tempThemeRoot.set(themeManifest.getParent()); + }) + .map(ThemeUtils::loadThemeManifest) + .doOnNext(newTheme -> { + if (!Objects.equals(themeName, newTheme.getMetadata().getName())) { + if (log.isDebugEnabled()) { + log.error("Want theme name: {}, but provided: {}", themeName, + newTheme.getMetadata().getName()); + } + throw new ServerWebInputException("please make sure the theme name is correct"); + } + }) + .flatMap(newTheme -> { + // Remove the theme before upgrading + return deleteThemeAndWaitForComplete(newTheme.getMetadata().getName()) + .thenReturn(newTheme); + }) + .doOnNext(newTheme -> { + // prepare the theme + var themePath = themeRoot.get().resolve(newTheme.getMetadata().getName()); + try { + copyRecursively(tempThemeRoot.get(), themePath); + } catch (IOException e) { + throw Exceptions.propagate(e); + } + }) + .flatMap(this::persistent) + .doFinally(signalType -> { + // clear the temporary folder + deleteRecursivelyAndSilently(tempDir.get()); + }); + } + + /** + * Creates theme manifest and related unstructured resources. + * TODO: In case of failure in saving midway, the problem of data consistency needs to be + * solved. + * + * @param themeManifest the theme custom model + * @return a theme custom model + * @see Theme + */ + public Mono persistent(Unstructured themeManifest) { + Assert.state(StringUtils.equals(Theme.KIND, themeManifest.getKind()), + "Theme manifest kind must be Theme."); + return client.create(themeManifest) + .map(theme -> Unstructured.OBJECT_MAPPER.convertValue(theme, Theme.class)) + .flatMap(theme -> { + var unstructureds = ThemeUtils.loadThemeResources(getThemePath(theme)); + if (unstructureds.stream() + .filter(hasSettingsYaml(theme)) + .count() > 1) { + return Mono.error(new IllegalStateException( + "Theme must only have one settings.yaml or settings.yml.")); + } + if (unstructureds.stream() + .filter(hasConfigYaml(theme)) + .count() > 1) { + return Mono.error(new IllegalStateException( + "Theme must only have one config.yaml or config.yml.")); + } + return Flux.fromIterable(unstructureds) + .flatMap(unstructured -> { + var spec = theme.getSpec(); + String name = unstructured.getMetadata().getName(); + + boolean isThemeSetting = unstructured.getKind().equals(Setting.KIND) + && StringUtils.equals(spec.getSettingName(), name); + + boolean isThemeConfig = unstructured.getKind().equals(ConfigMap.KIND) + && StringUtils.equals(spec.getConfigMapName(), name); + if (isThemeSetting || isThemeConfig) { + return client.create(unstructured); + } + return Mono.empty(); + }) + .then(Mono.just(theme)); + }); + } + + private Path getThemePath(Theme theme) { + return themeRoot.get().resolve(theme.getMetadata().getName()); + } + + private Predicate hasSettingsYaml(Theme theme) { + return unstructured -> Setting.KIND.equals(unstructured.getKind()) + && theme.getSpec().getSettingName().equals(unstructured.getMetadata().getName()); + } + + private Predicate hasConfigYaml(Theme theme) { + return unstructured -> ConfigMap.KIND.equals(unstructured.getKind()) + && theme.getSpec().getConfigMapName().equals(unstructured.getMetadata().getName()); + } + + Mono deleteThemeAndWaitForComplete(String themeName) { + return client.fetch(Theme.class, themeName) + .flatMap(client::delete) + .flatMap(deletingTheme -> waitForThemeDeleted(themeName) + .thenReturn(deletingTheme)); + } + + Mono waitForThemeDeleted(String themeName) { + return client.fetch(Theme.class, themeName) + .doOnNext(theme -> { + throw new RetryException("Re-check if the theme is deleted successfully"); + }) + .retryWhen(Retry.fixedDelay(20, Duration.ofMillis(100)) + .filter(t -> t instanceof RetryException)) + .onErrorMap(Exceptions::isRetryExhausted, + throwable -> new ServerErrorException("Wait timeout for theme deleted", throwable)) + .then(); + } +} diff --git a/src/main/java/run/halo/app/core/extension/theme/ThemeUtils.java b/src/main/java/run/halo/app/core/extension/theme/ThemeUtils.java index a28e7d78d..1c18aff05 100644 --- a/src/main/java/run/halo/app/core/extension/theme/ThemeUtils.java +++ b/src/main/java/run/halo/app/core/extension/theme/ThemeUtils.java @@ -91,22 +91,27 @@ class ThemeUtils { static Mono unzipThemeTo(InputStream inputStream, Path themeWorkDir, boolean override) { - AtomicReference tempDir = new AtomicReference<>(); - return Mono.fromCallable( - () -> { - Path tempDirectory = null; - Path themeTargetPath = null; - try (ZipInputStream zipInputStream = new ZipInputStream(inputStream)) { - tempDirectory = createTempDirectory(THEME_TMP_PREFIX); - unzip(zipInputStream, tempDirectory); - return tempDirectory; - } catch (IOException e) { - deleteRecursivelyAndSilently(themeTargetPath); - throw new ThemeInstallationException("Unable to install theme", e); - } - }) - .doOnNext(tempDir::set) - .flatMap(ThemeUtils::locateThemeManifest) + var tempDir = new AtomicReference(); + return Mono.just(inputStream) + .publishOn(Schedulers.boundedElastic()) + .doFirst(() -> { + try { + tempDir.set(createTempDirectory(THEME_TMP_PREFIX)); + } catch (IOException e) { + throw Exceptions.propagate(e); + } + }) + .doOnNext(is -> { + try (var zipIs = new ZipInputStream(is)) { + // unzip input stream into temporary directory + unzip(zipIs, tempDir.get()); + } catch (IOException e) { + throw Exceptions.propagate(e); + } + }) + .flatMap(is -> ThemeUtils.locateThemeManifest(tempDir.get())) + .switchIfEmpty( + Mono.error(() -> new ThemeInstallationException("Missing theme manifest"))) .map(themeManifestPath -> { var theme = loadThemeManifest(themeManifestPath); var themeName = theme.getMetadata().getName(); @@ -123,8 +128,7 @@ class ThemeUtils { throw Exceptions.propagate(e); } }) - .doFinally(signalType -> deleteRecursivelyAndSilently(tempDir.get())) - .subscribeOn(Schedulers.boundedElastic()); + .doFinally(signalType -> deleteRecursivelyAndSilently(tempDir.get())); } static Unstructured loadThemeManifest(Path themeManifestPath) { diff --git a/src/main/java/run/halo/app/infra/DefaultThemeInitializer.java b/src/main/java/run/halo/app/infra/DefaultThemeInitializer.java new file mode 100644 index 000000000..72963f080 --- /dev/null +++ b/src/main/java/run/halo/app/infra/DefaultThemeInitializer.java @@ -0,0 +1,65 @@ +package run.halo.app.infra; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.CountDownLatch; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Component; +import org.springframework.util.ResourceUtils; +import run.halo.app.core.extension.theme.ThemeService; +import run.halo.app.infra.properties.HaloProperties; +import run.halo.app.infra.properties.ThemeProperties; +import run.halo.app.infra.utils.FileUtils; + +@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(SchemeInitializedEvent 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 defaultThemeUri = ResourceUtils.getURL(location).toURI(); + var latch = new CountDownLatch(1); + themeService.install(Files.newInputStream(Path.of(defaultThemeUri))) + .doFinally(signalType -> latch.countDown()) + .subscribe(theme -> log.info("Initialized default theme: {}", + theme.getMetadata().getName())); + latch.await(); + // Because default active theme is default, we don't need to enabled it manually. + } catch (IOException | URISyntaxException | InterruptedException e) { + // we should skip the initialization error at here + log.warn("Failed to initialize theme from " + location, e); + } + } + + +} diff --git a/src/main/java/run/halo/app/infra/DefaultThemeRootGetter.java b/src/main/java/run/halo/app/infra/DefaultThemeRootGetter.java new file mode 100644 index 000000000..2df9380cc --- /dev/null +++ b/src/main/java/run/halo/app/infra/DefaultThemeRootGetter.java @@ -0,0 +1,21 @@ +package run.halo.app.infra; + +import java.nio.file.Path; +import org.springframework.stereotype.Component; +import run.halo.app.infra.properties.HaloProperties; + +@Component +public class DefaultThemeRootGetter implements ThemeRootGetter { + + private final HaloProperties haloProps; + + public DefaultThemeRootGetter(HaloProperties haloProps) { + this.haloProps = haloProps; + } + + @Override + public Path get() { + return haloProps.getWorkDir().resolve("themes"); + } + +} diff --git a/src/main/java/run/halo/app/infra/ThemeRootGetter.java b/src/main/java/run/halo/app/infra/ThemeRootGetter.java new file mode 100644 index 000000000..2f2a72a53 --- /dev/null +++ b/src/main/java/run/halo/app/infra/ThemeRootGetter.java @@ -0,0 +1,13 @@ +package run.halo.app.infra; + +import java.nio.file.Path; +import java.util.function.Supplier; + +/** + * ThemeRootGetter allows us to get root path of themes. + * + * @author johnniang + */ +public interface ThemeRootGetter extends Supplier { + +} diff --git a/src/main/java/run/halo/app/infra/properties/HaloProperties.java b/src/main/java/run/halo/app/infra/properties/HaloProperties.java index 66d2124bb..9816cfc81 100644 --- a/src/main/java/run/halo/app/infra/properties/HaloProperties.java +++ b/src/main/java/run/halo/app/infra/properties/HaloProperties.java @@ -42,4 +42,7 @@ public class HaloProperties { @Valid private final ConsoleProperties console = new ConsoleProperties(); + + @Valid + private final ThemeProperties theme = new ThemeProperties(); } diff --git a/src/main/java/run/halo/app/infra/properties/ThemeProperties.java b/src/main/java/run/halo/app/infra/properties/ThemeProperties.java new file mode 100644 index 000000000..b4decf3f3 --- /dev/null +++ b/src/main/java/run/halo/app/infra/properties/ThemeProperties.java @@ -0,0 +1,21 @@ +package run.halo.app.infra.properties; + +import jakarta.validation.Valid; +import lombok.Data; + +@Data +public class ThemeProperties { + + @Valid + private final Initializer initializer = new Initializer(); + + @Data + public static class Initializer { + + private boolean disabled = false; + + private String location = "classpath:themes/theme-earth.zip"; + + } + +} diff --git a/src/main/java/run/halo/app/theme/ThemePathPolicy.java b/src/main/java/run/halo/app/theme/ThemePathPolicy.java index a7d15fd83..c696ffdaa 100644 --- a/src/main/java/run/halo/app/theme/ThemePathPolicy.java +++ b/src/main/java/run/halo/app/theme/ThemePathPolicy.java @@ -9,7 +9,9 @@ import run.halo.app.core.extension.Theme; * * @author guqing * @since 2.0.0 + * @deprecated Use {@code run.halo.app.infra.ThemeRootGetter} */ +@Deprecated(forRemoval = true) public class ThemePathPolicy { public static final String THEME_WORK_DIR = "themes"; diff --git a/src/main/resources/extensions/system-configurable-configmap.yaml b/src/main/resources/extensions/system-configurable-configmap.yaml index bb2436f31..0694ccc3d 100644 --- a/src/main/resources/extensions/system-configurable-configmap.yaml +++ b/src/main/resources/extensions/system-configurable-configmap.yaml @@ -5,7 +5,7 @@ metadata: data: theme: | { - "active": "default" + "active": "theme-earth" } routeRules: | { diff --git a/src/main/resources/themes/theme-earth.zip b/src/main/resources/themes/theme-earth.zip new file mode 100644 index 0000000000000000000000000000000000000000..e1149fcc83e4ec154290dfc0852643679bd78bde GIT binary patch literal 113407 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
    Gc*S7K}9&FF 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 z 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 literal 0 HcmV?d00001 diff --git a/src/test/java/run/halo/app/core/extension/theme/ThemeEndpointTest.java b/src/test/java/run/halo/app/core/extension/theme/ThemeEndpointTest.java index deb65a1e5..8ec87d846 100644 --- a/src/test/java/run/halo/app/core/extension/theme/ThemeEndpointTest.java +++ b/src/test/java/run/halo/app/core/extension/theme/ThemeEndpointTest.java @@ -2,15 +2,17 @@ package run.halo.app.core.extension.theme; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.springframework.web.reactive.function.BodyInserters.fromMultipartData; -import static run.halo.app.extension.Unstructured.OBJECT_MAPPER; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; @@ -21,6 +23,7 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.skyscreamer.jsonassert.JSONAssert; @@ -30,16 +33,15 @@ import org.springframework.http.client.MultipartBodyBuilder; import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.util.FileSystemUtils; import org.springframework.util.ResourceUtils; +import org.springframework.web.server.ServerWebInputException; import reactor.core.publisher.Mono; import run.halo.app.core.extension.Setting; import run.halo.app.core.extension.Theme; import run.halo.app.extension.AbstractExtension; import run.halo.app.extension.Metadata; import run.halo.app.extension.ReactiveExtensionClient; -import run.halo.app.extension.Unstructured; -import run.halo.app.infra.properties.HaloProperties; +import run.halo.app.infra.ThemeRootGetter; import run.halo.app.infra.utils.JsonUtils; -import run.halo.app.infra.utils.YamlUnstructuredLoader; /** * Tests for {@link ThemeEndpoint}. @@ -51,10 +53,16 @@ import run.halo.app.infra.utils.YamlUnstructuredLoader; class ThemeEndpointTest { @Mock - private ReactiveExtensionClient extensionClient; + ReactiveExtensionClient extensionClient; @Mock - private HaloProperties haloProperties; + ThemeRootGetter themeRoot; + + @Mock + ThemeService themeService; + + @InjectMocks + ThemeEndpoint themeEndpoint; private Path tmpHaloWorkDir; @@ -64,21 +72,17 @@ class ThemeEndpointTest { @BeforeEach void setUp() throws IOException { - tmpHaloWorkDir = Files.createTempDirectory("halo-unit-test"); - when(haloProperties.getWorkDir()).thenReturn(tmpHaloWorkDir); - - ThemeEndpoint themeEndpoint = new ThemeEndpoint(extensionClient, haloProperties); - + tmpHaloWorkDir = Files.createTempDirectory("halo-theme-endpoint-test"); + lenient().when(themeRoot.get()).thenReturn(tmpHaloWorkDir); defaultTheme = ResourceUtils.getFile("classpath:themes/test-theme.zip"); - webTestClient = WebTestClient .bindToRouterFunction(themeEndpoint.endpoint()) .build(); } @AfterEach - void tearDown() { - FileSystemUtils.deleteRecursively(tmpHaloWorkDir.toFile()); + void tearDown() throws IOException { + FileSystemUtils.deleteRecursively(tmpHaloWorkDir); } @Nested @@ -90,13 +94,17 @@ class ThemeEndpointTest { bodyBuilder.part("file", new FileSystemResource(defaultTheme)) .contentType(MediaType.MULTIPART_FORM_DATA); - when(extensionClient.fetch(Theme.class, "invalid-theme")).thenReturn(Mono.empty()); + when(themeService.upgrade(eq("invalid-missing-manifest"), isA(InputStream.class))) + .thenReturn( + Mono.error(() -> new ServerWebInputException("Failed to upgrade theme"))); webTestClient.post() - .uri("/themes/invalid-theme/upgrade") + .uri("/themes/invalid-missing-manifest/upgrade") .body(fromMultipartData(bodyBuilder.build())) .exchange() .expectStatus().isBadRequest(); + + verify(themeService).upgrade(eq("invalid-missing-manifest"), isA(InputStream.class)); } @Test @@ -105,24 +113,13 @@ class ThemeEndpointTest { bodyBuilder.part("file", new FileSystemResource(defaultTheme)) .contentType(MediaType.MULTIPART_FORM_DATA); - var oldTheme = mock(Theme.class); - when(extensionClient.fetch(Theme.class, "default")) - // for old theme check - .thenReturn(Mono.just(oldTheme)) - // for theme deletion - .thenReturn(Mono.just(oldTheme)) - // for theme deleted check - .thenReturn(Mono.empty()); - - when(extensionClient.delete(oldTheme)).thenReturn(Mono.just(oldTheme)); - var metadata = new Metadata(); metadata.setName("default"); var newTheme = new Theme(); newTheme.setMetadata(metadata); - when(extensionClient.create(any(Unstructured.class))).thenReturn( - Mono.just(OBJECT_MAPPER.convertValue(newTheme, Unstructured.class))); + when(themeService.upgrade(eq("default"), isA(InputStream.class))) + .thenReturn(Mono.just(newTheme)); webTestClient.post() .uri("/themes/default/upgrade") @@ -130,41 +127,36 @@ class ThemeEndpointTest { .exchange() .expectStatus().isOk(); - verify(extensionClient, times(3)).fetch(Theme.class, "default"); - verify(extensionClient).delete(oldTheme); - verify(extensionClient).create(any(Unstructured.class)); + verify(themeService).upgrade(eq("default"), isA(InputStream.class)); } } @Test void install() { - when(extensionClient.create(any(Unstructured.class))).thenReturn( - Mono.fromCallable(() -> { - var defaultThemeManifestPath = tmpHaloWorkDir.resolve("themes/default/theme.yaml"); - assertThat(Files.exists(defaultThemeManifestPath)).isTrue(); - return new YamlUnstructuredLoader(new FileSystemResource(defaultThemeManifestPath)) - .load() - .get(0); - })); - - MultipartBodyBuilder multipartBodyBuilder = new MultipartBodyBuilder(); + var multipartBodyBuilder = new MultipartBodyBuilder(); multipartBodyBuilder.part("file", new FileSystemResource(defaultTheme)) .contentType(MediaType.MULTIPART_FORM_DATA); + var installedTheme = new Theme(); + var metadata = new Metadata(); + metadata.setName("fake-name"); + installedTheme.setMetadata(metadata); + when(themeService.install(any())).thenReturn(Mono.just(installedTheme)); + webTestClient.post() .uri("/themes/install") .body(fromMultipartData(multipartBodyBuilder.build())) .exchange() .expectStatus().isOk() .expectBody(Theme.class) - .value(theme -> { - verify(extensionClient, times(1)).create(any(Unstructured.class)); + .isEqualTo(installedTheme); - assertThat(theme).isNotNull(); - assertThat(theme.getMetadata().getName()).isEqualTo("default"); - }); + verify(themeService).install(any()); + + when(themeService.install(any())).thenReturn( + Mono.error(new RuntimeException("Fake exception"))); // Verify the theme is installed. webTestClient.post() .uri("/themes/install") @@ -190,9 +182,8 @@ class ThemeEndpointTest { when(extensionClient.fetch(Setting.class, "fake-setting")) .thenReturn(Mono.just(setting)); - when(haloProperties.getWorkDir()).thenReturn(tmpHaloWorkDir); - Path themeWorkDir = tmpHaloWorkDir.resolve("themes") - .resolve(theme.getMetadata().getName()); + // when(haloProperties.getWorkDir()).thenReturn(tmpHaloWorkDir); + Path themeWorkDir = themeRoot.get().resolve(theme.getMetadata().getName()); if (!Files.exists(themeWorkDir)) { Files.createDirectories(themeWorkDir); } diff --git a/src/test/java/run/halo/app/core/extension/theme/ThemeServiceImplTest.java b/src/test/java/run/halo/app/core/extension/theme/ThemeServiceImplTest.java new file mode 100644 index 000000000..321ed468a --- /dev/null +++ b/src/test/java/run/halo/app/core/extension/theme/ThemeServiceImplTest.java @@ -0,0 +1,182 @@ +package run.halo.app.core.extension.theme; + +import static java.nio.file.Files.createTempDirectory; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static run.halo.app.infra.utils.FileUtils.deleteRecursivelyAndSilently; +import static run.halo.app.infra.utils.FileUtils.zip; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.function.Consumer; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +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.util.ResourceUtils; +import org.springframework.web.server.ServerWebInputException; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; +import run.halo.app.core.extension.Theme; +import run.halo.app.extension.Metadata; +import run.halo.app.extension.ReactiveExtensionClient; +import run.halo.app.extension.Unstructured; +import run.halo.app.extension.exception.ExtensionException; +import run.halo.app.infra.ThemeRootGetter; +import run.halo.app.infra.exception.ThemeInstallationException; + +@ExtendWith(MockitoExtension.class) +class ThemeServiceImplTest { + + @Mock + ReactiveExtensionClient client; + + @Mock + ThemeRootGetter themeRoot; + + @InjectMocks + ThemeServiceImpl themeService; + + Path tmpDir; + + @BeforeEach + void setUp() throws IOException { + tmpDir = createTempDirectory("halo-theme-service-test-"); + lenient().when(themeRoot.get()).thenReturn(tmpDir.resolve("themes")); + // init the folder + Files.createDirectory(themeRoot.get()); + } + + @AfterEach + void cleanUp() { + deleteRecursivelyAndSilently(tmpDir); + } + + Path prepareTheme(String themeFilename) throws IOException, URISyntaxException { + var defaultThemeUri = ResourceUtils.getURL("classpath:themes/" + themeFilename).toURI(); + var defaultThemeZipPath = tmpDir.resolve("default.zip"); + zip(Path.of(defaultThemeUri), defaultThemeZipPath); + return defaultThemeZipPath; + } + + Theme createTheme() { + return createTheme(theme -> { + }); + } + + Theme createTheme(Consumer customizer) { + var metadata = new Metadata(); + metadata.setName("default"); + + var spec = new Theme.ThemeSpec(); + spec.setDisplayName("Default"); + + var theme = new Theme(); + theme.setMetadata(metadata); + theme.setSpec(spec); + customizer.accept(theme); + return theme; + } + + Unstructured convert(Theme theme) { + return Unstructured.OBJECT_MAPPER.convertValue(theme, Unstructured.class); + } + + @Nested + class UpgradeTest { + + @Test + void shouldFailIfThemeNotInstalledBefore() throws IOException, URISyntaxException { + var themeZipPath = prepareTheme("other"); + when(client.fetch(Theme.class, "default")).thenReturn(Mono.empty()); + try (var is = Files.newInputStream(themeZipPath)) { + StepVerifier.create(themeService.upgrade("default", is)) + .verifyError(ServerWebInputException.class); + } + + verify(client).fetch(Theme.class, "default"); + } + + @Test + void shouldUpgradeSuccessfully() throws IOException, URISyntaxException { + var themeZipPath = prepareTheme("other"); + + var oldTheme = createTheme(); + when(client.fetch(Theme.class, "default")) + // for old theme check + .thenReturn(Mono.just(oldTheme)) + // for theme deletion + .thenReturn(Mono.just(oldTheme)) + // for theme deleted check + .thenReturn(Mono.empty()); + + when(client.delete(oldTheme)).thenReturn(Mono.just(oldTheme)); + when(client.create(isA(Unstructured.class))).thenReturn( + Mono.just(convert(createTheme(t -> t.getSpec().setDisplayName("New fake theme"))))); + + try (var is = Files.newInputStream(themeZipPath)) { + StepVerifier.create(themeService.upgrade("default", is)) + .consumeNextWith(newTheme -> { + assertEquals("default", newTheme.getMetadata().getName()); + assertEquals("New fake theme", newTheme.getSpec().getDisplayName()); + }) + .verifyComplete(); + } + + verify(client, times(3)).fetch(Theme.class, "default"); + verify(client).delete(oldTheme); + verify(client).create(isA(Unstructured.class)); + } + } + + @Nested + class InstallTest { + + + @Test + void shouldInstallSuccessfully() throws IOException, URISyntaxException { + var defaultThemeZipPath = prepareTheme("default"); + when(client.create(isA(Unstructured.class))).thenReturn( + Mono.just(convert(createTheme()))); + try (var is = Files.newInputStream(defaultThemeZipPath)) { + StepVerifier.create(themeService.install(is)) + .consumeNextWith(theme -> { + assertEquals("default", theme.getMetadata().getName()); + assertEquals("Default", theme.getSpec().getDisplayName()); + }) + .verifyComplete(); + } + } + + @Test + void shouldFailWhenPersistentError() throws IOException, URISyntaxException { + var defaultThemeZipPath = prepareTheme("default"); + when(client.create(isA(Unstructured.class))).thenReturn( + Mono.error(() -> new ExtensionException("Failed to create the extension"))); + try (var is = Files.newInputStream(defaultThemeZipPath)) { + StepVerifier.create(themeService.install(is)) + .verifyError(ExtensionException.class); + } + } + + @Test + void shouldFailWhenThemeManifestIsInvalid() throws IOException, URISyntaxException { + var defaultThemeZipPath = prepareTheme("invalid-missing-manifest"); + try (var is = Files.newInputStream(defaultThemeZipPath)) { + StepVerifier.create(themeService.install(is)) + .verifyError(ThemeInstallationException.class); + } + } + } + +} \ No newline at end of file diff --git a/src/test/java/run/halo/app/security/SuperAdminInitializerTest.java b/src/test/java/run/halo/app/security/SuperAdminInitializerTest.java index 9d12ced3b..f0b830d62 100644 --- a/src/test/java/run/halo/app/security/SuperAdminInitializerTest.java +++ b/src/test/java/run/halo/app/security/SuperAdminInitializerTest.java @@ -20,7 +20,8 @@ import run.halo.app.extension.ReactiveExtensionClient; @SpringBootTest(properties = {"halo.security.initializer.disabled=false", "halo.security.initializer.super-admin-username=fake-admin", "halo.security.initializer.super-admin-password=fake-password", - "halo.required-extension-disabled=true"}) + "halo.required-extension-disabled=true", + "halo.theme.initializer.disabled=true"}) @AutoConfigureWebTestClient @AutoConfigureTestDatabase class SuperAdminInitializerTest { diff --git a/src/test/resources/themes/default/theme.yaml b/src/test/resources/themes/default/theme.yaml new file mode 100644 index 000000000..00263a89f --- /dev/null +++ b/src/test/resources/themes/default/theme.yaml @@ -0,0 +1,15 @@ +apiVersion: theme.halo.run/v1alpha1 +kind: Theme +metadata: + name: default +spec: + displayName: Default + author: + name: halo-dev + website: https://halo.run + description: Default theme for Halo 2.0 + logo: https://halo.run/logo + website: https://github.com/halo-sigs/theme-default + repo: https://github.com/halo-sigs/theme-default.git + version: 1.0.0 + require: 2.0.0 diff --git a/src/test/resources/themes/invalid-missing-manifest/i18n/default.properties b/src/test/resources/themes/invalid-missing-manifest/i18n/default.properties new file mode 100644 index 000000000..0321c8140 --- /dev/null +++ b/src/test/resources/themes/invalid-missing-manifest/i18n/default.properties @@ -0,0 +1 @@ +index.welcome=\u6B22\u8FCE\u6765\u5230\u9996\u9875 \ No newline at end of file diff --git a/src/test/resources/themes/invalid-missing-manifest/i18n/en.properties b/src/test/resources/themes/invalid-missing-manifest/i18n/en.properties new file mode 100644 index 000000000..1e6ec93cd --- /dev/null +++ b/src/test/resources/themes/invalid-missing-manifest/i18n/en.properties @@ -0,0 +1 @@ +index.welcome=Welcome to the index \ No newline at end of file diff --git a/src/test/resources/themes/invalid-missing-manifest/templates/index.html b/src/test/resources/themes/invalid-missing-manifest/templates/index.html new file mode 100644 index 000000000..441ad470c --- /dev/null +++ b/src/test/resources/themes/invalid-missing-manifest/templates/index.html @@ -0,0 +1,12 @@ + + + + + Title + + +index +

    +
    + + diff --git a/src/test/resources/themes/invalid-missing-manifest/templates/timezone.html b/src/test/resources/themes/invalid-missing-manifest/templates/timezone.html new file mode 100644 index 000000000..d37df4ea6 --- /dev/null +++ b/src/test/resources/themes/invalid-missing-manifest/templates/timezone.html @@ -0,0 +1 @@ +

    diff --git a/src/test/resources/themes/other/theme.yaml b/src/test/resources/themes/other/theme.yaml new file mode 100644 index 000000000..1347631ed --- /dev/null +++ b/src/test/resources/themes/other/theme.yaml @@ -0,0 +1,15 @@ +apiVersion: theme.halo.run/v1alpha1 +kind: Theme +metadata: + name: default +spec: + displayName: Default + author: + name: halo-dev + website: https://halo.run + description: Default theme for Halo 2.0 + logo: https://halo.run/logo + website: https://github.com/halo-sigs/theme-default + repo: https://github.com/halo-sigs/theme-default.git + version: 1.0.1 + require: 2.0.0