Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/cd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,7 @@ jobs:
contents: write
with:
app-id: app-KhIVw
skip-node-setup: true
node-version: "20"
pnpm-version: "9"
ui-path: "ui"
artifacts-path: 'app/build/libs'
4 changes: 3 additions & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,7 @@ jobs:
ci:
uses: halo-sigs/reusable-workflows/.github/workflows/plugin-ci.yaml@v1
with:
skip-node-setup: true
node-version: "20"
pnpm-version: "9"
ui-path: "ui"
artifacts-path: 'app/build/libs'
5 changes: 3 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ application-local.yml
application-local.yaml
application-local.properties

/admin-frontend/node_modules/
/workplace/
*/workplace/
*/workplace/
/ui/node_modules/
/app/src/main/resources/console/
10 changes: 10 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@ dependencies {
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.register('copyUI', Copy) {
dependsOn ':ui:build'
from project(':ui').layout.buildDirectory.dir('dist')
into layout.buildDirectory.dir('resources/main/console')
}

tasks.named('processResources') {
dependsOn('copyUI')
}

test {
useJUnitPlatform()
}
Expand Down
87 changes: 63 additions & 24 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,16 +10,17 @@
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 java.util.ArrayList;

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 {
Expand All @@ -33,6 +32,7 @@ public class FeedPluginEndpoint {
private final PostRssProvider postRssProvider;
private final ExtensionGetter extensionGetter;
private final RssCacheManager rssCacheManager;
private final ExternalUrlSupplier externalUrlSupplier;

@Bean
RouterFunction<ServerResponse> rssRouterFunction() {
Expand All @@ -44,6 +44,45 @@ RouterFunction<ServerResponse> rssRouterFunction() {
.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(result -> {
var allPosts = RssSource.builder()
.pattern(externalUrl + "/rss.xml")
.displayName("订阅所有文章")
.description("会根据设置的文章数量返回最新的文章")
.example("https://example.com/rss.xml")
.build();
var sources = new ArrayList<>();
sources.add(allPosts);
sources.addAll(result);
return ServerResponse.ok().bodyValue(sources);
});
}

@Builder
record RssSource(String displayName, String description, String pattern, String example) {
}

@Bean
RouterFunction<ServerResponse> additionalRssRouter() {
var pathMatcher = ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, "/feed/**");
Expand Down Expand Up @@ -84,25 +123,25 @@ private Mono<RequestPredicate> buildRequestPredicate(RssRouteItem routeItem) {
.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) {
Expand Down
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" ]
1 change: 1 addition & 0 deletions app/src/test/java/run/halo/feed/RSS2Test.java
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ void extractRssTagsTest() {
var lastBuildDate = RssXmlBuilder.instantToString(instant);
var rssXml = new RssXmlBuilder()
.withRss2(rss)
.withLastBuildDate(instant)
.withGenerator("Halo")
.withExtractRssTags("""
<user>
Expand Down
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ rootProject.name = 'plugin-feed'

include 'api'
include 'app'
include 'ui'
12 changes: 12 additions & 0 deletions ui/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# EditorConfig is awesome: https://EditorConfig.org

# top-most EditorConfig file
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = false
insert_final_newline = true
23 changes: 23 additions & 0 deletions ui/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/* eslint-env node */
require("@rushstack/eslint-patch/modern-module-resolution");

module.exports = {
root: true,
extends: [
"plugin:vue/vue3-recommended",
"eslint:recommended",
"@vue/eslint-config-typescript/recommended",
"@vue/eslint-config-prettier",
"@unocss",
],
env: {
node: true,
"vue/setup-compiler-macros": true,
},
rules: {
"vue/no-v-html": "off",
"vue/no-deprecated-slot-attribute": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-explicit-any": "off",
},
};
30 changes: 30 additions & 0 deletions ui/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
.DS_Store
dist
dist-ssr
coverage
*.local

/cypress/videos/
/cypress/screenshots/

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

*.tsbuildinfo
23 changes: 23 additions & 0 deletions ui/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
plugins {
id "com.github.node-gradle.node" version "7.1.0"
}

tasks.register('clean', Delete) {
delete layout.buildDirectory
}

tasks.register('build', PnpmTask) {
group = 'build'
description = 'Build console.'
dependsOn tasks.named('pnpmInstall')
pnpmCommand = ['run', 'build']
inputs.dir('src')
inputs.files(fileTree('node_modules').exclude('.cache'))
inputs.files(layout.projectDirectory.asFile.listFiles({ pathname ->
pathname.name.endsWithAny(".json", ".yaml", ".env", ".ts", ".json")
} as FileFilter))
outputs.dir(layout.buildDirectory.dir('dist'))
configure {
shouldRunAfter tasks.named('clean')
}
}
29 changes: 29 additions & 0 deletions ui/env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/// <reference types="vite/client" />
/// <reference types="unplugin-icons/types/vue" />
/// <reference types="vue-i18n/dist/vue-i18n.d.ts" />

export {};

declare module "axios" {
export interface AxiosRequestConfig {
mute?: boolean;
}
}

declare module "*.vue" {
import type { ComponentOptions } from "vue";
const Component: ComponentOptions;
export default Component;
}

declare module "*.md" {
import type { ComponentOptions } from "vue";
const Component: ComponentOptions;
export default Component;
}

declare module "vue" {
interface ComponentCustomProperties {
$formkit: any;
}
}
45 changes: 45 additions & 0 deletions ui/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"type": "module",
"scripts": {
"dev": "vite build --watch --mode=development",
"build": "vite build",
"test-unit": "vitest --environment jsdom --run",
"type-check": "vue-tsc -p tsconfig.app.json --noEmit",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
"prettier": "prettier --write src/"
},
"dependencies": {
"@halo-dev/api-client": "^2.20.0",
"@halo-dev/components": "^2.20.0",
"@halo-dev/console-shared": "^2.20.0",
"@tanstack/vue-query": "^4.37.1",
"axios": "^1.7.9",
"vue": "^3.5.13",
"vue-router": "^4.5.0"
},
"devDependencies": {
"@halo-dev/ui-plugin-bundler-kit": "^2.20.0",
"@rushstack/eslint-patch": "^1.10.4",
"@tsconfig/node18": "^2.0.1",
"@types/jsdom": "^20.0.1",
"@types/node": "^18.19.67",
"@unocss/eslint-config": "^0.61.9",
"@vitejs/plugin-vue": "^5.2.1",
"@vue/eslint-config-prettier": "^9.0.0",
"@vue/eslint-config-typescript": "^11.0.3",
"@vue/test-utils": "^2.4.6",
"@vue/tsconfig": "^0.4.0",
"eslint": "^8.57.1",
"eslint-plugin-vue": "^9.32.0",
"jsdom": "^19.0.0",
"prettier": "^3.4.2",
"prettier-plugin-organize-imports": "^4.1.0",
"sass": "^1.82.0",
"typescript": "~5.5.4",
"unocss": "^0.61.9",
"vite": "^5.4.11",
"vitest": "^0.34.6",
"vue-tsc": "^2.1.10"
},
"packageManager": "pnpm@9.14.4"
}
Loading