| | import { INestApplication, Injectable, Logger } from '@nestjs/common'; |
| | import { z } from 'zod'; |
| | import { TrpcService } from '@server/trpc/trpc.service'; |
| | import * as trpcExpress from '@trpc/server/adapters/express'; |
| | import { TRPCError } from '@trpc/server'; |
| | import { PrismaService } from '@server/prisma/prisma.service'; |
| | import { statusMap } from '@server/constants'; |
| | import { ConfigService } from '@nestjs/config'; |
| | import { ConfigurationType } from '@server/configuration'; |
| |
|
| | @Injectable() |
| | export class TrpcRouter { |
| | constructor( |
| | private readonly trpcService: TrpcService, |
| | private readonly prismaService: PrismaService, |
| | private readonly configService: ConfigService, |
| | ) {} |
| |
|
| | private readonly logger = new Logger(this.constructor.name); |
| |
|
| | accountRouter = this.trpcService.router({ |
| | list: this.trpcService.protectedProcedure |
| | .input( |
| | z.object({ |
| | limit: z.number().min(1).max(500).nullish(), |
| | cursor: z.string().nullish(), |
| | }), |
| | ) |
| | .query(async ({ input }) => { |
| | const limit = input.limit ?? 500; |
| | const { cursor } = input; |
| |
|
| | const items = await this.prismaService.account.findMany({ |
| | take: limit + 1, |
| | where: {}, |
| | select: { |
| | id: true, |
| | name: true, |
| | status: true, |
| | createdAt: true, |
| | updatedAt: true, |
| | token: false, |
| | }, |
| | cursor: cursor |
| | ? { |
| | id: cursor, |
| | } |
| | : undefined, |
| | orderBy: { |
| | createdAt: 'asc', |
| | }, |
| | }); |
| | let nextCursor: typeof cursor | undefined = undefined; |
| | if (items.length > limit) { |
| | |
| |
|
| | |
| | const nextItem = items.pop()!; |
| | nextCursor = nextItem.id; |
| | } |
| |
|
| | const disabledAccounts = this.trpcService.getBlockedAccountIds(); |
| | return { |
| | blocks: disabledAccounts, |
| | items, |
| | nextCursor, |
| | }; |
| | }), |
| | byId: this.trpcService.protectedProcedure |
| | .input(z.string()) |
| | .query(async ({ input: id }) => { |
| | const account = await this.prismaService.account.findUnique({ |
| | where: { id }, |
| | }); |
| | if (!account) { |
| | throw new TRPCError({ |
| | code: 'BAD_REQUEST', |
| | message: `No account with id '${id}'`, |
| | }); |
| | } |
| | return account; |
| | }), |
| | add: this.trpcService.protectedProcedure |
| | .input( |
| | z.object({ |
| | id: z.string().min(1).max(32), |
| | token: z.string().min(1), |
| | name: z.string().min(1), |
| | status: z.number().default(statusMap.ENABLE), |
| | }), |
| | ) |
| | .mutation(async ({ input }) => { |
| | const { id, ...data } = input; |
| | const account = await this.prismaService.account.upsert({ |
| | where: { |
| | id, |
| | }, |
| | update: data, |
| | create: input, |
| | }); |
| | this.trpcService.removeBlockedAccount(id); |
| |
|
| | return account; |
| | }), |
| | edit: this.trpcService.protectedProcedure |
| | .input( |
| | z.object({ |
| | id: z.string(), |
| | data: z.object({ |
| | token: z.string().min(1).optional(), |
| | name: z.string().min(1).optional(), |
| | status: z.number().optional(), |
| | }), |
| | }), |
| | ) |
| | .mutation(async ({ input }) => { |
| | const { id, data } = input; |
| | const account = await this.prismaService.account.update({ |
| | where: { id }, |
| | data, |
| | }); |
| | this.trpcService.removeBlockedAccount(id); |
| | return account; |
| | }), |
| | delete: this.trpcService.protectedProcedure |
| | .input(z.string()) |
| | .mutation(async ({ input: id }) => { |
| | await this.prismaService.account.delete({ where: { id } }); |
| | this.trpcService.removeBlockedAccount(id); |
| |
|
| | return id; |
| | }), |
| | }); |
| |
|
| | feedRouter = this.trpcService.router({ |
| | list: this.trpcService.protectedProcedure |
| | .input( |
| | z.object({ |
| | limit: z.number().min(1).max(500).nullish(), |
| | cursor: z.string().nullish(), |
| | }), |
| | ) |
| | .query(async ({ input }) => { |
| | const limit = input.limit ?? 500; |
| | const { cursor } = input; |
| |
|
| | const items = await this.prismaService.feed.findMany({ |
| | take: limit + 1, |
| | where: {}, |
| | cursor: cursor |
| | ? { |
| | id: cursor, |
| | } |
| | : undefined, |
| | orderBy: { |
| | createdAt: 'asc', |
| | }, |
| | }); |
| | let nextCursor: typeof cursor | undefined = undefined; |
| | if (items.length > limit) { |
| | |
| |
|
| | |
| | const nextItem = items.pop()!; |
| | nextCursor = nextItem.id; |
| | } |
| |
|
| | return { |
| | items: items, |
| | nextCursor, |
| | }; |
| | }), |
| | byId: this.trpcService.protectedProcedure |
| | .input(z.string()) |
| | .query(async ({ input: id }) => { |
| | const feed = await this.prismaService.feed.findUnique({ |
| | where: { id }, |
| | }); |
| | if (!feed) { |
| | throw new TRPCError({ |
| | code: 'BAD_REQUEST', |
| | message: `No feed with id '${id}'`, |
| | }); |
| | } |
| | return feed; |
| | }), |
| | add: this.trpcService.protectedProcedure |
| | .input( |
| | z.object({ |
| | id: z.string(), |
| | mpName: z.string(), |
| | mpCover: z.string(), |
| | mpIntro: z.string(), |
| | syncTime: z |
| | .number() |
| | .optional() |
| | .default(Math.floor(Date.now() / 1e3)), |
| | updateTime: z.number(), |
| | status: z.number().default(statusMap.ENABLE), |
| | }), |
| | ) |
| | .mutation(async ({ input }) => { |
| | const { id, ...data } = input; |
| | const feed = await this.prismaService.feed.upsert({ |
| | where: { |
| | id, |
| | }, |
| | update: data, |
| | create: input, |
| | }); |
| |
|
| | return feed; |
| | }), |
| | edit: this.trpcService.protectedProcedure |
| | .input( |
| | z.object({ |
| | id: z.string(), |
| | data: z.object({ |
| | mpName: z.string().optional(), |
| | mpCover: z.string().optional(), |
| | mpIntro: z.string().optional(), |
| | syncTime: z.number().optional(), |
| | updateTime: z.number().optional(), |
| | status: z.number().optional(), |
| | }), |
| | }), |
| | ) |
| | .mutation(async ({ input }) => { |
| | const { id, data } = input; |
| | const feed = await this.prismaService.feed.update({ |
| | where: { id }, |
| | data, |
| | }); |
| | return feed; |
| | }), |
| | delete: this.trpcService.protectedProcedure |
| | .input(z.string()) |
| | .mutation(async ({ input: id }) => { |
| | await this.prismaService.feed.delete({ where: { id } }); |
| | return id; |
| | }), |
| |
|
| | refreshArticles: this.trpcService.protectedProcedure |
| | .input( |
| | z.object({ |
| | mpId: z.string().optional(), |
| | }), |
| | ) |
| | .mutation(async ({ input: { mpId } }) => { |
| | if (mpId) { |
| | await this.trpcService.refreshMpArticlesAndUpdateFeed(mpId); |
| | } else { |
| | await this.trpcService.refreshAllMpArticlesAndUpdateFeed(); |
| | } |
| | }), |
| |
|
| | isRefreshAllMpArticlesRunning: this.trpcService.protectedProcedure.query( |
| | async () => { |
| | return this.trpcService.isRefreshAllMpArticlesRunning; |
| | }, |
| | ), |
| | }); |
| |
|
| | articleRouter = this.trpcService.router({ |
| | list: this.trpcService.protectedProcedure |
| | .input( |
| | z.object({ |
| | limit: z.number().min(1).max(500).nullish(), |
| | cursor: z.string().nullish(), |
| | mpId: z.string().nullish(), |
| | }), |
| | ) |
| | .query(async ({ input }) => { |
| | const limit = input.limit ?? 500; |
| | const { cursor, mpId } = input; |
| |
|
| | const items = await this.prismaService.article.findMany({ |
| | orderBy: [ |
| | { |
| | publishTime: 'desc', |
| | }, |
| | ], |
| | take: limit + 1, |
| | where: mpId ? { mpId } : undefined, |
| | cursor: cursor |
| | ? { |
| | id: cursor, |
| | } |
| | : undefined, |
| | }); |
| | let nextCursor: typeof cursor | undefined = undefined; |
| | if (items.length > limit) { |
| | |
| |
|
| | |
| | const nextItem = items.pop()!; |
| | nextCursor = nextItem.id; |
| | } |
| |
|
| | return { |
| | items, |
| | nextCursor, |
| | }; |
| | }), |
| | byId: this.trpcService.protectedProcedure |
| | .input(z.string()) |
| | .query(async ({ input: id }) => { |
| | const article = await this.prismaService.article.findUnique({ |
| | where: { id }, |
| | }); |
| | if (!article) { |
| | throw new TRPCError({ |
| | code: 'BAD_REQUEST', |
| | message: `No article with id '${id}'`, |
| | }); |
| | } |
| | return article; |
| | }), |
| |
|
| | add: this.trpcService.protectedProcedure |
| | .input( |
| | z.object({ |
| | id: z.string(), |
| | mpId: z.string(), |
| | title: z.string(), |
| | picUrl: z.string().optional().default(''), |
| | publishTime: z.number(), |
| | }), |
| | ) |
| | .mutation(async ({ input }) => { |
| | const { id, ...data } = input; |
| | const article = await this.prismaService.article.upsert({ |
| | where: { |
| | id, |
| | }, |
| | update: data, |
| | create: input, |
| | }); |
| |
|
| | return article; |
| | }), |
| | delete: this.trpcService.protectedProcedure |
| | .input(z.string()) |
| | .mutation(async ({ input: id }) => { |
| | await this.prismaService.article.delete({ where: { id } }); |
| | return id; |
| | }), |
| | }); |
| |
|
| | platformRouter = this.trpcService.router({ |
| | getMpArticles: this.trpcService.protectedProcedure |
| | .input( |
| | z.object({ |
| | mpId: z.string(), |
| | }), |
| | ) |
| | .mutation(async ({ input: { mpId } }) => { |
| | try { |
| | const results = await this.trpcService.getMpArticles(mpId); |
| | return results; |
| | } catch (err: any) { |
| | this.logger.log('getMpArticles err: ', err); |
| | throw new TRPCError({ |
| | code: 'INTERNAL_SERVER_ERROR', |
| | message: err.response?.data?.message || err.message, |
| | cause: err.stack, |
| | }); |
| | } |
| | }), |
| | getMpInfo: this.trpcService.protectedProcedure |
| | .input( |
| | z.object({ |
| | wxsLink: z |
| | .string() |
| | .refine((v) => v.startsWith('https://mp.weixin.qq.com/s/')), |
| | }), |
| | ) |
| | .mutation(async ({ input: { wxsLink: url } }) => { |
| | try { |
| | const results = await this.trpcService.getMpInfo(url); |
| | return results; |
| | } catch (err: any) { |
| | this.logger.log('getMpInfo err: ', err); |
| | throw new TRPCError({ |
| | code: 'INTERNAL_SERVER_ERROR', |
| | message: err.response?.data?.message || err.message, |
| | cause: err.stack, |
| | }); |
| | } |
| | }), |
| |
|
| | createLoginUrl: this.trpcService.protectedProcedure.mutation(async () => { |
| | return this.trpcService.createLoginUrl(); |
| | }), |
| | getLoginResult: this.trpcService.protectedProcedure |
| | .input( |
| | z.object({ |
| | id: z.string(), |
| | }), |
| | ) |
| | .query(async ({ input }) => { |
| | return this.trpcService.getLoginResult(input.id); |
| | }), |
| | }); |
| |
|
| | appRouter = this.trpcService.router({ |
| | feed: this.feedRouter, |
| | account: this.accountRouter, |
| | article: this.articleRouter, |
| | platform: this.platformRouter, |
| | }); |
| |
|
| | async applyMiddleware(app: INestApplication) { |
| | app.use( |
| | `/trpc`, |
| | trpcExpress.createExpressMiddleware({ |
| | router: this.appRouter, |
| | createContext: ({ req }) => { |
| | const authCode = |
| | this.configService.get<ConfigurationType['auth']>('auth')!.code; |
| |
|
| | if (authCode && req.headers.authorization !== authCode) { |
| | return { |
| | errorMsg: 'authCode不正确!', |
| | }; |
| | } |
| | return { |
| | errorMsg: null, |
| | }; |
| | }, |
| | middleware: (req, res, next) => { |
| | next(); |
| | }, |
| | }), |
| | ); |
| | } |
| | } |
| |
|
| | export type AppRouter = TrpcRouter[`appRouter`]; |
| |
|