Skip to content

Commit

Permalink
feat: support displaying a list of RSS sources in plugin details
Browse files Browse the repository at this point in the history
  • Loading branch information
guqing committed Dec 5, 2024
1 parent cff7c7e commit 5b9d4e6
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 46 deletions.
118 changes: 72 additions & 46 deletions app/src/main/java/run/halo/feed/FeedPluginEndpoint.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<ServerResponse> 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<ServerResponse> rssSourcesListerRouter() {
return RouterFunctions.route()
.GET("/apis/api.feed.halo.run/v1alpha1/rss-sources", this::listRssSources)
.build();
}

private Mono<ServerResponse> 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
Expand All @@ -52,20 +78,20 @@ RouterFunction<ServerResponse> additionalRssRouter() {
@NonNull
public Mono<HandlerFunction<ServerResponse>> 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<HandlerFunction<ServerResponse>> 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,
Expand All @@ -74,39 +100,39 @@ record RouteItem(RequestPredicate requestPredicate,

private HandlerFunction<ServerResponse> buildHandleFunction(RssRouteItem routeItem) {
return request -> rssCacheManager.get(request.path(), routeItem.handler(request))
.flatMap(item -> buildResponse(item));
.flatMap(item -> buildResponse(item));
}

private Mono<RequestPredicate> 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<ServerResponse> buildResponse(String xml) {
return ServerResponse.ok().contentType(MediaType.TEXT_XML)
.bodyValue(xml);
.bodyValue(xml);
}
}
14 changes: 14 additions & 0 deletions app/src/main/resources/extensions/roleTemplates.yaml
Original file line number Diff line number Diff line change
@@ -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" ]

0 comments on commit 5b9d4e6

Please sign in to comment.