mirror of https://github.com/halo-dev/halo
feat: theme side provides variables for theme and some system settings (#2406)
#### What type of PR is this? /kind feature /milestone 2.0 /area core #### What this PR does / why we need it: 提供当前使用主题(预览或激活)的 configMap 变量和部分系统设置等变量。 提供了以下变量: - `${theme}` 当前主题的信息,theme.yaml - `${theme.config}` 获取当前主题的设置项 - ~`${siteSetting}`~ `${site}` 提供必要系统变量 #### Which issue(s) this PR fixes: Fixes #2389 #### Special notes for your reviewer: how to test it? 再任意主题模板上使用表达式获取例如:`${theme}`,`${theme.config.sns?.email}` /cc @halo-dev/sig-halo #### Does this PR introduce a user-facing change? ```release-note None ```pull/2415/head
parent
069ff04c84
commit
a0d55c58f6
|
@ -74,6 +74,7 @@ dependencies {
|
|||
implementation "com.google.guava:guava:$guava"
|
||||
implementation "org.jsoup:jsoup:$jsoup"
|
||||
implementation "io.github.java-diff-utils:java-diff-utils:$javaDiffUtils"
|
||||
implementation "org.springframework.integration:spring-integration-core"
|
||||
|
||||
compileOnly 'org.projectlombok:lombok'
|
||||
testCompileOnly 'org.projectlombok:lombok'
|
||||
|
|
|
@ -2,6 +2,7 @@ package run.halo.app;
|
|||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import run.halo.app.infra.properties.HaloProperties;
|
||||
import run.halo.app.infra.properties.JwtProperties;
|
||||
|
@ -14,7 +15,8 @@ import run.halo.app.infra.properties.JwtProperties;
|
|||
* @author guqing
|
||||
* @date 2017-11-14
|
||||
*/
|
||||
@SpringBootApplication
|
||||
@SpringBootApplication(scanBasePackages = "run.halo.app", exclude =
|
||||
IntegrationAutoConfiguration.class)
|
||||
@EnableConfigurationProperties({HaloProperties.class, JwtProperties.class})
|
||||
public class Application {
|
||||
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
package run.halo.app.core.extension.reconciler;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import run.halo.app.extension.ConfigMap;
|
||||
|
@ -19,13 +22,16 @@ import run.halo.app.theme.router.PermalinkRuleChangedEvent;
|
|||
* @author guqing
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Slf4j
|
||||
public class SystemSettingReconciler implements Reconciler<Reconciler.Request> {
|
||||
|
||||
private static final String OLD_THEME_ROUTE_RULES = "halo.run/old-theme-route-rules";
|
||||
public static final String OLD_THEME_ROUTE_RULES = "halo.run/old-theme-route-rules";
|
||||
public static final String FINALIZER_NAME = "system-setting-protection";
|
||||
|
||||
private final ExtensionClient client;
|
||||
private final ApplicationContext applicationContext;
|
||||
|
||||
private final RouteRuleReconciler routeRuleReconciler = new RouteRuleReconciler();
|
||||
|
||||
public SystemSettingReconciler(ExtensionClient client, ApplicationContext applicationContext) {
|
||||
this.client = client;
|
||||
this.applicationContext = applicationContext;
|
||||
|
@ -39,110 +45,197 @@ public class SystemSettingReconciler implements Reconciler<Reconciler.Request> {
|
|||
}
|
||||
client.fetch(ConfigMap.class, name)
|
||||
.ifPresent(configMap -> {
|
||||
ConfigMap oldConfigMap = JsonUtils.deepCopy(configMap);
|
||||
|
||||
ruleChangedDispatcher(configMap);
|
||||
|
||||
if (!configMap.equals(oldConfigMap)) {
|
||||
client.update(configMap);
|
||||
}
|
||||
addFinalizerIfNecessary(configMap);
|
||||
routeRuleReconciler.reconcile(name);
|
||||
});
|
||||
return new Result(false, null);
|
||||
}
|
||||
|
||||
private void ruleChangedDispatcher(ConfigMap configMap) {
|
||||
Map<String, String> data = configMap.getData();
|
||||
private void addFinalizerIfNecessary(ConfigMap oldConfigMap) {
|
||||
Set<String> finalizers = oldConfigMap.getMetadata().getFinalizers();
|
||||
if (finalizers != null && finalizers.contains(FINALIZER_NAME)) {
|
||||
return;
|
||||
}
|
||||
client.fetch(ConfigMap.class, oldConfigMap.getMetadata().getName())
|
||||
.ifPresent(configMap -> {
|
||||
Set<String> newFinalizers = configMap.getMetadata().getFinalizers();
|
||||
if (newFinalizers == null) {
|
||||
newFinalizers = new HashSet<>();
|
||||
configMap.getMetadata().setFinalizers(newFinalizers);
|
||||
}
|
||||
newFinalizers.add(FINALIZER_NAME);
|
||||
client.update(configMap);
|
||||
});
|
||||
}
|
||||
|
||||
Map<String, String> annotations = getAnnotationsSafe(configMap);
|
||||
String oldRulesJson = annotations.get(OLD_THEME_ROUTE_RULES);
|
||||
class RouteRuleReconciler {
|
||||
|
||||
String routeRulesJson = data.get(SystemSetting.ThemeRouteRules.GROUP);
|
||||
// get new rules and replace old rules to new rules
|
||||
SystemSetting.ThemeRouteRules newRouteRules =
|
||||
JsonUtils.jsonToObject(routeRulesJson, SystemSetting.ThemeRouteRules.class);
|
||||
|
||||
// old rules is empty, means this is the first time to update theme route rules
|
||||
if (oldRulesJson == null) {
|
||||
oldRulesJson = "{}";
|
||||
public void reconcile(String name) {
|
||||
reconcileArchivesRule(name);
|
||||
reconcileTagsRule(name);
|
||||
reconcileCategoriesRule(name);
|
||||
reconcilePostRule(name);
|
||||
}
|
||||
|
||||
// diff old rules and new rules
|
||||
SystemSetting.ThemeRouteRules oldRules =
|
||||
JsonUtils.jsonToObject(oldRulesJson, SystemSetting.ThemeRouteRules.class);
|
||||
private void reconcileArchivesRule(String name) {
|
||||
client.fetch(ConfigMap.class, name).ifPresent(configMap -> {
|
||||
SystemSetting.ThemeRouteRules oldRules = getOldRouteRulesFromAnno(configMap);
|
||||
SystemSetting.ThemeRouteRules newRules = getRouteRules(configMap);
|
||||
|
||||
// dispatch event
|
||||
if (!StringUtils.equals(oldRules.getArchives(), newRouteRules.getArchives())) {
|
||||
// archives rule changed
|
||||
applicationContext.publishEvent(new PermalinkRuleChangedEvent(this,
|
||||
DefaultTemplateEnum.ARCHIVES,
|
||||
newRouteRules.getArchives()));
|
||||
final String oldArchivesPrefix = oldRules.getArchives();
|
||||
final String oldPostPattern = oldRules.getPost();
|
||||
|
||||
// dispatch event
|
||||
final boolean archivesPrefixChanged =
|
||||
!StringUtils.equals(oldRules.getArchives(), newRules.getArchives());
|
||||
|
||||
final boolean postPatternChanged =
|
||||
changePostPatternPrefixIfNecessary(oldArchivesPrefix, newRules);
|
||||
|
||||
if (archivesPrefixChanged || postPatternChanged) {
|
||||
oldRules.setPost(newRules.getPost());
|
||||
oldRules.setArchives(newRules.getArchives());
|
||||
updateNewRuleToConfigMap(configMap, oldRules, newRules);
|
||||
}
|
||||
|
||||
// archives rule changed
|
||||
if (archivesPrefixChanged) {
|
||||
log.debug("Archives prefix changed from [{}] to [{}].", oldArchivesPrefix,
|
||||
newRules.getArchives());
|
||||
applicationContext.publishEvent(new PermalinkRuleChangedEvent(this,
|
||||
DefaultTemplateEnum.ARCHIVES,
|
||||
newRules.getArchives()));
|
||||
}
|
||||
|
||||
if (postPatternChanged) {
|
||||
log.debug("Post pattern changed from [{}] to [{}].", oldPostPattern,
|
||||
newRules.getPost());
|
||||
// post rule changed
|
||||
applicationContext.publishEvent(new PermalinkRuleChangedEvent(this,
|
||||
DefaultTemplateEnum.POST, newRules.getPost()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!StringUtils.equals(oldRules.getTags(), newRouteRules.getTags())) {
|
||||
// tags rule changed
|
||||
applicationContext.publishEvent(new PermalinkRuleChangedEvent(this,
|
||||
DefaultTemplateEnum.TAGS,
|
||||
newRouteRules.getTags()));
|
||||
}
|
||||
|
||||
if (!StringUtils.equals(oldRules.getCategories(), newRouteRules.getCategories())) {
|
||||
// categories rule changed
|
||||
applicationContext.publishEvent(new PermalinkRuleChangedEvent(this,
|
||||
DefaultTemplateEnum.CATEGORIES,
|
||||
newRouteRules.getCategories()));
|
||||
}
|
||||
|
||||
if (!StringUtils.equals(oldRules.getPost(), newRouteRules.getPost())) {
|
||||
// post rule changed
|
||||
applicationContext.publishEvent(new PermalinkRuleChangedEvent(this,
|
||||
DefaultTemplateEnum.POST,
|
||||
newRouteRules.getPost()));
|
||||
}
|
||||
|
||||
// TODO 此处立即同步 post 的新 pattern 到数据库,才能更新到文章页面的 permalink 地址
|
||||
// 但会导致乐观锁失效会失败一次 reconcile
|
||||
if (changePostPatternPrefixIfNecessary(oldRules, newRouteRules)) {
|
||||
data.put(SystemSetting.ThemeRouteRules.GROUP, JsonUtils.objectToJson(newRouteRules));
|
||||
annotations.put(OLD_THEME_ROUTE_RULES, JsonUtils.objectToJson(newRouteRules));
|
||||
// update config map immediately
|
||||
private void updateNewRuleToConfigMap(ConfigMap configMap,
|
||||
SystemSetting.ThemeRouteRules oldRules,
|
||||
SystemSetting.ThemeRouteRules newRules) {
|
||||
Map<String, String> annotations = getAnnotationsSafe(configMap);
|
||||
annotations.put(OLD_THEME_ROUTE_RULES, JsonUtils.objectToJson(oldRules));
|
||||
configMap.getData().put(SystemSetting.ThemeRouteRules.GROUP,
|
||||
JsonUtils.objectToJson(newRules));
|
||||
client.update(configMap);
|
||||
}
|
||||
|
||||
// update theme setting
|
||||
data.put(SystemSetting.ThemeRouteRules.GROUP, JsonUtils.objectToJson(newRouteRules));
|
||||
annotations.put(OLD_THEME_ROUTE_RULES, JsonUtils.objectToJson(newRouteRules));
|
||||
}
|
||||
private void reconcileTagsRule(String name) {
|
||||
client.fetch(ConfigMap.class, name).ifPresent(configMap -> {
|
||||
SystemSetting.ThemeRouteRules oldRules = getOldRouteRulesFromAnno(configMap);
|
||||
SystemSetting.ThemeRouteRules newRules = getRouteRules(configMap);
|
||||
final String oldTagsPrefix = oldRules.getTags();
|
||||
if (!StringUtils.equals(oldTagsPrefix, newRules.getTags())) {
|
||||
oldRules.setTags(newRules.getTags());
|
||||
updateNewRuleToConfigMap(configMap, oldRules, newRules);
|
||||
|
||||
boolean changePostPatternPrefixIfNecessary(SystemSetting.ThemeRouteRules oldRules,
|
||||
SystemSetting.ThemeRouteRules newRules) {
|
||||
if (StringUtils.isBlank(oldRules.getArchives())
|
||||
|| StringUtils.isBlank(newRules.getPost())) {
|
||||
log.debug("Tags prefix changed from [{}] to [{}].", oldTagsPrefix,
|
||||
newRules.getTags());
|
||||
// then publish event
|
||||
applicationContext.publishEvent(new PermalinkRuleChangedEvent(this,
|
||||
DefaultTemplateEnum.TAGS,
|
||||
newRules.getTags()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void reconcileCategoriesRule(String name) {
|
||||
client.fetch(ConfigMap.class, name).ifPresent(configMap -> {
|
||||
SystemSetting.ThemeRouteRules oldRules = getOldRouteRulesFromAnno(configMap);
|
||||
SystemSetting.ThemeRouteRules newRules = getRouteRules(configMap);
|
||||
final String oldCategoriesPrefix = oldRules.getCategories();
|
||||
if (!StringUtils.equals(oldCategoriesPrefix, newRules.getCategories())) {
|
||||
oldRules.setCategories(newRules.getCategories());
|
||||
updateNewRuleToConfigMap(configMap, oldRules, newRules);
|
||||
|
||||
log.debug("Categories prefix changed from [{}] to [{}].", oldCategoriesPrefix,
|
||||
newRules.getCategories());
|
||||
// categories rule changed
|
||||
applicationContext.publishEvent(new PermalinkRuleChangedEvent(this,
|
||||
DefaultTemplateEnum.CATEGORIES,
|
||||
newRules.getCategories()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void reconcilePostRule(String name) {
|
||||
client.fetch(ConfigMap.class, name).ifPresent(configMap -> {
|
||||
SystemSetting.ThemeRouteRules oldRules = getOldRouteRulesFromAnno(configMap);
|
||||
SystemSetting.ThemeRouteRules newRules = getRouteRules(configMap);
|
||||
|
||||
final String oldPostPattern = oldRules.getPost();
|
||||
if (!StringUtils.equals(oldPostPattern, newRules.getPost())) {
|
||||
oldRules.setPost(newRules.getPost());
|
||||
updateNewRuleToConfigMap(configMap, oldRules, newRules);
|
||||
|
||||
log.debug("Categories prefix changed from [{}] to [{}].", oldPostPattern,
|
||||
newRules.getPost());
|
||||
// post rule changed
|
||||
applicationContext.publishEvent(new PermalinkRuleChangedEvent(this,
|
||||
DefaultTemplateEnum.POST,
|
||||
newRules.getPost()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static boolean changePostPatternPrefixIfNecessary(String oldArchivePrefix,
|
||||
SystemSetting.ThemeRouteRules newRules) {
|
||||
if (StringUtils.isBlank(oldArchivePrefix)
|
||||
|| StringUtils.isBlank(newRules.getPost())) {
|
||||
return false;
|
||||
}
|
||||
String newArchivesPrefix = newRules.getArchives();
|
||||
if (StringUtils.equals(oldArchivePrefix, newArchivesPrefix)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String oldPrefix = StringUtils.removeStart(oldArchivePrefix, "/");
|
||||
String postPattern = StringUtils.removeStart(newRules.getPost(), "/");
|
||||
|
||||
if (postPattern.startsWith(oldPrefix)) {
|
||||
String postPatternToUpdate = PathUtils.combinePath(newArchivesPrefix,
|
||||
StringUtils.removeStart(postPattern, oldPrefix));
|
||||
newRules.setPost(postPatternToUpdate);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
String oldArchivesPrefix = StringUtils.removeStart(oldRules.getArchives(), "/");
|
||||
|
||||
String postPattern = StringUtils.removeStart(newRules.getPost(), "/");
|
||||
String newArchivesPrefix = newRules.getArchives();
|
||||
if (postPattern.startsWith(oldArchivesPrefix)) {
|
||||
String postPatternToUpdate = PathUtils.combinePath(newArchivesPrefix,
|
||||
StringUtils.removeStart(postPattern, oldArchivesPrefix));
|
||||
newRules.setPost(postPatternToUpdate);
|
||||
private SystemSetting.ThemeRouteRules getOldRouteRulesFromAnno(ConfigMap configMap) {
|
||||
Map<String, String> annotations = getAnnotationsSafe(configMap);
|
||||
String oldRulesJson = annotations.get(OLD_THEME_ROUTE_RULES);
|
||||
|
||||
// post rule changed
|
||||
applicationContext.publishEvent(new PermalinkRuleChangedEvent(this,
|
||||
DefaultTemplateEnum.POST, postPatternToUpdate));
|
||||
return true;
|
||||
// old rules is empty, means this is the first time to update theme route rules
|
||||
if (oldRulesJson == null) {
|
||||
oldRulesJson = "{}";
|
||||
}
|
||||
|
||||
// diff old rules and new rules
|
||||
return JsonUtils.jsonToObject(oldRulesJson, SystemSetting.ThemeRouteRules.class);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private Map<String, String> getAnnotationsSafe(ConfigMap configMap) {
|
||||
Map<String, String> annotations = configMap.getMetadata().getAnnotations();
|
||||
if (annotations == null) {
|
||||
annotations = new HashMap<>();
|
||||
configMap.getMetadata().setAnnotations(annotations);
|
||||
private SystemSetting.ThemeRouteRules getRouteRules(ConfigMap configMap) {
|
||||
Map<String, String> data = configMap.getData();
|
||||
// get new rules and replace old rules to new rules
|
||||
return JsonUtils.jsonToObject(data.get(SystemSetting.ThemeRouteRules.GROUP),
|
||||
SystemSetting.ThemeRouteRules.class);
|
||||
}
|
||||
|
||||
private Map<String, String> getAnnotationsSafe(ConfigMap configMap) {
|
||||
Map<String, String> annotations = configMap.getMetadata().getAnnotations();
|
||||
if (annotations == null) {
|
||||
annotations = new HashMap<>();
|
||||
configMap.getMetadata().setAnnotations(annotations);
|
||||
}
|
||||
return annotations;
|
||||
}
|
||||
return annotations;
|
||||
}
|
||||
|
||||
public boolean isSystemSetting(String name) {
|
||||
|
|
|
@ -36,9 +36,13 @@ public class SystemConfigurableEnvironmentFetcher {
|
|||
|
||||
@NonNull
|
||||
private Mono<Map<String, String>> getValuesInternal() {
|
||||
return extensionClient.fetch(ConfigMap.class, SystemSetting.SYSTEM_CONFIG)
|
||||
return getConfigMap()
|
||||
.filter(configMap -> configMap.getData() != null)
|
||||
.map(ConfigMap::getData)
|
||||
.defaultIfEmpty(Map.of());
|
||||
}
|
||||
|
||||
public Mono<ConfigMap> getConfigMap() {
|
||||
return extensionClient.fetch(ConfigMap.class, SystemSetting.SYSTEM_CONFIG);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,4 +38,44 @@ public class SystemSetting {
|
|||
|
||||
private String footer;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Basic {
|
||||
public static final String GROUP = "basic";
|
||||
String title;
|
||||
String subtitle;
|
||||
String logo;
|
||||
String favicon;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class User {
|
||||
public static final String GROUP = "user";
|
||||
Boolean allowRegistration;
|
||||
String defaultRole;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Post {
|
||||
public static final String GROUP = "post";
|
||||
String sortOrder;
|
||||
Integer pageSize;
|
||||
Boolean review;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Seo {
|
||||
public static final String GROUP = "seo";
|
||||
Boolean blockSpiders;
|
||||
String keywords;
|
||||
String description;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class Comment {
|
||||
public static final String GROUP = "comment";
|
||||
Boolean enable;
|
||||
Boolean requireReviewForNew;
|
||||
Boolean systemUserOnly;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,19 @@
|
|||
package run.halo.app.theme;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.reactive.result.view.View;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.thymeleaf.spring6.view.reactive.ThymeleafReactiveView;
|
||||
import org.thymeleaf.spring6.view.reactive.ThymeleafReactiveViewResolver;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
import run.halo.app.theme.finders.FinderRegistry;
|
||||
|
@ -49,9 +54,51 @@ public class HaloViewResolver extends ThymeleafReactiveViewResolver {
|
|||
// calculate the engine before rendering
|
||||
setTemplateEngine(engineManager.getTemplateEngine(theme));
|
||||
return super.render(model, contentType, exchange)
|
||||
.subscribeOn(Schedulers.boundedElastic());
|
||||
.subscribeOn(Schedulers.boundedElastic());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
protected Mono<Map<String, Object>> getModelAttributes(Map<String, ?> model,
|
||||
@NonNull ServerWebExchange exchange) {
|
||||
Mono<Map<String, Object>> contextBasedStaticVariables =
|
||||
getContextBasedStaticVariables(exchange);
|
||||
Mono<Map<String, Object>> modelAttributes = super.getModelAttributes(model, exchange);
|
||||
return Flux.merge(modelAttributes, contextBasedStaticVariables)
|
||||
.collectList()
|
||||
.map(modelMapList -> {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
modelMapList.forEach(result::putAll);
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Mono<Map<String, Object>> getContextBasedStaticVariables(
|
||||
ServerWebExchange exchange) {
|
||||
ApplicationContext applicationContext = obtainApplicationContext();
|
||||
|
||||
return Mono.just(new HashMap<String, Object>())
|
||||
.flatMap(staticVariables -> {
|
||||
List<Mono<Map<String, Object>>> monoList = applicationContext.getBeansOfType(
|
||||
ViewContextBasedVariablesAcquirer.class)
|
||||
.values()
|
||||
.stream()
|
||||
.map(acquirer -> acquirer.acquire(exchange))
|
||||
.toList();
|
||||
return Flux.merge(monoList)
|
||||
.collectList()
|
||||
.map(modelList -> {
|
||||
Map<String, Object> mergedModel = new HashMap<>();
|
||||
modelList.forEach(mergedModel::putAll);
|
||||
return mergedModel;
|
||||
})
|
||||
.map(mergedModel -> {
|
||||
staticVariables.putAll(mergedModel);
|
||||
return staticVariables;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
package run.halo.app.theme;
|
||||
|
||||
import java.util.Map;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
|
||||
import run.halo.app.theme.finders.vo.SiteSettingVo;
|
||||
|
||||
/**
|
||||
* Site setting variables acquirer.
|
||||
*
|
||||
* @author guqing
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Component
|
||||
public class SiteSettingVariablesAcquirer implements ViewContextBasedVariablesAcquirer {
|
||||
|
||||
private final SystemConfigurableEnvironmentFetcher environmentFetcher;
|
||||
|
||||
public SiteSettingVariablesAcquirer(SystemConfigurableEnvironmentFetcher environmentFetcher) {
|
||||
this.environmentFetcher = environmentFetcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Map<String, Object>> acquire(ServerWebExchange exchange) {
|
||||
return environmentFetcher.getConfigMap()
|
||||
.filter(configMap -> configMap.getData() != null)
|
||||
.map(configMap -> {
|
||||
SiteSettingVo siteSettingVo = SiteSettingVo.from(configMap);
|
||||
return Map.of("site", siteSettingVo);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package run.halo.app.theme;
|
||||
|
||||
import java.util.Map;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
import run.halo.app.theme.finders.ThemeFinder;
|
||||
import run.halo.app.theme.finders.vo.ThemeVo;
|
||||
|
||||
/**
|
||||
* Theme context based variables acquirer.
|
||||
*
|
||||
* @author guqing
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Component
|
||||
public class ThemeContextBasedVariablesAcquirer implements ViewContextBasedVariablesAcquirer {
|
||||
private final ThemeFinder themeFinder;
|
||||
private final ThemeResolver themeResolver;
|
||||
|
||||
public ThemeContextBasedVariablesAcquirer(ThemeFinder themeFinder,
|
||||
ThemeResolver themeResolver) {
|
||||
this.themeFinder = themeFinder;
|
||||
this.themeResolver = themeResolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Map<String, Object>> acquire(ServerWebExchange exchange) {
|
||||
return themeResolver.getTheme(exchange.getRequest())
|
||||
.publishOn(Schedulers.boundedElastic())
|
||||
.map(themeContext -> {
|
||||
String name = themeContext.getName();
|
||||
ThemeVo themeVo = themeFinder.getByName(name);
|
||||
if (themeVo == null) {
|
||||
return Map.of();
|
||||
}
|
||||
return Map.of("theme", themeVo);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package run.halo.app.theme;
|
||||
|
||||
import java.util.Map;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface ViewContextBasedVariablesAcquirer {
|
||||
|
||||
Mono<Map<String, Object>> acquire(ServerWebExchange exchange);
|
||||
}
|
|
@ -13,7 +13,7 @@ import org.thymeleaf.standard.StandardDialect;
|
|||
* @since 2.0.0
|
||||
*/
|
||||
public class HaloProcessorDialect extends AbstractProcessorDialect {
|
||||
private static final String DIALECT_NAME = "Halo Theme Dialect";
|
||||
private static final String DIALECT_NAME = "haloThemeProcessorDialect";
|
||||
|
||||
public HaloProcessorDialect() {
|
||||
// We will set this dialect the same "dialect processor" precedence as
|
||||
|
@ -27,6 +27,7 @@ public class HaloProcessorDialect extends AbstractProcessorDialect {
|
|||
// add more processors
|
||||
processors.add(new GlobalHeadInjectionProcessor(dialectPrefix));
|
||||
processors.add(new TemplateFooterElementTagProcessor(dialectPrefix));
|
||||
processors.add(new JsonNodePropertyAccessorBoundariesProcessor());
|
||||
return processors;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
package run.halo.app.theme.dialect;
|
||||
|
||||
import org.springframework.integration.json.JsonPropertyAccessor;
|
||||
import org.thymeleaf.context.ITemplateContext;
|
||||
import org.thymeleaf.model.ITemplateEnd;
|
||||
import org.thymeleaf.model.ITemplateStart;
|
||||
import org.thymeleaf.processor.templateboundaries.AbstractTemplateBoundariesProcessor;
|
||||
import org.thymeleaf.processor.templateboundaries.ITemplateBoundariesStructureHandler;
|
||||
import org.thymeleaf.spring6.expression.ThymeleafEvaluationContext;
|
||||
import org.thymeleaf.standard.StandardDialect;
|
||||
import org.thymeleaf.templatemode.TemplateMode;
|
||||
|
||||
/**
|
||||
* A template boundaries processor for add {@link JsonPropertyAccessor} to
|
||||
* {@link ThymeleafEvaluationContext}.
|
||||
*
|
||||
* @author guqing
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class JsonNodePropertyAccessorBoundariesProcessor
|
||||
extends AbstractTemplateBoundariesProcessor {
|
||||
private static final int PRECEDENCE = StandardDialect.PROCESSOR_PRECEDENCE;
|
||||
private static final JsonPropertyAccessor JSON_PROPERTY_ACCESSOR = new JsonPropertyAccessor();
|
||||
|
||||
public JsonNodePropertyAccessorBoundariesProcessor() {
|
||||
super(TemplateMode.HTML, PRECEDENCE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doProcessTemplateStart(ITemplateContext context, ITemplateStart templateStart,
|
||||
ITemplateBoundariesStructureHandler structureHandler) {
|
||||
ThymeleafEvaluationContext evaluationContext =
|
||||
(ThymeleafEvaluationContext) context.getVariable(
|
||||
ThymeleafEvaluationContext.THYMELEAF_EVALUATION_CONTEXT_CONTEXT_VARIABLE_NAME);
|
||||
if (evaluationContext != null) {
|
||||
evaluationContext.addPropertyAccessor(JSON_PROPERTY_ACCESSOR);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doProcessTemplateEnd(ITemplateContext context, ITemplateEnd templateEnd,
|
||||
ITemplateBoundariesStructureHandler structureHandler) {
|
||||
// nothing to do
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package run.halo.app.theme.finders;
|
||||
|
||||
import run.halo.app.theme.finders.vo.ThemeVo;
|
||||
|
||||
/**
|
||||
* A finder for theme.
|
||||
*
|
||||
* @author guqing
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public interface ThemeFinder {
|
||||
|
||||
ThemeVo activation();
|
||||
|
||||
ThemeVo getByName(String themeName);
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
package run.halo.app.theme.finders.impl;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.core.extension.Theme;
|
||||
import run.halo.app.extension.ConfigMap;
|
||||
import run.halo.app.extension.ReactiveExtensionClient;
|
||||
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
|
||||
import run.halo.app.infra.SystemSetting;
|
||||
import run.halo.app.infra.utils.JsonUtils;
|
||||
import run.halo.app.theme.finders.Finder;
|
||||
import run.halo.app.theme.finders.ThemeFinder;
|
||||
import run.halo.app.theme.finders.vo.ThemeVo;
|
||||
|
||||
/**
|
||||
* A default implementation for {@link ThemeFinder}.
|
||||
*
|
||||
* @author guqing
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Finder("themeFinder")
|
||||
public class ThemeFinderImpl implements ThemeFinder {
|
||||
|
||||
private final ReactiveExtensionClient client;
|
||||
private final SystemConfigurableEnvironmentFetcher environmentFetcher;
|
||||
|
||||
public ThemeFinderImpl(ReactiveExtensionClient client,
|
||||
SystemConfigurableEnvironmentFetcher environmentFetcher) {
|
||||
this.client = client;
|
||||
this.environmentFetcher = environmentFetcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThemeVo activation() {
|
||||
return environmentFetcher.fetch(SystemSetting.Theme.GROUP, SystemSetting.Theme.class)
|
||||
.map(SystemSetting.Theme::getActive)
|
||||
.flatMap(themeName -> client.fetch(Theme.class, themeName))
|
||||
.flatMap(theme -> themeWithConfig(ThemeVo.from(theme)))
|
||||
.block();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThemeVo getByName(String themeName) {
|
||||
return client.fetch(Theme.class, themeName)
|
||||
.flatMap(theme -> themeWithConfig(ThemeVo.from(theme)))
|
||||
.block();
|
||||
}
|
||||
|
||||
private Mono<ThemeVo> themeWithConfig(ThemeVo themeVo) {
|
||||
if (StringUtils.isBlank(themeVo.getSpec().getConfigMapName())) {
|
||||
return Mono.just(themeVo);
|
||||
}
|
||||
return client.fetch(ConfigMap.class, themeVo.getSpec().getConfigMapName())
|
||||
.map(configMap -> {
|
||||
Map<String, JsonNode> config = new HashMap<>();
|
||||
configMap.getData().forEach((k, v) -> {
|
||||
JsonNode jsonNode = JsonUtils.jsonToObject(v, JsonNode.class);
|
||||
config.put(k, jsonNode);
|
||||
});
|
||||
JsonNode configJson = JsonUtils.mapToObject(config, JsonNode.class);
|
||||
return themeVo.withConfig(configJson);
|
||||
})
|
||||
.switchIfEmpty(Mono.just(themeVo));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
package run.halo.app.theme.finders.vo;
|
||||
|
||||
import java.util.Map;
|
||||
import lombok.Builder;
|
||||
import lombok.Value;
|
||||
import org.springframework.util.Assert;
|
||||
import run.halo.app.extension.ConfigMap;
|
||||
import run.halo.app.infra.SystemSetting;
|
||||
import run.halo.app.infra.utils.JsonUtils;
|
||||
|
||||
/**
|
||||
* Site setting value object for theme.
|
||||
*
|
||||
* @author guqing
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Value
|
||||
@Builder
|
||||
public class SiteSettingVo {
|
||||
|
||||
String title;
|
||||
|
||||
String subtitle;
|
||||
|
||||
String logo;
|
||||
|
||||
String favicon;
|
||||
|
||||
Boolean allowRegistration;
|
||||
|
||||
PostSetting post;
|
||||
|
||||
SeoSetting seo;
|
||||
|
||||
CommentSetting comment;
|
||||
|
||||
/**
|
||||
* Convert to system {@link ConfigMap} to {@link SiteSettingVo}.
|
||||
*
|
||||
* @param configMap config map named system
|
||||
* @return site setting value object
|
||||
*/
|
||||
public static SiteSettingVo from(ConfigMap configMap) {
|
||||
Assert.notNull(configMap, "The configMap must not be null.");
|
||||
Map<String, String> data = configMap.getData();
|
||||
if (data == null) {
|
||||
return builder().build();
|
||||
}
|
||||
SystemSetting.Basic basicSetting =
|
||||
toObject(data.get(SystemSetting.Basic.GROUP), SystemSetting.Basic.class);
|
||||
|
||||
SystemSetting.User userSetting =
|
||||
toObject(data.get(SystemSetting.User.GROUP), SystemSetting.User.class);
|
||||
|
||||
SystemSetting.Post postSetting =
|
||||
toObject(data.get(SystemSetting.Post.GROUP), SystemSetting.Post.class);
|
||||
|
||||
SystemSetting.Seo seoSetting =
|
||||
toObject(data.get(SystemSetting.Seo.GROUP), SystemSetting.Seo.class);
|
||||
|
||||
SystemSetting.Comment commentSetting = toObject(data.get(SystemSetting.Comment.GROUP),
|
||||
SystemSetting.Comment.class);
|
||||
return builder()
|
||||
.title(basicSetting.getTitle())
|
||||
.subtitle(basicSetting.getSubtitle())
|
||||
.logo(basicSetting.getLogo())
|
||||
.favicon(basicSetting.getFavicon())
|
||||
.allowRegistration(userSetting.getAllowRegistration())
|
||||
.post(PostSetting.builder()
|
||||
.sortOrder(postSetting.getSortOrder())
|
||||
.pageSize(postSetting.getPageSize())
|
||||
.build())
|
||||
.seo(SeoSetting.builder()
|
||||
.blockSpiders(seoSetting.getBlockSpiders())
|
||||
.keywords(seoSetting.getKeywords())
|
||||
.description(seoSetting.getDescription())
|
||||
.build())
|
||||
.comment(CommentSetting.builder()
|
||||
.enable(commentSetting.getEnable())
|
||||
.requireReviewForNew(commentSetting.getRequireReviewForNew())
|
||||
.systemUserOnly(commentSetting.getSystemUserOnly())
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
private static <T> T toObject(String json, Class<T> type) {
|
||||
if (json == null) {
|
||||
// empty object
|
||||
json = "{}";
|
||||
}
|
||||
return JsonUtils.jsonToObject(json, type);
|
||||
}
|
||||
|
||||
@Value
|
||||
@Builder
|
||||
public static class PostSetting {
|
||||
String sortOrder;
|
||||
|
||||
Integer pageSize;
|
||||
}
|
||||
|
||||
@Value
|
||||
@Builder
|
||||
public static class SeoSetting {
|
||||
Boolean blockSpiders;
|
||||
|
||||
String keywords;
|
||||
|
||||
String description;
|
||||
}
|
||||
|
||||
@Value
|
||||
@Builder
|
||||
public static class CommentSetting {
|
||||
Boolean enable;
|
||||
|
||||
Boolean systemUserOnly;
|
||||
|
||||
Boolean requireReviewForNew;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package run.halo.app.theme.finders.vo;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import lombok.Builder;
|
||||
import lombok.ToString;
|
||||
import lombok.Value;
|
||||
import lombok.With;
|
||||
import run.halo.app.core.extension.Theme;
|
||||
import run.halo.app.extension.MetadataOperator;
|
||||
|
||||
/**
|
||||
* A value object for {@link Theme}.
|
||||
*
|
||||
* @author guqing
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Value
|
||||
@Builder
|
||||
@ToString
|
||||
public class ThemeVo {
|
||||
|
||||
MetadataOperator metadata;
|
||||
|
||||
Theme.ThemeSpec spec;
|
||||
|
||||
@With
|
||||
JsonNode config;
|
||||
|
||||
/**
|
||||
* Convert {@link Theme} to {@link ThemeVo}.
|
||||
*
|
||||
* @param theme theme extension
|
||||
* @return theme value object
|
||||
*/
|
||||
public static ThemeVo from(Theme theme) {
|
||||
return ThemeVo.builder()
|
||||
.metadata(theme.getMetadata())
|
||||
.spec(theme.getSpec())
|
||||
.config(null)
|
||||
.build();
|
||||
}
|
||||
}
|
|
@ -91,7 +91,7 @@ spec:
|
|||
label: SEO 设置
|
||||
formSchema:
|
||||
- $formkit: checkbox
|
||||
name: blockSpides
|
||||
name: blockSpiders
|
||||
label: "屏蔽搜索引擎"
|
||||
value: false
|
||||
- $formkit: textarea
|
||||
|
|
|
@ -1,15 +1,30 @@
|
|||
package run.halo.app.core.extension.reconciler;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import run.halo.app.extension.ConfigMap;
|
||||
import run.halo.app.extension.ExtensionClient;
|
||||
import run.halo.app.extension.Metadata;
|
||||
import run.halo.app.extension.controller.Reconciler;
|
||||
import run.halo.app.infra.SystemSetting;
|
||||
import run.halo.app.infra.utils.JsonUtils;
|
||||
|
||||
/**
|
||||
* Tests for {@link SystemSettingReconciler}.
|
||||
|
@ -34,16 +49,133 @@ class SystemSettingReconcilerTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
void changePostPatternPrefixIfNecessary() {
|
||||
SystemSetting.ThemeRouteRules oldRouteRules = new SystemSetting.ThemeRouteRules();
|
||||
oldRouteRules.setPost("/archives/{slug}");
|
||||
oldRouteRules.setArchives("archives");
|
||||
void reconcileArchivesRouteRule() {
|
||||
ConfigMap configMap = systemConfigMapForRouteRule(rules -> {
|
||||
rules.setArchives("archives-new");
|
||||
return rules;
|
||||
});
|
||||
when(client.fetch(eq(ConfigMap.class), eq(SystemSetting.SYSTEM_CONFIG)))
|
||||
.thenReturn(Optional.of(configMap));
|
||||
systemSettingReconciler.reconcile(new Reconciler.Request(SystemSetting.SYSTEM_CONFIG));
|
||||
ArgumentCaptor<ConfigMap> captor = ArgumentCaptor.forClass(ConfigMap.class);
|
||||
verify(client, times(2)).update(captor.capture());
|
||||
|
||||
List<ConfigMap> allValues = captor.getAllValues();
|
||||
ConfigMap updatedConfigMap = allValues.get(1);
|
||||
assertThat(rulesFrom(updatedConfigMap).getArchives()).isEqualTo("archives-new");
|
||||
assertThat(rulesFrom(updatedConfigMap).getPost()).isEqualTo("/archives-new/{slug}");
|
||||
|
||||
assertThat(oldRulesFromAnno(updatedConfigMap).getArchives()).isEqualTo("archives-new");
|
||||
assertThat(oldRulesFromAnno(updatedConfigMap).getPost()).isEqualTo("/archives-new/{slug}");
|
||||
|
||||
// archives and post
|
||||
verify(applicationContext, times(2)).publishEvent(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void reconcileTagsRule() {
|
||||
ConfigMap configMap = systemConfigMapForRouteRule(rules -> {
|
||||
rules.setTags("tags-new");
|
||||
return rules;
|
||||
});
|
||||
when(client.fetch(eq(ConfigMap.class), eq(SystemSetting.SYSTEM_CONFIG)))
|
||||
.thenReturn(Optional.of(configMap));
|
||||
systemSettingReconciler.reconcile(new Reconciler.Request(SystemSetting.SYSTEM_CONFIG));
|
||||
ArgumentCaptor<ConfigMap> captor = ArgumentCaptor.forClass(ConfigMap.class);
|
||||
verify(client, times(2)).update(captor.capture());
|
||||
|
||||
List<ConfigMap> allValues = captor.getAllValues();
|
||||
ConfigMap updatedConfigMap = allValues.get(1);
|
||||
assertThat(rulesFrom(updatedConfigMap).getTags()).isEqualTo("tags-new");
|
||||
|
||||
assertThat(oldRulesFromAnno(updatedConfigMap).getTags()).isEqualTo("tags-new");
|
||||
|
||||
verify(applicationContext, times(1)).publishEvent(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void reconcileCategoriesRule() {
|
||||
ConfigMap configMap = systemConfigMapForRouteRule(rules -> {
|
||||
rules.setCategories("categories-new");
|
||||
return rules;
|
||||
});
|
||||
when(client.fetch(eq(ConfigMap.class), eq(SystemSetting.SYSTEM_CONFIG)))
|
||||
.thenReturn(Optional.of(configMap));
|
||||
systemSettingReconciler.reconcile(new Reconciler.Request(SystemSetting.SYSTEM_CONFIG));
|
||||
ArgumentCaptor<ConfigMap> captor = ArgumentCaptor.forClass(ConfigMap.class);
|
||||
verify(client, times(2)).update(captor.capture());
|
||||
|
||||
List<ConfigMap> allValues = captor.getAllValues();
|
||||
ConfigMap updatedConfigMap = allValues.get(1);
|
||||
assertThat(rulesFrom(updatedConfigMap).getCategories()).isEqualTo("categories-new");
|
||||
|
||||
assertThat(oldRulesFromAnno(updatedConfigMap).getCategories()).isEqualTo("categories-new");
|
||||
|
||||
verify(applicationContext, times(1)).publishEvent(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void reconcilePostRule() {
|
||||
ConfigMap configMap = systemConfigMapForRouteRule(rules -> {
|
||||
rules.setPost("/post-new/{slug}");
|
||||
return rules;
|
||||
});
|
||||
when(client.fetch(eq(ConfigMap.class), eq(SystemSetting.SYSTEM_CONFIG)))
|
||||
.thenReturn(Optional.of(configMap));
|
||||
systemSettingReconciler.reconcile(new Reconciler.Request(SystemSetting.SYSTEM_CONFIG));
|
||||
ArgumentCaptor<ConfigMap> captor = ArgumentCaptor.forClass(ConfigMap.class);
|
||||
verify(client, times(2)).update(captor.capture());
|
||||
|
||||
List<ConfigMap> allValues = captor.getAllValues();
|
||||
ConfigMap updatedConfigMap = allValues.get(1);
|
||||
assertThat(rulesFrom(updatedConfigMap).getPost()).isEqualTo("/post-new/{slug}");
|
||||
|
||||
assertThat(oldRulesFromAnno(updatedConfigMap).getPost()).isEqualTo("/post-new/{slug}");
|
||||
|
||||
verify(applicationContext, times(1)).publishEvent(any());
|
||||
}
|
||||
|
||||
private SystemSetting.ThemeRouteRules rulesFrom(ConfigMap configMap) {
|
||||
String s = configMap.getData().get(SystemSetting.ThemeRouteRules.GROUP);
|
||||
return JsonUtils.jsonToObject(s, SystemSetting.ThemeRouteRules.class);
|
||||
}
|
||||
|
||||
private SystemSetting.ThemeRouteRules oldRulesFromAnno(ConfigMap configMap) {
|
||||
Map<String, String> annotations = configMap.getMetadata().getAnnotations();
|
||||
String s = annotations.get(SystemSettingReconciler.OLD_THEME_ROUTE_RULES);
|
||||
return JsonUtils.jsonToObject(s, SystemSetting.ThemeRouteRules.class);
|
||||
}
|
||||
|
||||
private ConfigMap systemConfigMapForRouteRule(
|
||||
Function<SystemSetting.ThemeRouteRules, SystemSetting.ThemeRouteRules> function) {
|
||||
ConfigMap configMap = new ConfigMap();
|
||||
Metadata metadata = new Metadata();
|
||||
metadata.setName(SystemSetting.SYSTEM_CONFIG);
|
||||
configMap.setMetadata(metadata);
|
||||
|
||||
SystemSetting.ThemeRouteRules themeRouteRules = new SystemSetting.ThemeRouteRules();
|
||||
themeRouteRules.setArchives("archives");
|
||||
themeRouteRules.setTags("tags");
|
||||
themeRouteRules.setCategories("categories");
|
||||
themeRouteRules.setPost("/archives/{slug}");
|
||||
Map<String, String> annotations = new HashMap<>();
|
||||
annotations.put(SystemSettingReconciler.OLD_THEME_ROUTE_RULES,
|
||||
JsonUtils.objectToJson(themeRouteRules));
|
||||
metadata.setAnnotations(annotations);
|
||||
|
||||
SystemSetting.ThemeRouteRules newRules = function.apply(themeRouteRules);
|
||||
configMap.putDataItem(SystemSetting.ThemeRouteRules.GROUP,
|
||||
JsonUtils.objectToJson(newRules));
|
||||
return configMap;
|
||||
}
|
||||
|
||||
@Test
|
||||
void changePostPatternPrefixIfNecessary() {
|
||||
SystemSetting.ThemeRouteRules newRouteRules = new SystemSetting.ThemeRouteRules();
|
||||
newRouteRules.setPost("/archives/{slug}");
|
||||
newRouteRules.setArchives("new");
|
||||
boolean result = systemSettingReconciler.changePostPatternPrefixIfNecessary(oldRouteRules,
|
||||
newRouteRules);
|
||||
boolean result = SystemSettingReconciler.RouteRuleReconciler
|
||||
.changePostPatternPrefixIfNecessary("archives", newRouteRules);
|
||||
assertThat(result).isTrue();
|
||||
|
||||
assertThat(newRouteRules.getPost()).isEqualTo("/new/{slug}");
|
||||
|
|
|
@ -13,7 +13,6 @@ import java.time.ZoneId;
|
|||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.TimeZone;
|
||||
import java.util.function.Function;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
@ -53,8 +52,6 @@ class ThemeJava8TimeDialectIntegrationTest {
|
|||
|
||||
private URL defaultThemeUrl;
|
||||
|
||||
Function<ServerHttpRequest, ThemeContext> themeContextFunction;
|
||||
|
||||
@Autowired
|
||||
private WebTestClient webTestClient;
|
||||
|
||||
|
@ -63,8 +60,8 @@ class ThemeJava8TimeDialectIntegrationTest {
|
|||
@BeforeEach
|
||||
void setUp() throws FileNotFoundException {
|
||||
defaultThemeUrl = ResourceUtils.getURL("classpath:themes/default");
|
||||
when(themeResolver.getTheme(any(ServerHttpRequest.class))).thenReturn(
|
||||
Mono.just(createDefaultContext()));
|
||||
when(themeResolver.getTheme(any(ServerHttpRequest.class)))
|
||||
.thenReturn(Mono.just(createDefaultContext()));
|
||||
defaultTimeZone = TimeZone.getDefault();
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue