mirror of https://github.com/halo-dev/halo
feat: support JSON-based retrieval and update for theme and plugin configs
parent
46793af0bd
commit
a634065d48
|
@ -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.",
|
||||
|
|
|
@ -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.",
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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)) {
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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" ]
|
||||
|
|
|
@ -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" ]
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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"));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue