From 5b9d4e6a36efbc9d244a02757daedc280c5f05bc Mon Sep 17 00:00:00 2001 From: guqing <1484563614@qq.com> Date: Thu, 5 Dec 2024 17:38:27 +0800 Subject: [PATCH] feat: support displaying a list of RSS sources in plugin details --- .../run/halo/feed/FeedPluginEndpoint.java | 118 +++++++++++------- .../resources/extensions/roleTemplates.yaml | 14 +++ 2 files changed, 86 insertions(+), 46 deletions(-) create mode 100644 app/src/main/resources/extensions/roleTemplates.yaml diff --git a/app/src/main/java/run/halo/feed/FeedPluginEndpoint.java b/app/src/main/java/run/halo/feed/FeedPluginEndpoint.java index ce1ada9..c1f6689 100644 --- a/app/src/main/java/run/halo/feed/FeedPluginEndpoint.java +++ b/app/src/main/java/run/halo/feed/FeedPluginEndpoint.java @@ -1,9 +1,7 @@ package run.halo.feed; -import static org.springframework.web.reactive.function.server.RequestPredicates.accept; -import static org.springframework.web.reactive.function.server.RequestPredicates.path; - import lombok.AllArgsConstructor; +import lombok.Builder; import org.apache.commons.lang3.StringUtils; import org.springframework.context.annotation.Bean; import org.springframework.http.HttpMethod; @@ -12,36 +10,64 @@ import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher; import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers; import org.springframework.stereotype.Component; -import org.springframework.web.reactive.function.server.HandlerFunction; -import org.springframework.web.reactive.function.server.RequestPredicate; -import org.springframework.web.reactive.function.server.RouterFunction; -import org.springframework.web.reactive.function.server.RouterFunctions; -import org.springframework.web.reactive.function.server.ServerRequest; -import org.springframework.web.reactive.function.server.ServerResponse; +import org.springframework.web.reactive.function.server.*; import reactor.core.publisher.Mono; +import run.halo.app.infra.ExternalUrlSupplier; import run.halo.app.plugin.extensionpoint.ExtensionGetter; import run.halo.feed.provider.PostRssProvider; +import static org.springframework.web.reactive.function.server.RequestPredicates.accept; +import static org.springframework.web.reactive.function.server.RequestPredicates.path; + @Component @AllArgsConstructor public class FeedPluginEndpoint { static final RequestPredicate ACCEPT_PREDICATE = accept( - MediaType.TEXT_XML, - MediaType.APPLICATION_RSS_XML + MediaType.TEXT_XML, + MediaType.APPLICATION_RSS_XML ); private final PostRssProvider postRssProvider; private final ExtensionGetter extensionGetter; private final RssCacheManager rssCacheManager; + private final ExternalUrlSupplier externalUrlSupplier; @Bean RouterFunction rssRouterFunction() { return RouterFunctions.route() - .GET(path("/feed.xml").or(path("/rss.xml")).and(ACCEPT_PREDICATE), - request -> rssCacheManager.get("/rss.xml", postRssProvider.handler(request)) - .flatMap(this::buildResponse) - ) - .build(); + .GET(path("/feed.xml").or(path("/rss.xml")).and(ACCEPT_PREDICATE), + request -> rssCacheManager.get("/rss.xml", postRssProvider.handler(request)) + .flatMap(this::buildResponse) + ) + .build(); + } + + @Bean + RouterFunction rssSourcesListerRouter() { + return RouterFunctions.route() + .GET("/apis/api.feed.halo.run/v1alpha1/rss-sources", this::listRssSources) + .build(); + } + + private Mono listRssSources(ServerRequest request) { + var externalUrl = externalUrlSupplier.getURL(request.exchange().getRequest()).toString(); + return extensionGetter.getEnabledExtensions(RssRouteItem.class) + .concatMap(item -> { + var rssSource = RssSource.builder() + .displayName(item.displayName()) + .description(item.description()) + .example(item.example()); + return item.pathPattern() + .map(pattern -> buildPathPattern(pattern, item.namespace())) + .doOnNext(path -> rssSource.pattern(externalUrl + path)) + .then(Mono.fromSupplier(rssSource::build)); + }) + .collectList() + .flatMap(ServerResponse.ok()::bodyValue); + } + + @Builder + record RssSource(String displayName, String description, String pattern, String example) { } @Bean @@ -52,20 +78,20 @@ RouterFunction additionalRssRouter() { @NonNull public Mono> route(@NonNull ServerRequest request) { return pathMatcher.matches(request.exchange()) - .filter(ServerWebExchangeMatcher.MatchResult::isMatch) - .flatMap(matched -> handleRequest(request)); + .filter(ServerWebExchangeMatcher.MatchResult::isMatch) + .flatMap(matched -> handleRequest(request)); } private Mono> handleRequest(ServerRequest request) { return extensionGetter.getEnabledExtensions(RssRouteItem.class) - .concatMap(routeItem -> buildRequestPredicate(routeItem) - .map(requestPredicate -> new RouteItem(requestPredicate, - buildHandleFunction(routeItem)) + .concatMap(routeItem -> buildRequestPredicate(routeItem) + .map(requestPredicate -> new RouteItem(requestPredicate, + buildHandleFunction(routeItem)) + ) ) - ) - .filter(route -> route.requestPredicate.test(request)) - .next() - .map(RouteItem::handler); + .filter(route -> route.requestPredicate.test(request)) + .next() + .map(RouteItem::handler); } record RouteItem(RequestPredicate requestPredicate, @@ -74,39 +100,39 @@ record RouteItem(RequestPredicate requestPredicate, private HandlerFunction buildHandleFunction(RssRouteItem routeItem) { return request -> rssCacheManager.get(request.path(), routeItem.handler(request)) - .flatMap(item -> buildResponse(item)); + .flatMap(item -> buildResponse(item)); } private Mono buildRequestPredicate(RssRouteItem routeItem) { return routeItem.pathPattern() - .map(pathPattern -> path( - buildPathPattern(pathPattern, routeItem.namespace())) - .and(ACCEPT_PREDICATE) - ); + .map(pathPattern -> path( + buildPathPattern(pathPattern, routeItem.namespace())) + .and(ACCEPT_PREDICATE) + ); } + }; + } - private String buildPathPattern(String pathPattern, String namespace) { - var sb = new StringBuilder("/feed/"); + private String buildPathPattern(String pathPattern, String namespace) { + var sb = new StringBuilder("/feed/"); - if (StringUtils.isNotBlank(namespace)) { - sb.append(namespace); - if (!namespace.endsWith("/")) { - sb.append("/"); - } - } + if (StringUtils.isNotBlank(namespace)) { + sb.append(namespace); + if (!namespace.endsWith("/")) { + sb.append("/"); + } + } - if (pathPattern.startsWith("/")) { - pathPattern = pathPattern.substring(1); - } - sb.append(pathPattern); + if (pathPattern.startsWith("/")) { + pathPattern = pathPattern.substring(1); + } + sb.append(pathPattern); - return sb.toString(); - } - }; + return sb.toString(); } private Mono buildResponse(String xml) { return ServerResponse.ok().contentType(MediaType.TEXT_XML) - .bodyValue(xml); + .bodyValue(xml); } } diff --git a/app/src/main/resources/extensions/roleTemplates.yaml b/app/src/main/resources/extensions/roleTemplates.yaml new file mode 100644 index 0000000..7dd54e7 --- /dev/null +++ b/app/src/main/resources/extensions/roleTemplates.yaml @@ -0,0 +1,14 @@ +apiVersion: v1alpha1 +kind: Role +metadata: + name: template-feed-public-apis + labels: + halo.run/role-template: "true" + halo.run/hidden: "true" + rbac.authorization.halo.run/aggregate-to-anonymous: "true" + annotations: + rbac.authorization.halo.run/display-name: "获取订阅源列表" +rules: + - apiGroups: [ "api.feed.halo.run" ] + resources: [ "rss-sources" ] + verbs: [ "list" ]