mirror of https://github.com/halo-dev/halo
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
parent
2d87a95193
commit
fe3860a8b2
|
@ -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();
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in New Issue