feat: support JSON-based retrieval and update for theme and plugin configs

pull/6661/head
guqing 2024-09-14 16:08:50 +08:00
parent 46793af0bd
commit a634065d48
11 changed files with 605 additions and 22 deletions

View File

@ -2871,7 +2871,7 @@
},
"/apis/api.console.halo.run/v1alpha1/plugins/{name}/config": {
"get": {
"description": "Fetch configMap of plugin by configured configMapName.",
"description": "Fetch configMap of plugin by configured configMapName. it is deprecated since 2.20.0",
"operationId": "fetchPluginConfig",
"parameters": [
{
@ -2900,7 +2900,8 @@
]
},
"put": {
"description": "Update the configMap of plugin setting.",
"deprecated": true,
"description": "Update the configMap of plugin setting, it is deprecated since 2.20.0",
"operationId": "updatePluginConfig",
"parameters": [
{
@ -2939,6 +2940,70 @@
]
}
},
"/apis/api.console.halo.run/v1alpha1/plugins/{name}/json-config": {
"get": {
"description": "Fetch converted json config of plugin by configured configMapName.",
"operationId": "fetchPluginJsonConfig",
"parameters": [
{
"in": "path",
"name": "name",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"default": {
"content": {
"*/*": {
"schema": {
"type": "object"
}
}
},
"description": "default response"
}
},
"tags": [
"PluginV1alpha1Console"
]
},
"put": {
"description": "Update the config of plugin setting.",
"operationId": "updatePluginJsonConfig",
"parameters": [
{
"in": "path",
"name": "name",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
},
"required": true
},
"responses": {
"204": {
"content": {},
"description": "No Content"
}
},
"tags": [
"PluginV1alpha1Console"
]
}
},
"/apis/api.console.halo.run/v1alpha1/plugins/{name}/plugin-state": {
"put": {
"description": "Change the running state of a plugin by name.",
@ -4545,7 +4610,8 @@
},
"/apis/api.console.halo.run/v1alpha1/themes/{name}/config": {
"get": {
"description": "Fetch configMap of theme by configured configMapName.",
"deprecated": true,
"description": "Fetch configMap of theme by configured configMapName. It is deprecated.",
"operationId": "fetchThemeConfig",
"parameters": [
{
@ -4574,7 +4640,8 @@
]
},
"put": {
"description": "Update the configMap of theme setting.",
"deprecated": true,
"description": "Update the configMap of theme setting. It is deprecated.",
"operationId": "updateThemeConfig",
"parameters": [
{
@ -4637,6 +4704,70 @@
]
}
},
"/apis/api.console.halo.run/v1alpha1/themes/{name}/json-config": {
"get": {
"description": "Fetch converted json config of theme by configured configMapName.",
"operationId": "fetchThemeJsonConfig",
"parameters": [
{
"in": "path",
"name": "name",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"default": {
"content": {
"*/*": {
"schema": {
"type": "object"
}
}
},
"description": "default response"
}
},
"tags": [
"ThemeV1alpha1Console"
]
},
"put": {
"description": "Update the configMap of theme setting.",
"operationId": "updateThemeJsonConfig",
"parameters": [
{
"in": "path",
"name": "name",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
},
"required": true
},
"responses": {
"204": {
"content": {},
"description": "No Content"
}
},
"tags": [
"ThemeV1alpha1Console"
]
}
},
"/apis/api.console.halo.run/v1alpha1/themes/{name}/reload": {
"put": {
"description": "Reload theme setting.",

View File

@ -738,7 +738,7 @@
},
"/apis/api.console.halo.run/v1alpha1/plugins/{name}/config": {
"get": {
"description": "Fetch configMap of plugin by configured configMapName.",
"description": "Fetch configMap of plugin by configured configMapName. it is deprecated since 2.20.0",
"operationId": "fetchPluginConfig",
"parameters": [
{
@ -767,7 +767,8 @@
]
},
"put": {
"description": "Update the configMap of plugin setting.",
"deprecated": true,
"description": "Update the configMap of plugin setting, it is deprecated since 2.20.0",
"operationId": "updatePluginConfig",
"parameters": [
{
@ -806,6 +807,70 @@
]
}
},
"/apis/api.console.halo.run/v1alpha1/plugins/{name}/json-config": {
"get": {
"description": "Fetch converted json config of plugin by configured configMapName.",
"operationId": "fetchPluginJsonConfig",
"parameters": [
{
"in": "path",
"name": "name",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"default": {
"content": {
"*/*": {
"schema": {
"type": "object"
}
}
},
"description": "default response"
}
},
"tags": [
"PluginV1alpha1Console"
]
},
"put": {
"description": "Update the config of plugin setting.",
"operationId": "updatePluginJsonConfig",
"parameters": [
{
"in": "path",
"name": "name",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
},
"required": true
},
"responses": {
"204": {
"content": {},
"description": "No Content"
}
},
"tags": [
"PluginV1alpha1Console"
]
}
},
"/apis/api.console.halo.run/v1alpha1/plugins/{name}/plugin-state": {
"put": {
"description": "Change the running state of a plugin by name.",
@ -2412,7 +2477,8 @@
},
"/apis/api.console.halo.run/v1alpha1/themes/{name}/config": {
"get": {
"description": "Fetch configMap of theme by configured configMapName.",
"deprecated": true,
"description": "Fetch configMap of theme by configured configMapName. It is deprecated.",
"operationId": "fetchThemeConfig",
"parameters": [
{
@ -2441,7 +2507,8 @@
]
},
"put": {
"description": "Update the configMap of theme setting.",
"deprecated": true,
"description": "Update the configMap of theme setting. It is deprecated.",
"operationId": "updateThemeConfig",
"parameters": [
{
@ -2504,6 +2571,70 @@
]
}
},
"/apis/api.console.halo.run/v1alpha1/themes/{name}/json-config": {
"get": {
"description": "Fetch converted json config of theme by configured configMapName.",
"operationId": "fetchThemeJsonConfig",
"parameters": [
{
"in": "path",
"name": "name",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"default": {
"content": {
"*/*": {
"schema": {
"type": "object"
}
}
},
"description": "default response"
}
},
"tags": [
"ThemeV1alpha1Console"
]
},
"put": {
"description": "Update the configMap of theme setting.",
"operationId": "updateThemeJsonConfig",
"parameters": [
{
"in": "path",
"name": "name",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
},
"required": true
},
"responses": {
"204": {
"content": {},
"description": "No Content"
}
},
"tags": [
"ThemeV1alpha1Console"
]
}
},
"/apis/api.console.halo.run/v1alpha1/themes/{name}/reload": {
"put": {
"description": "Reload theme setting.",

View File

@ -17,6 +17,7 @@ import static run.halo.app.extension.index.query.QueryFactory.or;
import static run.halo.app.extension.router.QueryParamBuildUtil.sortParameter;
import static run.halo.app.infra.utils.FileUtils.deleteFileSilently;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.media.Schema;
import java.io.FileNotFoundException;
@ -41,6 +42,7 @@ import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.data.domain.Sort;
import org.springframework.http.CacheControl;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.multipart.FilePart;
import org.springframework.http.codec.multipart.FormFieldPart;
@ -61,6 +63,7 @@ import reactor.util.retry.Retry;
import run.halo.app.core.extension.Plugin;
import run.halo.app.core.extension.Setting;
import run.halo.app.core.extension.service.PluginService;
import run.halo.app.core.extension.service.SettingConfigService;
import run.halo.app.core.extension.theme.SettingUtils;
import run.halo.app.extension.ConfigMap;
import run.halo.app.extension.ListOptions;
@ -80,6 +83,8 @@ public class PluginEndpoint implements CustomEndpoint, InitializingBean {
private final ReactiveUrlDataBufferFetcher reactiveUrlDataBufferFetcher;
private final SettingConfigService settingConfigService;
private final WebProperties webProperties;
private final Scheduler scheduler = Schedulers.boundedElastic();
@ -91,10 +96,12 @@ public class PluginEndpoint implements CustomEndpoint, InitializingBean {
public PluginEndpoint(ReactiveExtensionClient client,
PluginService pluginService,
ReactiveUrlDataBufferFetcher reactiveUrlDataBufferFetcher,
SettingConfigService settingConfigService,
WebProperties webProperties) {
this.client = client;
this.pluginService = pluginService;
this.reactiveUrlDataBufferFetcher = reactiveUrlDataBufferFetcher;
this.settingConfigService = settingConfigService;
this.webProperties = webProperties;
}
@ -157,9 +164,9 @@ public class PluginEndpoint implements CustomEndpoint, InitializingBean {
.content(contentBuilder().mediaType(MediaType.MULTIPART_FORM_DATA_VALUE)
.schema(schemaBuilder().implementation(InstallRequest.class))))
)
.PUT("plugins/{name}/config", this::updatePluginConfig,
builder -> builder.operationId("updatePluginConfig")
.description("Update the configMap of plugin setting.")
.PUT("plugins/{name}/json-config", this::updatePluginJsonConfig,
builder -> builder.operationId("updatePluginJsonConfig")
.description("Update the config of plugin setting.")
.tag(tag)
.parameter(parameterBuilder()
.name("name")
@ -167,6 +174,26 @@ public class PluginEndpoint implements CustomEndpoint, InitializingBean {
.required(true)
.implementation(String.class)
)
.requestBody(requestBodyBuilder()
.required(true)
.content(contentBuilder().mediaType(MediaType.APPLICATION_JSON_VALUE)
.schema(schemaBuilder().implementation(ObjectNode.class))))
.response(responseBuilder()
.responseCode(String.valueOf(HttpStatus.NO_CONTENT.value()))
.implementation(Void.class))
)
.PUT("plugins/{name}/config", this::updatePluginConfig,
builder -> builder.operationId("updatePluginConfig")
.description(
"Update the configMap of plugin setting, it is deprecated since 2.20.0")
.tag(tag)
.parameter(parameterBuilder()
.name("name")
.in(ParameterIn.PATH)
.required(true)
.implementation(String.class)
)
.deprecated(true)
.requestBody(requestBodyBuilder()
.required(true)
.content(contentBuilder().mediaType(MediaType.APPLICATION_JSON_VALUE)
@ -243,7 +270,9 @@ public class PluginEndpoint implements CustomEndpoint, InitializingBean {
)
.GET("plugins/{name}/config", this::fetchPluginConfig,
builder -> builder.operationId("fetchPluginConfig")
.description("Fetch configMap of plugin by configured configMapName.")
.description(
"Fetch configMap of plugin by configured configMapName. it is deprecated "
+ "since 2.20.0")
.tag(tag)
.parameter(parameterBuilder()
.name("name")
@ -254,6 +283,20 @@ public class PluginEndpoint implements CustomEndpoint, InitializingBean {
.response(responseBuilder()
.implementation(ConfigMap.class))
)
.GET("plugins/{name}/json-config", this::fetchPluginJsonConfig,
builder -> builder.operationId("fetchPluginJsonConfig")
.description(
"Fetch converted json config of plugin by configured configMapName.")
.tag(tag)
.parameter(parameterBuilder()
.name("name")
.in(ParameterIn.PATH)
.required(true)
.implementation(String.class)
)
.response(responseBuilder()
.implementation(ObjectNode.class))
)
.GET("plugin-presets", this::listPresets,
builder -> builder.operationId("ListPluginPresets")
.description("List all plugin presets in the system.")
@ -275,6 +318,35 @@ public class PluginEndpoint implements CustomEndpoint, InitializingBean {
.build();
}
private Mono<ServerResponse> fetchPluginJsonConfig(ServerRequest request) {
final var name = request.pathVariable("name");
return client.fetch(Plugin.class, name)
.mapNotNull(plugin -> plugin.getSpec().getConfigMapName())
.flatMap(settingConfigService::fetchConfig)
.flatMap(json -> ServerResponse.ok().bodyValue(json));
}
private Mono<ServerResponse> updatePluginJsonConfig(ServerRequest request) {
final var pluginName = request.pathVariable("name");
return client.fetch(Plugin.class, pluginName)
.doOnNext(plugin -> {
String configMapName = plugin.getSpec().getConfigMapName();
if (!StringUtils.hasText(configMapName)) {
throw new ServerWebInputException(
"Unable to complete the request because the plugin configMapName is blank");
}
})
.flatMap(plugin -> {
final String configMapName = plugin.getSpec().getConfigMapName();
return request.bodyToMono(ObjectNode.class)
.switchIfEmpty(
Mono.error(new ServerWebInputException("Required request body is missing")))
.flatMap(configMapJsonData ->
settingConfigService.upsertConfig(configMapName, configMapJsonData));
})
.then(ServerResponse.noContent().build());
}
Mono<ServerResponse> changePluginRunningState(ServerRequest request) {
final var name = request.pathVariable("name");
return request.bodyToMono(RunningStateRequest.class)

View File

@ -0,0 +1,19 @@
package run.halo.app.core.extension.service;
import com.fasterxml.jackson.databind.node.ObjectNode;
import reactor.core.publisher.Mono;
import run.halo.app.core.extension.Setting;
import run.halo.app.extension.ConfigMap;
/**
* {@link Setting} related {@link ConfigMap} service.
*
* @author guqing
* @since 2.20.0
*/
public interface SettingConfigService {
Mono<Void> upsertConfig(String configMapName, ObjectNode configJsonData);
Mono<ObjectNode> fetchConfig(String configMapName);
}

View File

@ -0,0 +1,57 @@
package run.halo.app.core.extension.service.impl;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.time.Duration;
import lombok.RequiredArgsConstructor;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import reactor.core.publisher.Mono;
import reactor.util.retry.Retry;
import run.halo.app.core.extension.Setting;
import run.halo.app.core.extension.service.SettingConfigService;
import run.halo.app.core.extension.theme.SettingUtils;
import run.halo.app.extension.ConfigMap;
import run.halo.app.extension.Metadata;
import run.halo.app.extension.ReactiveExtensionClient;
/**
* {@link Setting} related {@link ConfigMap} service implementation.
*
* @author guqing
* @since 2.20.0
*/
@Component
@RequiredArgsConstructor
public class SettingConfigServiceImpl implements SettingConfigService {
private final ReactiveExtensionClient client;
@Override
public Mono<Void> upsertConfig(String configMapName, ObjectNode configJsonData) {
Assert.notNull(configMapName, "Config map name must not be null");
Assert.notNull(configJsonData, "Config json data must not be null");
var data = SettingUtils.settingConfigJsonToMap(configJsonData);
return Mono.defer(() -> client.fetch(ConfigMap.class, configMapName)
.flatMap(persisted -> {
persisted.setData(data);
return client.update(persisted);
}))
.retryWhen(Retry.backoff(5, Duration.ofMillis(100))
.filter(OptimisticLockingFailureException.class::isInstance)
)
.switchIfEmpty(Mono.defer(() -> {
var configMap = new ConfigMap();
configMap.setMetadata(new Metadata());
configMap.getMetadata().setName(configMapName);
configMap.setData(data);
return client.create(configMap);
}))
.then();
}
@Override
public Mono<ObjectNode> fetchConfig(String configMapName) {
return client.fetch(ConfigMap.class, configMapName)
.map(SettingUtils::settingConfigToJson);
}
}

View File

@ -129,7 +129,30 @@ public class SettingUtils {
}
}
JsonNode mapToJsonNode(Map<String, String> map) {
/**
* Convert {@link Setting} related configMap data to JsonNode.
*
* @param configMap {@link ConfigMap} instance
* @return JsonNode
*/
public static ObjectNode settingConfigToJson(ConfigMap configMap) {
if (configMap.getData() == null) {
return JsonNodeFactory.instance.objectNode();
}
return mapToJsonNode(configMap.getData());
}
/**
* Convert the result of {@link #settingConfigToJson(ConfigMap)} in reverse to Map.
*
* @param node JsonNode object
* @return {@link ConfigMap#getData()}
*/
public static Map<String, String> settingConfigJsonToMap(ObjectNode node) {
return jsonNodeToStringMap(node);
}
ObjectNode mapToJsonNode(Map<String, String> map) {
ObjectNode objectNode = JsonNodeFactory.instance.objectNode();
map.forEach((k, v) -> {
if (isJson(v)) {

View File

@ -9,6 +9,7 @@ import static org.springdoc.core.fn.builders.schema.Builder.schemaBuilder;
import static org.springframework.http.HttpStatus.NO_CONTENT;
import static org.springframework.web.reactive.function.server.RequestPredicates.contentType;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.media.Schema;
import java.net.URI;
@ -38,6 +39,7 @@ 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.core.extension.service.SettingConfigService;
import run.halo.app.extension.ConfigMap;
import run.halo.app.extension.ListResult;
import run.halo.app.extension.ReactiveExtensionClient;
@ -73,6 +75,8 @@ public class ThemeEndpoint implements CustomEndpoint {
private final ReactiveUrlDataBufferFetcher urlDataBufferFetcher;
private final SettingConfigService settingConfigService;
@Override
public RouterFunction<ServerResponse> endpoint() {
var tag = "ThemeV1alpha1Console";
@ -161,8 +165,9 @@ public class ThemeEndpoint implements CustomEndpoint {
)
.PUT("themes/{name}/config", this::updateThemeConfig,
builder -> builder.operationId("updateThemeConfig")
.description("Update the configMap of theme setting.")
.description("Update the configMap of theme setting. It is deprecated.")
.tag(tag)
.deprecated(true)
.parameter(parameterBuilder()
.name("name")
.in(ParameterIn.PATH)
@ -176,6 +181,24 @@ public class ThemeEndpoint implements CustomEndpoint {
.response(responseBuilder()
.implementation(ConfigMap.class))
)
.PUT("themes/{name}/json-config", this::updateThemeJsonConfig,
builder -> builder.operationId("updateThemeJsonConfig")
.description("Update the configMap of theme setting.")
.tag(tag)
.parameter(parameterBuilder()
.name("name")
.in(ParameterIn.PATH)
.required(true)
.implementation(String.class)
)
.requestBody(requestBodyBuilder()
.required(true)
.content(contentBuilder().mediaType(MediaType.APPLICATION_JSON_VALUE)
.schema(schemaBuilder().implementation(ObjectNode.class))))
.response(responseBuilder()
.responseCode(String.valueOf(NO_CONTENT.value()))
.implementation(Void.class))
)
.PUT("themes/{name}/activation", this::activateTheme,
builder -> builder.operationId("activateTheme")
.description("Activate a theme by name.")
@ -235,8 +258,10 @@ public class ThemeEndpoint implements CustomEndpoint {
)
.GET("themes/{name}/config", this::fetchThemeConfig,
builder -> builder.operationId("fetchThemeConfig")
.description("Fetch configMap of theme by configured configMapName.")
.description(
"Fetch configMap of theme by configured configMapName. It is deprecated.")
.tag(tag)
.deprecated(true)
.parameter(parameterBuilder()
.name("name")
.in(ParameterIn.PATH)
@ -246,9 +271,52 @@ public class ThemeEndpoint implements CustomEndpoint {
.response(responseBuilder()
.implementation(ConfigMap.class))
)
.GET("themes/{name}/json-config", this::fetchThemeJsonConfig,
builder -> builder.operationId("fetchThemeJsonConfig")
.description(
"Fetch converted json config of theme by configured configMapName.")
.tag(tag)
.parameter(parameterBuilder()
.name("name")
.in(ParameterIn.PATH)
.required(true)
.implementation(String.class)
)
.response(responseBuilder()
.implementation(ObjectNode.class))
)
.build();
}
private Mono<ServerResponse> fetchThemeJsonConfig(ServerRequest request) {
return themeNameInPathVariableOrActivated(request)
.flatMap(themeName -> client.fetch(Theme.class, themeName))
.mapNotNull(theme -> theme.getSpec().getConfigMapName())
.flatMap(settingConfigService::fetchConfig)
.flatMap(json -> ServerResponse.ok().bodyValue(json));
}
private Mono<ServerResponse> updateThemeJsonConfig(ServerRequest request) {
final var themeName = request.pathVariable("name");
return client.fetch(Theme.class, themeName)
.doOnNext(theme -> {
String configMapName = theme.getSpec().getConfigMapName();
if (StringUtils.isBlank(configMapName)) {
throw new ServerWebInputException(
"Unable to complete the request because the theme configMapName is blank.");
}
})
.flatMap(theme -> {
final var configMapName = theme.getSpec().getConfigMapName();
return request.bodyToMono(ObjectNode.class)
.switchIfEmpty(
Mono.error(new ServerWebInputException("Required request body is missing")))
.flatMap(configJsonData ->
settingConfigService.upsertConfig(configMapName, configJsonData));
})
.then(ServerResponse.noContent().build());
}
private Mono<ServerResponse> invalidateCache(ServerRequest request) {
final var name = request.pathVariable("name");
return client.get(Theme.class, name)
@ -306,6 +374,7 @@ public class ThemeEndpoint implements CustomEndpoint {
.flatMap(activatedTheme -> ServerResponse.ok().bodyValue(activatedTheme));
}
@Deprecated(since = "2.20.0", forRemoval = true)
private Mono<ServerResponse> updateThemeConfig(ServerRequest request) {
final var themeName = request.pathVariable("name");
return client.fetch(Theme.class, themeName)
@ -343,6 +412,7 @@ public class ThemeEndpoint implements CustomEndpoint {
.flatMap(configMap -> ServerResponse.ok().bodyValue(configMap));
}
@Deprecated(since = "2.20.0", forRemoval = true)
private Mono<ServerResponse> fetchThemeConfig(ServerRequest request) {
return themeNameInPathVariableOrActivated(request)
.flatMap(themeName -> client.fetch(Theme.class, themeName))

View File

@ -16,8 +16,8 @@ rules:
resources: [ "plugins" ]
verbs: [ "create", "patch", "update", "delete", "deletecollection" ]
- apiGroups: [ "api.console.halo.run" ]
resources: [ "plugins/upgrade", "plugins/resetconfig", "plugins/config", "plugins/reload",
"plugins/install-from-uri", "plugins/upgrade-from-uri", "plugins/plugin-state" ]
resources: [ "plugins/upgrade", "plugins/resetconfig", "plugins/config", "plugins/json-config",
"plugins/reload", "plugins/install-from-uri", "plugins/upgrade-from-uri", "plugins/plugin-state" ]
verbs: [ "*" ]
- apiGroups: [ "api.console.halo.run" ]
resources: [ "plugin-presets" ]
@ -41,5 +41,5 @@ rules:
resources: [ "plugins" ]
verbs: [ "get", "list" ]
- apiGroups: [ "api.console.halo.run" ]
resources: [ "plugins", "plugins/setting", "plugins/config" ]
resources: [ "plugins", "plugins/setting", "plugins/config", "plugins/json-config" ]
verbs: [ "get", "list" ]

View File

@ -15,8 +15,8 @@ rules:
resources: [ "themes" ]
verbs: [ "*" ]
- apiGroups: [ "api.console.halo.run" ]
resources: [ "themes", "themes/reload", "themes/resetconfig", "themes/config", "themes/activation",
"themes/install-from-uri", "themes/upgrade-from-uri", "themes/invalidate-cache" ]
resources: [ "themes", "themes/reload", "themes/resetconfig", "themes/config", "themes/json-config",
"themes/activation", "themes/install-from-uri", "themes/upgrade-from-uri", "themes/invalidate-cache" ]
verbs: [ "*" ]
- nonResourceURLs: [ "/apis/api.console.halo.run/themes/install" ]
verbs: [ "create" ]
@ -37,6 +37,6 @@ rules:
resources: [ "themes" ]
verbs: [ "get", "list" ]
- apiGroups: [ "api.console.halo.run" ]
resources: [ "themes", "themes/activation", "themes/setting", "themes/config" ]
resources: [ "themes", "themes/activation", "themes/setting", "themes/config", "themes/json-config" ]
verbs: [ "get", "list" ]

View File

@ -22,6 +22,7 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant;
import java.util.List;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@ -45,6 +46,7 @@ import reactor.core.publisher.Mono;
import run.halo.app.core.extension.Plugin;
import run.halo.app.core.extension.Setting;
import run.halo.app.core.extension.service.PluginService;
import run.halo.app.core.extension.service.SettingConfigService;
import run.halo.app.extension.ConfigMap;
import run.halo.app.extension.ListOptions;
import run.halo.app.extension.ListResult;
@ -67,6 +69,9 @@ class PluginEndpointTest {
@Mock
PluginService pluginService;
@Mock
SettingConfigService settingConfigService;
@Spy
WebProperties webProperties = new WebProperties();
@ -278,6 +283,22 @@ class PluginEndpointTest {
.exchange()
.expectStatus().isOk();
}
@Test
void updateJsonConfigTest() {
Plugin plugin = createPlugin("fake-plugin");
plugin.getSpec().setConfigMapName("fake-config-map");
when(client.fetch(eq(Plugin.class), eq("fake-plugin"))).thenReturn(Mono.just(plugin));
when(settingConfigService.upsertConfig(eq("fake-config-map"), any()))
.thenReturn(Mono.empty());
webClient.put()
.uri("/plugins/fake-plugin/json-config")
.body(Mono.just(Map.of()), Map.class)
.exchange()
.expectStatus().is2xxSuccessful();
}
}
@Nested
@ -325,6 +346,23 @@ class PluginEndpointTest {
verify(client).fetch(eq(ConfigMap.class), eq("fake-config"));
verify(client).fetch(eq(Plugin.class), eq("fake"));
}
@Test
void fetchJsonConfig() {
Plugin plugin = createPlugin("fake");
plugin.getSpec().setConfigMapName("fake-config");
when(settingConfigService.fetchConfig(eq("fake-config")))
.thenReturn(Mono.empty());
when(client.fetch(eq(Plugin.class), eq("fake"))).thenReturn(Mono.just(plugin));
webClient.get()
.uri("/plugins/fake/json-config")
.exchange()
.expectStatus().isOk();
verify(settingConfigService).fetchConfig(eq("fake-config"));
verify(client).fetch(eq(Plugin.class), eq("fake"));
}
}
Plugin createPlugin(String name) {

View File

@ -14,6 +14,7 @@ import java.io.IOException;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
@ -33,6 +34,7 @@ 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.core.extension.service.SettingConfigService;
import run.halo.app.extension.ConfigMap;
import run.halo.app.extension.Metadata;
import run.halo.app.extension.ReactiveExtensionClient;
@ -69,6 +71,9 @@ class ThemeEndpointTest {
@Mock
private ReactiveUrlDataBufferFetcher urlDataBufferFetcher;
@Mock
private SettingConfigService settingConfigService;
@InjectMocks
ThemeEndpoint themeEndpoint;
@ -298,8 +303,25 @@ class ThemeEndpointTest {
.exchange()
.expectStatus().isOk();
}
}
@Test
void updateJsonConfigTest() {
Theme theme = new Theme();
theme.setMetadata(new Metadata());
theme.setSpec(new Theme.ThemeSpec());
theme.getSpec().setConfigMapName("fake-config-map");
when(client.fetch(eq(Theme.class), eq("fake-theme"))).thenReturn(Mono.just(theme));
when(settingConfigService.upsertConfig(eq("fake-config-map"), any()))
.thenReturn(Mono.empty());
webTestClient.put()
.uri("/themes/fake-theme/json-config")
.body(Mono.just(Map.of()), Map.class)
.exchange()
.expectStatus().is2xxSuccessful();
}
}
@Test
void fetchActivatedTheme() {
@ -360,4 +382,24 @@ class ThemeEndpointTest {
verify(client).fetch(eq(ConfigMap.class), eq("fake-config"));
verify(client).fetch(eq(Theme.class), eq("fake"));
}
@Test
void fetchThemeJsonConfigTest() {
Theme theme = new Theme();
theme.setMetadata(new Metadata());
theme.getMetadata().setName("fake");
theme.setSpec(new Theme.ThemeSpec());
theme.getSpec().setConfigMapName("fake-config");
when(settingConfigService.fetchConfig(eq("fake-config"))).thenReturn(Mono.empty());
when(client.fetch(eq(Theme.class), eq("fake"))).thenReturn(Mono.just(theme));
webTestClient.get()
.uri("/themes/fake/json-config")
.exchange()
.expectStatus().isOk();
verify(settingConfigService).fetchConfig(eq("fake-config"));
verify(client).fetch(eq(Theme.class), eq("fake"));
}
}