diff --git a/build.gradle b/build.gradle index a11ae72bb..bc669e644 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,7 @@ plugins { id 'org.springframework.boot' version '3.0.2' id 'io.spring.dependency-management' version '1.1.0' + id "com.gorylenko.gradle-git-properties" version "2.3.2" id "checkstyle" id 'java' } diff --git a/src/main/java/run/halo/app/Application.java b/src/main/java/run/halo/app/Application.java index 582253fbd..6871d916b 100644 --- a/src/main/java/run/halo/app/Application.java +++ b/src/main/java/run/halo/app/Application.java @@ -1,8 +1,9 @@ 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.builder.SpringApplicationBuilder; +import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.scheduling.annotation.EnableScheduling; import run.halo.app.infra.properties.HaloProperties; @@ -22,7 +23,9 @@ import run.halo.app.infra.properties.HaloProperties; public class Application { public static void main(String[] args) { - SpringApplication.run(Application.class, args); + new SpringApplicationBuilder(Application.class) + .applicationStartup(new BufferingApplicationStartup(1024)) + .run(args); } } diff --git a/src/main/java/run/halo/app/actuator/GlobalInfoEndpoint.java b/src/main/java/run/halo/app/actuator/GlobalInfoEndpoint.java new file mode 100644 index 000000000..884400db7 --- /dev/null +++ b/src/main/java/run/halo/app/actuator/GlobalInfoEndpoint.java @@ -0,0 +1,87 @@ +package run.halo.app.actuator; + +import java.net.URI; +import java.util.Locale; +import java.util.TimeZone; +import lombok.Data; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; +import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpoint; +import org.springframework.stereotype.Component; +import run.halo.app.extension.ConfigMap; +import run.halo.app.infra.ExternalUrlSupplier; +import run.halo.app.infra.SystemConfigurableEnvironmentFetcher; +import run.halo.app.infra.SystemSetting; +import run.halo.app.infra.SystemSetting.Comment; +import run.halo.app.infra.SystemSetting.User; + +@WebEndpoint(id = "globalinfo") +@Component +public class GlobalInfoEndpoint { + + private final ObjectProvider systemConfigFetcher; + + private final ExternalUrlSupplier externalUrl; + + public GlobalInfoEndpoint( + ObjectProvider systemConfigFetcher, + ExternalUrlSupplier externalUrl) { + this.systemConfigFetcher = systemConfigFetcher; + this.externalUrl = externalUrl; + } + + @ReadOperation + public GlobalInfo globalInfo() { + final var info = new GlobalInfo(); + info.setExternalUrl(externalUrl.get()); + info.setLocale(Locale.getDefault()); + info.setTimeZone(TimeZone.getDefault()); + systemConfigFetcher.ifAvailable(fetcher -> fetcher.getConfigMapBlocking() + .ifPresent(configMap -> { + handleCommentSetting(info, configMap); + handleUserSetting(info, configMap); + })); + + return info; + } + + @Data + public static class GlobalInfo { + + private URI externalUrl; + + private TimeZone timeZone; + + private Locale locale; + + private boolean allowComments; + + private boolean allowAnonymousComments; + + private boolean allowRegistration; + + } + + private void handleCommentSetting(GlobalInfo info, ConfigMap configMap) { + var comment = SystemSetting.get(configMap, Comment.GROUP, Comment.class); + if (comment == null) { + info.setAllowComments(true); + info.setAllowAnonymousComments(true); + } else { + info.setAllowComments(comment.getEnable() != null && comment.getEnable()); + info.setAllowAnonymousComments( + comment.getSystemUserOnly() == null || !comment.getSystemUserOnly()); + } + } + + private void handleUserSetting(GlobalInfo info, ConfigMap configMap) { + var user = SystemSetting.get(configMap, User.GROUP, User.class); + if (user == null) { + info.setAllowRegistration(false); + } else { + info.setAllowRegistration( + user.getAllowRegistration() != null && user.getAllowRegistration()); + } + } + +} diff --git a/src/main/java/run/halo/app/config/WebServerSecurityConfig.java b/src/main/java/run/halo/app/config/WebServerSecurityConfig.java index c832f4c80..78507095e 100644 --- a/src/main/java/run/halo/app/config/WebServerSecurityConfig.java +++ b/src/main/java/run/halo/app/config/WebServerSecurityConfig.java @@ -51,7 +51,8 @@ public class WebServerSecurityConfig { ObjectProvider securityConfigurers, ServerSecurityContextRepository securityContextRepository) { - http.securityMatcher(pathMatchers("/api/**", "/apis/**", "/login", "/logout")) + http.securityMatcher( + pathMatchers("/api/**", "/apis/**", "/login", "/logout", "/actuator/**")) .authorizeExchange().anyExchange() .access(new RequestInfoAuthorizationManager(roleService)).and() .anonymous(spec -> { diff --git a/src/main/java/run/halo/app/infra/SystemSetting.java b/src/main/java/run/halo/app/infra/SystemSetting.java index bc0c76a9c..d2e186dd2 100644 --- a/src/main/java/run/halo/app/infra/SystemSetting.java +++ b/src/main/java/run/halo/app/infra/SystemSetting.java @@ -3,6 +3,9 @@ package run.halo.app.infra; import java.util.LinkedHashMap; import java.util.Set; import lombok.Data; +import org.springframework.boot.convert.ApplicationConversionService; +import run.halo.app.extension.ConfigMap; +import run.halo.app.infra.utils.JsonUtils; /** * TODO Optimization value acquisition. @@ -102,4 +105,16 @@ public class SystemSetting { } + public static T get(ConfigMap configMap, String key, Class type) { + var data = configMap.getData(); + var valueString = data.get(key); + if (valueString == null) { + return null; + } + var conversionService = ApplicationConversionService.getSharedInstance(); + if (conversionService.canConvert(String.class, type)) { + return conversionService.convert(valueString, type); + } + return JsonUtils.jsonToObject(valueString, type); + } } diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 0e176e69f..49a8b7e14 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -50,3 +50,18 @@ logging: max-file-size: 10MB total-size-cap: 1GB max-history: 0 + +management: + endpoints: + web: + exposure: + include: ["health", "info", "startup", "globalinfo"] + endpoint: + health: + probes: + enabled: true + info: + java: + enabled: true + os: + enabled: true diff --git a/src/main/resources/extensions/role-template-anonymous.yaml b/src/main/resources/extensions/role-template-anonymous.yaml index 8f2b08504..67682b106 100644 --- a/src/main/resources/extensions/role-template-anonymous.yaml +++ b/src/main/resources/extensions/role-template-anonymous.yaml @@ -17,3 +17,5 @@ rules: verbs: [ "*" ] - nonResourceURLs: [ "/apis/api.halo.run/v1alpha1/trackers/*" ] verbs: [ "create" ] + - nonResourceURLs: [ "/actuator/globalinfo", "/actuator/health", "/actuator/health/*"] + verbs: [ "get" ] diff --git a/src/test/java/run/halo/app/infra/SystemSettingTest.java b/src/test/java/run/halo/app/infra/SystemSettingTest.java index c54ff65aa..46f163f6f 100644 --- a/src/test/java/run/halo/app/infra/SystemSettingTest.java +++ b/src/test/java/run/halo/app/infra/SystemSettingTest.java @@ -1,9 +1,14 @@ package run.halo.app.infra; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.HashMap; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import run.halo.app.extension.ConfigMap; +import run.halo.app.infra.SystemSetting.Comment; import run.halo.app.infra.SystemSetting.ExtensionPointEnabled; import run.halo.app.infra.utils.JsonUtils; @@ -27,4 +32,29 @@ class SystemSettingTest { } } + @Test + void shouldGetConfigFromJson() { + var configMap = new ConfigMap(); + configMap.putDataItem("comment", """ + {"enable": true} + """); + var comment = SystemSetting.get(configMap, Comment.GROUP, Comment.class); + assertTrue(comment.getEnable()); + } + + @Test + void shouldGetNullIfKeyNotExist() { + var configMap = new ConfigMap(); + configMap.setData(new HashMap<>()); + String fake = SystemSetting.get(configMap, "fake-key", String.class); + assertNull(fake); + } + + @Test + void shouldGetConfigViaConversionService() { + var configMap = new ConfigMap(); + configMap.putDataItem("int", "100"); + var integer = SystemSetting.get(configMap, "int", Integer.class); + assertEquals(100, integer); + } } \ No newline at end of file