Upload 97 files
Browse files- .github/workflows/docker-release.yml +142 -0
- .vscode/extensions.json +16 -0
- .vscode/settings.json +43 -0
- apps/server/package.json +1 -1
- apps/server/src/app.controller.ts +10 -2
- apps/server/src/app.service.ts +0 -5
- apps/server/src/feeds/feeds.service.ts +10 -1
- apps/server/src/trpc/trpc.router.ts +7 -7
- apps/web/index.html +1 -0
- apps/web/package.json +1 -1
- apps/web/src/pages/accounts/index.tsx +1 -3
- apps/web/src/pages/feeds/index.tsx +53 -12
- apps/web/src/provider/trpc.tsx +11 -6
- apps/web/src/utils/env.ts +3 -0
- apps/web/src/vite-env.d.ts +1 -0
- package.json +1 -1
.github/workflows/docker-release.yml
ADDED
@@ -0,0 +1,142 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
name: Build WeWeRSS images and push image to docker hub
|
2 |
+
on:
|
3 |
+
workflow_dispatch:
|
4 |
+
push:
|
5 |
+
# paths:
|
6 |
+
# - "apps/**"
|
7 |
+
# - "Dockerfile"
|
8 |
+
tags:
|
9 |
+
- "v*.*.*"
|
10 |
+
|
11 |
+
concurrency:
|
12 |
+
group: docker-release
|
13 |
+
cancel-in-progress: true
|
14 |
+
|
15 |
+
jobs:
|
16 |
+
check-env:
|
17 |
+
permissions:
|
18 |
+
contents: none
|
19 |
+
runs-on: ubuntu-latest
|
20 |
+
timeout-minutes: 5
|
21 |
+
outputs:
|
22 |
+
check-docker: ${{ steps.check-docker.outputs.defined }}
|
23 |
+
steps:
|
24 |
+
- id: check-docker
|
25 |
+
env:
|
26 |
+
DOCKER_HUB_NAME: ${{ secrets.DOCKER_HUB_NAME }}
|
27 |
+
if: ${{ env.DOCKER_HUB_NAME != '' }}
|
28 |
+
run: echo "defined=true" >> $GITHUB_OUTPUT
|
29 |
+
|
30 |
+
release-images:
|
31 |
+
runs-on: ubuntu-latest
|
32 |
+
timeout-minutes: 120
|
33 |
+
permissions:
|
34 |
+
packages: write
|
35 |
+
contents: read
|
36 |
+
id-token: write
|
37 |
+
steps:
|
38 |
+
- name: Checkout
|
39 |
+
uses: actions/checkout@v4
|
40 |
+
with:
|
41 |
+
fetch-depth: 1
|
42 |
+
|
43 |
+
- name: Set up QEMU
|
44 |
+
uses: docker/setup-qemu-action@v3
|
45 |
+
|
46 |
+
- name: Set up Docker Buildx
|
47 |
+
uses: docker/setup-buildx-action@v3
|
48 |
+
|
49 |
+
- name: Login to Docker Hub
|
50 |
+
uses: docker/login-action@v2
|
51 |
+
with:
|
52 |
+
username: ${{ secrets.DOCKER_HUB_NAME }}
|
53 |
+
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
|
54 |
+
|
55 |
+
- name: Login to GitHub Container Registry
|
56 |
+
uses: docker/login-action@v3
|
57 |
+
with:
|
58 |
+
registry: ghcr.io
|
59 |
+
username: ${{ github.repository_owner }}
|
60 |
+
password: ${{ secrets.GITHUB_TOKEN }}
|
61 |
+
|
62 |
+
- name: Extract Docker metadata (sqlite)
|
63 |
+
id: meta-sqlite
|
64 |
+
uses: docker/metadata-action@v5
|
65 |
+
with:
|
66 |
+
images: |
|
67 |
+
${{ secrets.DOCKER_HUB_NAME }}/wewe-rss-sqlite
|
68 |
+
ghcr.io/cooderl/wewe-rss-sqlite
|
69 |
+
tags: |
|
70 |
+
type=raw,value=latest,enable=true
|
71 |
+
type=raw,value=${{ github.ref_name }},enable=true
|
72 |
+
flavor: latest=false
|
73 |
+
|
74 |
+
- name: Build and push Docker image (sqlite)
|
75 |
+
id: build-and-push-sqlite
|
76 |
+
uses: docker/build-push-action@v5
|
77 |
+
with:
|
78 |
+
context: .
|
79 |
+
push: true
|
80 |
+
tags: ${{ steps.meta-sqlite.outputs.tags }}
|
81 |
+
labels: ${{ steps.meta-sqlite.outputs.labels }}
|
82 |
+
target: app-sqlite
|
83 |
+
platforms: linux/amd64,linux/arm64
|
84 |
+
cache-from: type=gha,scope=docker-release
|
85 |
+
cache-to: type=gha,mode=max,scope=docker-release
|
86 |
+
|
87 |
+
- name: Extract Docker metadata
|
88 |
+
id: meta
|
89 |
+
uses: docker/metadata-action@v5
|
90 |
+
with:
|
91 |
+
images: |
|
92 |
+
${{ secrets.DOCKER_HUB_NAME }}/wewe-rss
|
93 |
+
ghcr.io/cooderl/wewe-rss
|
94 |
+
tags: |
|
95 |
+
type=raw,value=latest,enable=true
|
96 |
+
type=raw,value=${{ github.ref_name }},enable=true
|
97 |
+
flavor: latest=false
|
98 |
+
|
99 |
+
- name: Build and push Docker image
|
100 |
+
id: build-and-push
|
101 |
+
uses: docker/build-push-action@v5
|
102 |
+
with:
|
103 |
+
context: .
|
104 |
+
push: true
|
105 |
+
tags: ${{ steps.meta.outputs.tags }}
|
106 |
+
labels: ${{ steps.meta.outputs.labels }}
|
107 |
+
target: app
|
108 |
+
platforms: linux/amd64,linux/arm64
|
109 |
+
cache-from: type=gha,scope=docker-release
|
110 |
+
cache-to: type=gha,mode=max,scope=docker-release
|
111 |
+
|
112 |
+
- name: Set env
|
113 |
+
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
114 |
+
|
115 |
+
- name: Create a Release
|
116 |
+
uses: elgohr/Github-Release-Action@v5
|
117 |
+
env:
|
118 |
+
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
119 |
+
with:
|
120 |
+
title: ${{ env.RELEASE_VERSION }}
|
121 |
+
|
122 |
+
description:
|
123 |
+
runs-on: ubuntu-latest
|
124 |
+
needs: check-env
|
125 |
+
if: needs.check-env.outputs.check-docker == 'true'
|
126 |
+
timeout-minutes: 5
|
127 |
+
steps:
|
128 |
+
- uses: actions/checkout@v4
|
129 |
+
|
130 |
+
- name: Docker Hub Description(sqlite)
|
131 |
+
uses: peter-evans/dockerhub-description@v4
|
132 |
+
with:
|
133 |
+
username: ${{ secrets.DOCKER_HUB_NAME }}
|
134 |
+
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
|
135 |
+
repository: ${{ secrets.DOCKER_HUB_NAME }}/wewe-rss-sqlite
|
136 |
+
|
137 |
+
- name: Docker Hub Description
|
138 |
+
uses: peter-evans/dockerhub-description@v4
|
139 |
+
with:
|
140 |
+
username: ${{ secrets.DOCKER_HUB_NAME }}
|
141 |
+
password: ${{ secrets.DOCKER_HUB_PASSWORD }}
|
142 |
+
repository: ${{ secrets.DOCKER_HUB_NAME }}/wewe-rss
|
.vscode/extensions.json
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"recommendations": [
|
3 |
+
"esbenp.prettier-vscode",
|
4 |
+
"dbaeumer.vscode-eslint",
|
5 |
+
"stylelint.vscode-stylelint",
|
6 |
+
"streetsidesoftware.code-spell-checker",
|
7 |
+
"DavidAnson.vscode-markdownlint",
|
8 |
+
"Gruntfuggly.todo-tree",
|
9 |
+
"mikestead.dotenv",
|
10 |
+
"foxundermoon.next-js",
|
11 |
+
"Prisma.prisma",
|
12 |
+
"planbcoding.vscode-react-refactor",
|
13 |
+
"yoavbls.pretty-ts-errors",
|
14 |
+
"usernamehw.errorlens"
|
15 |
+
]
|
16 |
+
}
|
.vscode/settings.json
ADDED
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"typescript.tsdk": "node_modules/.pnpm/[email protected]/node_modules/typescript/lib",
|
3 |
+
"typescript.enablePromptUseWorkspaceTsdk": true,
|
4 |
+
"[javascript]": {
|
5 |
+
"editor.formatOnSave": true,
|
6 |
+
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
7 |
+
},
|
8 |
+
"[typescript]": {
|
9 |
+
"editor.formatOnSave": true,
|
10 |
+
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
11 |
+
},
|
12 |
+
"[html]": {
|
13 |
+
"editor.formatOnSave": true,
|
14 |
+
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
15 |
+
},
|
16 |
+
"[scss]": {
|
17 |
+
"editor.formatOnSave": true,
|
18 |
+
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
19 |
+
},
|
20 |
+
"[css]": {
|
21 |
+
"editor.formatOnSave": true,
|
22 |
+
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
23 |
+
},
|
24 |
+
"[yaml]": {
|
25 |
+
"editor.formatOnSave": true,
|
26 |
+
"editor.defaultFormatter": "redhat.vscode-yaml"
|
27 |
+
},
|
28 |
+
"[json]": {
|
29 |
+
"editor.formatOnSave": true,
|
30 |
+
"editor.defaultFormatter": "vscode.json-language-features"
|
31 |
+
},
|
32 |
+
"cSpell.words": [
|
33 |
+
"callout",
|
34 |
+
"checkstyle",
|
35 |
+
"commitlint",
|
36 |
+
"daisyui",
|
37 |
+
"nestjs",
|
38 |
+
"nextui",
|
39 |
+
"tailwindcss",
|
40 |
+
"Trpc",
|
41 |
+
"wewe"
|
42 |
+
]
|
43 |
+
}
|
apps/server/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1 |
{
|
2 |
"name": "server",
|
3 |
-
"version": "1.
|
4 |
"description": "",
|
5 |
"author": "",
|
6 |
"private": true,
|
|
|
1 |
{
|
2 |
"name": "server",
|
3 |
+
"version": "1.9.0",
|
4 |
"description": "",
|
5 |
"author": "",
|
6 |
"private": true,
|
apps/server/src/app.controller.ts
CHANGED
@@ -1,9 +1,14 @@
|
|
1 |
import { Controller, Get, Redirect, Render } from '@nestjs/common';
|
2 |
import { AppService } from './app.service';
|
|
|
|
|
3 |
|
4 |
@Controller()
|
5 |
export class AppController {
|
6 |
-
constructor(
|
|
|
|
|
|
|
7 |
|
8 |
@Get()
|
9 |
getHello(): string {
|
@@ -23,9 +28,12 @@ export class AppController {
|
|
23 |
@Render('index.hbs')
|
24 |
dashRender() {
|
25 |
const { originUrl: weweRssServerOriginUrl } =
|
26 |
-
this.
|
|
|
|
|
27 |
return {
|
28 |
weweRssServerOriginUrl,
|
|
|
29 |
};
|
30 |
}
|
31 |
}
|
|
|
1 |
import { Controller, Get, Redirect, Render } from '@nestjs/common';
|
2 |
import { AppService } from './app.service';
|
3 |
+
import { ConfigService } from '@nestjs/config';
|
4 |
+
import { ConfigurationType } from './configuration';
|
5 |
|
6 |
@Controller()
|
7 |
export class AppController {
|
8 |
+
constructor(
|
9 |
+
private readonly appService: AppService,
|
10 |
+
private readonly configService: ConfigService,
|
11 |
+
) {}
|
12 |
|
13 |
@Get()
|
14 |
getHello(): string {
|
|
|
28 |
@Render('index.hbs')
|
29 |
dashRender() {
|
30 |
const { originUrl: weweRssServerOriginUrl } =
|
31 |
+
this.configService.get<ConfigurationType['feed']>('feed')!;
|
32 |
+
const { code } = this.configService.get<ConfigurationType['auth']>('auth')!;
|
33 |
+
|
34 |
return {
|
35 |
weweRssServerOriginUrl,
|
36 |
+
enabledAuthCode: !!code,
|
37 |
};
|
38 |
}
|
39 |
}
|
apps/server/src/app.service.ts
CHANGED
@@ -1,6 +1,5 @@
|
|
1 |
import { Injectable } from '@nestjs/common';
|
2 |
import { ConfigService } from '@nestjs/config';
|
3 |
-
import { ConfigurationType } from './configuration';
|
4 |
|
5 |
@Injectable()
|
6 |
export class AppService {
|
@@ -12,8 +11,4 @@ export class AppService {
|
|
12 |
</div>
|
13 |
`;
|
14 |
}
|
15 |
-
|
16 |
-
getFeedConfig() {
|
17 |
-
return this.configService.get<ConfigurationType['feed']>('feed')!;
|
18 |
-
}
|
19 |
}
|
|
|
1 |
import { Injectable } from '@nestjs/common';
|
2 |
import { ConfigService } from '@nestjs/config';
|
|
|
3 |
|
4 |
@Injectable()
|
5 |
export class AppService {
|
|
|
11 |
</div>
|
12 |
`;
|
13 |
}
|
|
|
|
|
|
|
|
|
14 |
}
|
apps/server/src/feeds/feeds.service.ts
CHANGED
@@ -156,6 +156,7 @@ export class FeedsService {
|
|
156 |
copyright: '',
|
157 |
updated: new Date(feedInfo.updateTime * 1e3),
|
158 |
generator: 'WeWe-RSS',
|
|
|
159 |
});
|
160 |
|
161 |
feed.addExtension({
|
@@ -163,16 +164,23 @@ export class FeedsService {
|
|
163 |
objects: `WeWe-RSS`,
|
164 |
});
|
165 |
|
|
|
|
|
|
|
|
|
166 |
/**mode 高于 globalMode。如果 mode 值存在,取 mode 值*/
|
167 |
const enableFullText =
|
168 |
typeof mode === 'string'
|
169 |
? mode === 'fulltext'
|
170 |
: globalMode === 'fulltext';
|
171 |
|
|
|
|
|
172 |
const mapper = async (item) => {
|
173 |
-
const { title, id, publishTime, picUrl } = item;
|
174 |
const link = `https://mp.weixin.qq.com/s/${id}`;
|
175 |
|
|
|
176 |
const published = new Date(publishTime * 1e3);
|
177 |
|
178 |
let description = '';
|
@@ -188,6 +196,7 @@ export class FeedsService {
|
|
188 |
description,
|
189 |
date: published,
|
190 |
image: picUrl,
|
|
|
191 |
});
|
192 |
};
|
193 |
|
|
|
156 |
copyright: '',
|
157 |
updated: new Date(feedInfo.updateTime * 1e3),
|
158 |
generator: 'WeWe-RSS',
|
159 |
+
author: { name: feedInfo.mpName },
|
160 |
});
|
161 |
|
162 |
feed.addExtension({
|
|
|
164 |
objects: `WeWe-RSS`,
|
165 |
});
|
166 |
|
167 |
+
const feeds = await this.prismaService.feed.findMany({
|
168 |
+
select: { id: true, mpName: true },
|
169 |
+
});
|
170 |
+
|
171 |
/**mode 高于 globalMode。如果 mode 值存在,取 mode 值*/
|
172 |
const enableFullText =
|
173 |
typeof mode === 'string'
|
174 |
? mode === 'fulltext'
|
175 |
: globalMode === 'fulltext';
|
176 |
|
177 |
+
const showAuthor = feedInfo.id === 'all';
|
178 |
+
|
179 |
const mapper = async (item) => {
|
180 |
+
const { title, id, publishTime, picUrl, mpId } = item;
|
181 |
const link = `https://mp.weixin.qq.com/s/${id}`;
|
182 |
|
183 |
+
const mpName = feeds.find((item) => item.id === mpId)?.mpName || '-';
|
184 |
const published = new Date(publishTime * 1e3);
|
185 |
|
186 |
let description = '';
|
|
|
196 |
description,
|
197 |
date: published,
|
198 |
image: picUrl,
|
199 |
+
author: showAuthor ? [{ name: mpName }] : undefined,
|
200 |
});
|
201 |
};
|
202 |
|
apps/server/src/trpc/trpc.router.ts
CHANGED
@@ -22,12 +22,12 @@ export class TrpcRouter {
|
|
22 |
list: this.trpcService.protectedProcedure
|
23 |
.input(
|
24 |
z.object({
|
25 |
-
limit: z.number().min(1).max(
|
26 |
cursor: z.string().nullish(),
|
27 |
}),
|
28 |
)
|
29 |
.query(async ({ input }) => {
|
30 |
-
const limit = input.limit ??
|
31 |
const { cursor } = input;
|
32 |
|
33 |
const items = await this.prismaService.account.findMany({
|
@@ -132,12 +132,12 @@ export class TrpcRouter {
|
|
132 |
list: this.trpcService.protectedProcedure
|
133 |
.input(
|
134 |
z.object({
|
135 |
-
limit: z.number().min(1).max(
|
136 |
cursor: z.string().nullish(),
|
137 |
}),
|
138 |
)
|
139 |
.query(async ({ input }) => {
|
140 |
-
const limit = input.limit ??
|
141 |
const { cursor } = input;
|
142 |
|
143 |
const items = await this.prismaService.feed.findMany({
|
@@ -247,13 +247,13 @@ export class TrpcRouter {
|
|
247 |
list: this.trpcService.protectedProcedure
|
248 |
.input(
|
249 |
z.object({
|
250 |
-
limit: z.number().min(1).max(
|
251 |
cursor: z.string().nullish(),
|
252 |
mpId: z.string().nullish(),
|
253 |
}),
|
254 |
)
|
255 |
.query(async ({ input }) => {
|
256 |
-
const limit = input.limit ??
|
257 |
const { cursor, mpId } = input;
|
258 |
|
259 |
const items = await this.prismaService.article.findMany({
|
@@ -401,7 +401,7 @@ export class TrpcRouter {
|
|
401 |
const authCode =
|
402 |
this.configService.get<ConfigurationType['auth']>('auth')!.code;
|
403 |
|
404 |
-
if (req.headers.authorization !== authCode) {
|
405 |
return {
|
406 |
errorMsg: 'authCode不正确!',
|
407 |
};
|
|
|
22 |
list: this.trpcService.protectedProcedure
|
23 |
.input(
|
24 |
z.object({
|
25 |
+
limit: z.number().min(1).max(500).nullish(),
|
26 |
cursor: z.string().nullish(),
|
27 |
}),
|
28 |
)
|
29 |
.query(async ({ input }) => {
|
30 |
+
const limit = input.limit ?? 500;
|
31 |
const { cursor } = input;
|
32 |
|
33 |
const items = await this.prismaService.account.findMany({
|
|
|
132 |
list: this.trpcService.protectedProcedure
|
133 |
.input(
|
134 |
z.object({
|
135 |
+
limit: z.number().min(1).max(500).nullish(),
|
136 |
cursor: z.string().nullish(),
|
137 |
}),
|
138 |
)
|
139 |
.query(async ({ input }) => {
|
140 |
+
const limit = input.limit ?? 500;
|
141 |
const { cursor } = input;
|
142 |
|
143 |
const items = await this.prismaService.feed.findMany({
|
|
|
247 |
list: this.trpcService.protectedProcedure
|
248 |
.input(
|
249 |
z.object({
|
250 |
+
limit: z.number().min(1).max(500).nullish(),
|
251 |
cursor: z.string().nullish(),
|
252 |
mpId: z.string().nullish(),
|
253 |
}),
|
254 |
)
|
255 |
.query(async ({ input }) => {
|
256 |
+
const limit = input.limit ?? 500;
|
257 |
const { cursor, mpId } = input;
|
258 |
|
259 |
const items = await this.prismaService.article.findMany({
|
|
|
401 |
const authCode =
|
402 |
this.configService.get<ConfigurationType['auth']>('auth')!.code;
|
403 |
|
404 |
+
if (authCode && req.headers.authorization !== authCode) {
|
405 |
return {
|
406 |
errorMsg: 'authCode不正确!',
|
407 |
};
|
apps/web/index.html
CHANGED
@@ -11,6 +11,7 @@
|
|
11 |
<div id="root"></div>
|
12 |
<script>
|
13 |
window.__WEWE_RSS_SERVER_ORIGIN_URL__ = '{{ weweRssServerOriginUrl }}';
|
|
|
14 |
</script>
|
15 |
<script type="module" src="/src/main.tsx"></script>
|
16 |
</body>
|
|
|
11 |
<div id="root"></div>
|
12 |
<script>
|
13 |
window.__WEWE_RSS_SERVER_ORIGIN_URL__ = '{{ weweRssServerOriginUrl }}';
|
14 |
+
window.__WEWE_RSS_ENABLED_AUTH_CODE__ = {{ enabledAuthCode }};
|
15 |
</script>
|
16 |
<script type="module" src="/src/main.tsx"></script>
|
17 |
</body>
|
apps/web/package.json
CHANGED
@@ -1,7 +1,7 @@
|
|
1 |
{
|
2 |
"name": "web",
|
3 |
"private": true,
|
4 |
-
"version": "1.
|
5 |
"type": "module",
|
6 |
"scripts": {
|
7 |
"dev": "vite",
|
|
|
1 |
{
|
2 |
"name": "web",
|
3 |
"private": true,
|
4 |
+
"version": "1.9.0",
|
5 |
"type": "module",
|
6 |
"scripts": {
|
7 |
"dev": "vite",
|
apps/web/src/pages/accounts/index.tsx
CHANGED
@@ -25,9 +25,7 @@ import { statusMap } from '@web/constants';
|
|
25 |
const AccountPage = () => {
|
26 |
const { isOpen, onOpen, onClose, onOpenChange } = useDisclosure();
|
27 |
|
28 |
-
const { refetch, data, isFetching } = trpc.account.list.useQuery({
|
29 |
-
limit: 100,
|
30 |
-
});
|
31 |
|
32 |
const { mutateAsync: updateAccount } = trpc.account.edit.useMutation({});
|
33 |
|
|
|
25 |
const AccountPage = () => {
|
26 |
const { isOpen, onOpen, onClose, onOpenChange } = useDisclosure();
|
27 |
|
28 |
+
const { refetch, data, isFetching } = trpc.account.list.useQuery({});
|
|
|
|
|
29 |
|
30 |
const { mutateAsync: updateAccount } = trpc.account.edit.useMutation({});
|
31 |
|
apps/web/src/pages/feeds/index.tsx
CHANGED
@@ -30,9 +30,7 @@ const Feeds = () => {
|
|
30 |
|
31 |
const { isOpen, onOpen, onOpenChange, onClose } = useDisclosure();
|
32 |
const { refetch: refetchFeedList, data: feedData } = trpc.feed.list.useQuery(
|
33 |
-
{
|
34 |
-
limit: 100,
|
35 |
-
},
|
36 |
{
|
37 |
refetchOnWindowFocus: true,
|
38 |
},
|
@@ -93,6 +91,38 @@ const Feeds = () => {
|
|
93 |
return feedData?.items.find((item) => item.id === currentMpId);
|
94 |
}, [currentMpId, feedData?.items]);
|
95 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
96 |
return (
|
97 |
<>
|
98 |
<div className="h-full flex justify-between">
|
@@ -238,15 +268,26 @@ const Feeds = () => {
|
|
238 |
</Tooltip>
|
239 |
</div>
|
240 |
) : (
|
241 |
-
<
|
242 |
-
|
243 |
-
|
244 |
-
|
245 |
-
|
246 |
-
|
247 |
-
|
248 |
-
|
249 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
250 |
)}
|
251 |
</div>
|
252 |
<div className="p-2 overflow-y-auto">
|
|
|
30 |
|
31 |
const { isOpen, onOpen, onOpenChange, onClose } = useDisclosure();
|
32 |
const { refetch: refetchFeedList, data: feedData } = trpc.feed.list.useQuery(
|
33 |
+
{},
|
|
|
|
|
34 |
{
|
35 |
refetchOnWindowFocus: true,
|
36 |
},
|
|
|
91 |
return feedData?.items.find((item) => item.id === currentMpId);
|
92 |
}, [currentMpId, feedData?.items]);
|
93 |
|
94 |
+
const handleExportOpml = async (ev) => {
|
95 |
+
ev.preventDefault();
|
96 |
+
ev.stopPropagation();
|
97 |
+
if (!feedData?.items?.length) {
|
98 |
+
console.warn('没有订阅源');
|
99 |
+
return;
|
100 |
+
}
|
101 |
+
|
102 |
+
let opmlContent = `<?xml version="1.0" encoding="UTF-8"?>
|
103 |
+
<opml version="2.0">
|
104 |
+
<head>
|
105 |
+
<title>WeWeRSS 所有订阅源</title>
|
106 |
+
</head>
|
107 |
+
<body>
|
108 |
+
`;
|
109 |
+
|
110 |
+
feedData?.items.forEach((sub) => {
|
111 |
+
opmlContent += ` <outline text="${sub.mpName}" type="rss" xmlUrl="${window.location.origin}/feeds/${sub.id}.atom" htmlUrl="${window.location.origin}/feeds/${sub.id}.atom"/>\n`;
|
112 |
+
});
|
113 |
+
|
114 |
+
opmlContent += ` </body>
|
115 |
+
</opml>`;
|
116 |
+
|
117 |
+
const blob = new Blob([opmlContent], { type: 'text/xml;charset=utf-8;' });
|
118 |
+
const link = document.createElement('a');
|
119 |
+
link.href = URL.createObjectURL(blob);
|
120 |
+
link.download = 'WeWeRSS-All.opml';
|
121 |
+
document.body.appendChild(link);
|
122 |
+
link.click();
|
123 |
+
document.body.removeChild(link);
|
124 |
+
};
|
125 |
+
|
126 |
return (
|
127 |
<>
|
128 |
<div className="h-full flex justify-between">
|
|
|
268 |
</Tooltip>
|
269 |
</div>
|
270 |
) : (
|
271 |
+
<div className="flex gap-2">
|
272 |
+
<Link
|
273 |
+
href="#"
|
274 |
+
color="foreground"
|
275 |
+
onClick={handleExportOpml}
|
276 |
+
size="sm"
|
277 |
+
>
|
278 |
+
导出OPML
|
279 |
+
</Link>
|
280 |
+
<Divider orientation="vertical" />
|
281 |
+
<Link
|
282 |
+
size="sm"
|
283 |
+
showAnchorIcon
|
284 |
+
target="_blank"
|
285 |
+
href={`${serverOriginUrl}/feeds/all.atom`}
|
286 |
+
color="foreground"
|
287 |
+
>
|
288 |
+
RSS
|
289 |
+
</Link>
|
290 |
+
</div>
|
291 |
)}
|
292 |
</div>
|
293 |
<div className="p-2 overflow-y-auto">
|
apps/web/src/provider/trpc.tsx
CHANGED
@@ -5,13 +5,19 @@ import { useState } from 'react';
|
|
5 |
import { toast } from 'sonner';
|
6 |
import { isTRPCClientError, trpc } from '../utils/trpc';
|
7 |
import { getAuthCode, setAuthCode } from '../utils/auth';
|
8 |
-
import { serverOriginUrl } from '../utils/env';
|
9 |
|
10 |
export const TrpcProvider: React.FC<{ children: React.ReactNode }> = ({
|
11 |
children,
|
12 |
}) => {
|
13 |
const navigate = useNavigate();
|
14 |
|
|
|
|
|
|
|
|
|
|
|
|
|
15 |
const [queryClient] = useState(
|
16 |
() =>
|
17 |
new QueryClient({
|
@@ -38,8 +44,7 @@ export const TrpcProvider: React.FC<{ children: React.ReactNode }> = ({
|
|
38 |
description: error.message,
|
39 |
});
|
40 |
|
41 |
-
|
42 |
-
navigate('/login');
|
43 |
} else {
|
44 |
toast.error('请求失败!', {
|
45 |
description: error.message,
|
@@ -56,8 +61,7 @@ export const TrpcProvider: React.FC<{ children: React.ReactNode }> = ({
|
|
56 |
toast.error('无权限', {
|
57 |
description: error.message,
|
58 |
});
|
59 |
-
|
60 |
-
navigate('/login');
|
61 |
} else {
|
62 |
toast.error('请求失败!', {
|
63 |
description: error.message,
|
@@ -82,9 +86,10 @@ export const TrpcProvider: React.FC<{ children: React.ReactNode }> = ({
|
|
82 |
const token = getAuthCode();
|
83 |
|
84 |
if (!token) {
|
85 |
-
|
86 |
return {};
|
87 |
}
|
|
|
88 |
return token
|
89 |
? {
|
90 |
Authorization: `${token}`,
|
|
|
5 |
import { toast } from 'sonner';
|
6 |
import { isTRPCClientError, trpc } from '../utils/trpc';
|
7 |
import { getAuthCode, setAuthCode } from '../utils/auth';
|
8 |
+
import { enabledAuthCode, serverOriginUrl } from '../utils/env';
|
9 |
|
10 |
export const TrpcProvider: React.FC<{ children: React.ReactNode }> = ({
|
11 |
children,
|
12 |
}) => {
|
13 |
const navigate = useNavigate();
|
14 |
|
15 |
+
const handleNoAuth = () => {
|
16 |
+
if (enabledAuthCode) {
|
17 |
+
setAuthCode('');
|
18 |
+
navigate('/login');
|
19 |
+
}
|
20 |
+
};
|
21 |
const [queryClient] = useState(
|
22 |
() =>
|
23 |
new QueryClient({
|
|
|
44 |
description: error.message,
|
45 |
});
|
46 |
|
47 |
+
handleNoAuth();
|
|
|
48 |
} else {
|
49 |
toast.error('请求失败!', {
|
50 |
description: error.message,
|
|
|
61 |
toast.error('无权限', {
|
62 |
description: error.message,
|
63 |
});
|
64 |
+
handleNoAuth();
|
|
|
65 |
} else {
|
66 |
toast.error('请求失败!', {
|
67 |
description: error.message,
|
|
|
86 |
const token = getAuthCode();
|
87 |
|
88 |
if (!token) {
|
89 |
+
handleNoAuth();
|
90 |
return {};
|
91 |
}
|
92 |
+
|
93 |
return token
|
94 |
? {
|
95 |
Authorization: `${token}`,
|
apps/web/src/utils/env.ts
CHANGED
@@ -5,3 +5,6 @@ export const serverOriginUrl = isProd
|
|
5 |
: import.meta.env.VITE_SERVER_ORIGIN_URL;
|
6 |
|
7 |
export const appVersion = __APP_VERSION__;
|
|
|
|
|
|
|
|
5 |
: import.meta.env.VITE_SERVER_ORIGIN_URL;
|
6 |
|
7 |
export const appVersion = __APP_VERSION__;
|
8 |
+
|
9 |
+
export const enabledAuthCode =
|
10 |
+
window.__WEWE_RSS_ENABLED_AUTH_CODE__ === false ? false : true;
|
apps/web/src/vite-env.d.ts
CHANGED
@@ -7,6 +7,7 @@ interface ImportMetaEnv {
|
|
7 |
|
8 |
interface Window {
|
9 |
__WEWE_RSS_SERVER_ORIGIN_URL__?: string;
|
|
|
10 |
}
|
11 |
|
12 |
declare const __APP_VERSION__: string;
|
|
|
7 |
|
8 |
interface Window {
|
9 |
__WEWE_RSS_SERVER_ORIGIN_URL__?: string;
|
10 |
+
__WEWE_RSS_ENABLED_AUTH_CODE__?: boolean;
|
11 |
}
|
12 |
|
13 |
declare const __APP_VERSION__: string;
|
package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1 |
{
|
2 |
"name": "wewe-rss",
|
3 |
-
"version": "1.
|
4 |
"private": true,
|
5 |
"author": "cooderl <[email protected]>",
|
6 |
"description": "",
|
|
|
1 |
{
|
2 |
"name": "wewe-rss",
|
3 |
+
"version": "1.9.0",
|
4 |
"private": true,
|
5 |
"author": "cooderl <[email protected]>",
|
6 |
"description": "",
|