refactor: reload theme.yaml when theme reload (#2486)

#### What type of PR is this?
/kind improvement
/area core
/milestone 2.0

#### What this PR does / why we need it:
主题 reload endpoint 增加对 theme.yaml 的 reload 操作
#### Which issue(s) this PR fixes:

Fixes #

#### Special notes for your reviewer:
/cc @halo-dev/sig-halo 
#### Does this PR introduce a user-facing change?

```release-note
None
```
pull/2493/head
guqing 2022-09-28 23:28:18 +08:00 committed by GitHub
parent 2d87a95193
commit fe3860a8b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 61 additions and 13 deletions

View File

@ -53,6 +53,7 @@ import run.halo.app.infra.properties.HaloProperties;
import run.halo.app.infra.utils.DataBufferUtils; import run.halo.app.infra.utils.DataBufferUtils;
import run.halo.app.infra.utils.FileUtils; import run.halo.app.infra.utils.FileUtils;
import run.halo.app.infra.utils.YamlUnstructuredLoader; import run.halo.app.infra.utils.YamlUnstructuredLoader;
import run.halo.app.theme.ThemePathPolicy;
/** /**
* Endpoint for managing themes. * Endpoint for managing themes.
@ -66,10 +67,12 @@ public class ThemeEndpoint implements CustomEndpoint {
private final ReactiveExtensionClient client; private final ReactiveExtensionClient client;
private final HaloProperties haloProperties; private final HaloProperties haloProperties;
private final ThemePathPolicy themePathPolicy;
public ThemeEndpoint(ReactiveExtensionClient client, HaloProperties haloProperties) { public ThemeEndpoint(ReactiveExtensionClient client, HaloProperties haloProperties) {
this.client = client; this.client = client;
this.haloProperties = haloProperties; this.haloProperties = haloProperties;
this.themePathPolicy = new ThemePathPolicy(haloProperties.getWorkDir());
} }
@Override @Override
@ -101,7 +104,7 @@ public class ThemeEndpoint implements CustomEndpoint {
.implementation(String.class) .implementation(String.class)
) )
.response(responseBuilder() .response(responseBuilder()
.implementation(Setting.class)) .implementation(Theme.class))
) )
.build(); .build();
} }
@ -124,12 +127,26 @@ public class ThemeEndpoint implements CustomEndpoint {
persistent.setSpec(setting.getSpec()); persistent.setSpec(setting.getSpec());
return client.update(persistent); return client.update(persistent);
}) })
.switchIfEmpty(Mono.defer(() -> client.create(setting)))) .switchIfEmpty(Mono.defer(() -> client.create(setting)))
.orElse(Mono.empty()); .thenReturn(theme)
)
.orElse(Mono.just(theme));
}) })
.flatMap(setting -> ServerResponse.ok() .flatMap(themeToUse -> {
Path themePath = themePathPolicy.generate(themeToUse);
Path themeManifestPath = ThemeUtils.resolveThemeManifest(themePath);
if (themeManifestPath == null) {
return Mono.error(new IllegalArgumentException(
"The manifest file [theme.yaml] is required."));
}
Unstructured unstructured = ThemeUtils.loadThemeManifest(themeManifestPath);
Theme newTheme = Unstructured.OBJECT_MAPPER.convertValue(unstructured, Theme.class);
themeToUse.setSpec(newTheme.getSpec());
return client.update(themeToUse);
})
.flatMap(theme -> ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON)
.bodyValue(setting)); .bodyValue(theme));
} }
public record InstallRequest( public record InstallRequest(
@ -299,7 +316,7 @@ public class ThemeEndpoint implements CustomEndpoint {
} }
} }
private static Unstructured loadThemeManifest(Path themeManifestPath) { static Unstructured loadThemeManifest(Path themeManifestPath) {
List<Unstructured> unstructureds = List<Unstructured> unstructureds =
new YamlUnstructuredLoader(new FileSystemResource(themeManifestPath)) new YamlUnstructuredLoader(new FileSystemResource(themeManifestPath))
.load(); .load();

View File

@ -30,6 +30,7 @@ import org.springframework.web.reactive.function.BodyInserters;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import run.halo.app.core.extension.Setting; import run.halo.app.core.extension.Setting;
import run.halo.app.core.extension.Theme; import run.halo.app.core.extension.Theme;
import run.halo.app.extension.AbstractExtension;
import run.halo.app.extension.Metadata; import run.halo.app.extension.Metadata;
import run.halo.app.extension.ReactiveExtensionClient; import run.halo.app.extension.ReactiveExtensionClient;
import run.halo.app.extension.Unstructured; import run.halo.app.extension.Unstructured;
@ -61,11 +62,10 @@ class ThemeEndpointTest {
@BeforeEach @BeforeEach
void setUp() throws IOException { void setUp() throws IOException {
tmpHaloWorkDir = Files.createTempDirectory("halo-unit-test"); tmpHaloWorkDir = Files.createTempDirectory("halo-unit-test");
when(haloProperties.getWorkDir()).thenReturn(tmpHaloWorkDir);
ThemeEndpoint themeEndpoint = new ThemeEndpoint(extensionClient, haloProperties); ThemeEndpoint themeEndpoint = new ThemeEndpoint(extensionClient, haloProperties);
when(haloProperties.getWorkDir()).thenReturn(tmpHaloWorkDir);
defaultTheme = ResourceUtils.getFile("classpath:themes/test-theme.zip"); defaultTheme = ResourceUtils.getFile("classpath:themes/test-theme.zip");
webTestClient = WebTestClient webTestClient = WebTestClient
@ -122,6 +122,7 @@ class ThemeEndpointTest {
theme.setMetadata(new Metadata()); theme.setMetadata(new Metadata());
theme.getMetadata().setName("fake-theme"); theme.getMetadata().setName("fake-theme");
theme.setSpec(new Theme.ThemeSpec()); theme.setSpec(new Theme.ThemeSpec());
theme.getSpec().setDisplayName("Hello");
theme.getSpec().setSettingName("fake-setting"); theme.getSpec().setSettingName("fake-setting");
when(extensionClient.fetch(Theme.class, "fake-theme")) when(extensionClient.fetch(Theme.class, "fake-theme"))
.thenReturn(Mono.just(theme)); .thenReturn(Mono.just(theme));
@ -152,9 +153,20 @@ class ThemeEndpointTest {
children: Register children: Register
"""); """);
Files.writeString(themeWorkDir.resolve("theme.yaml"), """
apiVersion: v1alpha1
kind: Theme
metadata:
name: fake-theme
spec:
displayName: Fake Theme
""");
when(extensionClient.update(any(Theme.class)))
.thenReturn(Mono.just(theme));
when(extensionClient.update(any(Setting.class))) when(extensionClient.update(any(Setting.class)))
.thenReturn(Mono.just(setting)); .thenReturn(Mono.just(setting));
ArgumentCaptor<Setting> captor = ArgumentCaptor.forClass(Setting.class); ArgumentCaptor<AbstractExtension> captor = ArgumentCaptor.forClass(Setting.class);
webTestClient.put() webTestClient.put()
.uri("/themes/fake-theme/reload-setting") .uri("/themes/fake-theme/reload-setting")
.exchange() .exchange()
@ -162,10 +174,12 @@ class ThemeEndpointTest {
.isOk() .isOk()
.expectBody(Setting.class) .expectBody(Setting.class)
.value(settingRes -> { .value(settingRes -> {
verify(extensionClient, times(1)).update(captor.capture()); verify(extensionClient, times(2)).update(captor.capture());
verify(extensionClient, times(0)).create(any(Setting.class)); verify(extensionClient, times(0)).create(any(Setting.class));
Setting value = captor.getValue(); List<AbstractExtension> allValues = captor.getAllValues();
System.out.println(JsonUtils.objectToJson(value)); assertThat(allValues.get(0)).isInstanceOfAny(Setting.class);
Setting newSetting = (Setting) allValues.get(0);
Theme newTheme = (Theme) allValues.get(1);
try { try {
JSONAssert.assertEquals(""" JSONAssert.assertEquals("""
{ {
@ -188,7 +202,24 @@ class ThemeEndpointTest {
"metadata": {} "metadata": {}
} }
""", """,
JsonUtils.objectToJson(value), JsonUtils.objectToJson(newSetting),
true);
JSONAssert.assertEquals("""
{
"spec": {
"displayName": "Fake Theme",
"version": "*",
"require": "*"
},
"apiVersion": "theme.halo.run/v1alpha1",
"kind": "Theme",
"metadata": {
"name": "fake-theme"
}
}
""",
JsonUtils.objectToJson(newTheme),
true); true);
} catch (JSONException e) { } catch (JSONException e) {
throw new RuntimeException(e); throw new RuntimeException(e);