| 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(100).nullish(), |
| cursor: z.string().nullish(), |
| }), |
| ) |
| .query(async ({ input }) => { |
| const limit = input.limit ?? 50; |
| 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, |
| }); |
|
|
| 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, |
| }); |
| return account; |
| }), |
| delete: this.trpcService.protectedProcedure |
| .input(z.string()) |
| .mutation(async ({ input: id }) => { |
| await this.prismaService.account.delete({ where: { id } }); |
| return id; |
| }), |
| }); |
|
|
| feedRouter = this.trpcService.router({ |
| list: this.trpcService.protectedProcedure |
| .input( |
| z.object({ |
| limit: z.number().min(1).max(100).nullish(), |
| cursor: z.string().nullish(), |
| }), |
| ) |
| .query(async ({ input }) => { |
| const limit = input.limit ?? 50; |
| 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.string()) |
| .mutation(async ({ input: mpId }) => { |
| await this.trpcService.refreshMpArticlesAndUpdateFeed(mpId); |
| }), |
| }); |
|
|
| articleRouter = this.trpcService.router({ |
| list: this.trpcService.protectedProcedure |
| .input( |
| z.object({ |
| limit: z.number().min(1).max(100).nullish(), |
| cursor: z.string().nullish(), |
| mpId: z.string().nullish(), |
| }), |
| ) |
| .query(async ({ input }) => { |
| const limit = input.limit ?? 50; |
| 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 (req.headers.authorization !== authCode) { |
| return { |
| errorMsg: 'authCode不正确!', |
| }; |
| } |
| return { |
| errorMsg: null, |
| }; |
| }, |
| middleware: (req, res, next) => { |
| next(); |
| }, |
| }), |
| ); |
| } |
| } |
|
|
| export type AppRouter = TrpcRouter[`appRouter`]; |
|
|