wewe-rss / apps /server /src /trpc /trpc.router.ts
Elkins's picture
Upload 97 files
d193c91 verified
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) {
// Remove the last item and use it as next cursor
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
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(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) {
// Remove the last item and use it as next cursor
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
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(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) {
// Remove the last item and use it as next cursor
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
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`];