Skip to content

Commit 1a84f2c

Browse files
authored
♻️ refactor: refactor trpc request to use zod schema (#10016)
refactor request api
1 parent 80202ed commit 1a84f2c

File tree

8 files changed

+161
-88
lines changed

8 files changed

+161
-88
lines changed

packages/types/src/message/ui/params.ts

Lines changed: 98 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
/* eslint-disable sort-keys-fix/sort-keys-fix , typescript-sort-keys/interface */
2+
import { z } from 'zod';
3+
24
import { UploadFileItem } from '../../files';
35
import { MessageSemanticSearchChunk } from '../../rag';
4-
import { ChatMessageError } from '../common/base';
6+
import { ChatMessageError, ChatMessageErrorSchema } from '../common/base';
57
import { ChatPluginPayload } from '../common/tools';
6-
import { UIChatMessage, UIMessageRoleType } from './chat';
8+
import { UIChatMessage } from './chat';
9+
import { SemanticSearchChunkSchema } from './rag';
10+
11+
export type CreateMessageRoleType = 'user' | 'assistant' | 'tool' | 'supervisor';
712

813
export interface CreateMessageParams
914
extends Partial<Omit<UIChatMessage, 'content' | 'role' | 'topicId' | 'chunksList'>> {
@@ -14,7 +19,7 @@ export interface CreateMessageParams
1419
fromModel?: string;
1520
fromProvider?: string;
1621
groupId?: string;
17-
role: UIMessageRoleType;
22+
role: CreateMessageRoleType;
1823
sessionId: string;
1924
targetId?: string | null;
2025
threadId?: string | null;
@@ -28,7 +33,7 @@ export interface CreateMessageParams
2833
*/
2934
export interface CreateNewMessageParams {
3035
// ========== Required fields ==========
31-
role: UIMessageRoleType;
36+
role: CreateMessageRoleType;
3237
content: string;
3338
sessionId: string;
3439

@@ -103,3 +108,92 @@ export interface SendGroupMessageParams {
103108
*/
104109
targetMemberId?: string | null;
105110
}
111+
112+
// ========== Zod Schemas ========== //
113+
114+
const UIMessageRoleTypeSchema = z.enum(['user', 'assistant', 'tool', 'supervisor']);
115+
116+
const ChatPluginPayloadSchema = z.object({
117+
apiName: z.string(),
118+
arguments: z.string(),
119+
identifier: z.string(),
120+
type: z.string(),
121+
});
122+
123+
export const CreateMessageParamsSchema = z
124+
.object({
125+
content: z.string(),
126+
role: UIMessageRoleTypeSchema,
127+
sessionId: z.string().nullable().optional(),
128+
error: ChatMessageErrorSchema.nullable().optional(),
129+
fileChunks: z.array(SemanticSearchChunkSchema).optional(),
130+
files: z.array(z.string()).optional(),
131+
fromModel: z.string().optional(),
132+
fromProvider: z.string().optional(),
133+
groupId: z.string().optional(),
134+
targetId: z.string().nullable().optional(),
135+
threadId: z.string().nullable().optional(),
136+
topicId: z.string().optional(),
137+
traceId: z.string().optional(),
138+
// Allow additional fields from UIChatMessage (many can be null)
139+
agentId: z.string().optional(),
140+
children: z.any().optional(),
141+
chunksList: z.any().optional(),
142+
createdAt: z.number().optional(),
143+
extra: z.any().optional(),
144+
favorite: z.boolean().optional(),
145+
fileList: z.any().optional(),
146+
id: z.string().optional(),
147+
imageList: z.any().optional(),
148+
meta: z.any().optional(),
149+
metadata: z.any().nullable().optional(),
150+
model: z.string().nullable().optional(),
151+
observationId: z.string().optional(),
152+
parentId: z.string().optional(),
153+
performance: z.any().optional(),
154+
plugin: z.any().optional(),
155+
pluginError: z.any().optional(),
156+
pluginState: z.any().optional(),
157+
provider: z.string().nullable().optional(),
158+
quotaId: z.string().optional(),
159+
ragQuery: z.string().nullable().optional(),
160+
ragQueryId: z.string().nullable().optional(),
161+
reasoning: z.any().optional(),
162+
search: z.any().optional(),
163+
tool_call_id: z.string().optional(),
164+
toolCalls: z.any().optional(),
165+
tools: z.any().optional(),
166+
translate: z.any().optional(),
167+
tts: z.any().optional(),
168+
updatedAt: z.number().optional(),
169+
})
170+
.passthrough();
171+
172+
export const CreateNewMessageParamsSchema = z
173+
.object({
174+
// Required fields
175+
role: UIMessageRoleTypeSchema,
176+
content: z.string(),
177+
sessionId: z.string().nullable().optional(),
178+
// Tool related
179+
tool_call_id: z.string().optional(),
180+
plugin: ChatPluginPayloadSchema.optional(),
181+
// Grouping
182+
parentId: z.string().optional(),
183+
groupId: z.string().optional(),
184+
// Context
185+
topicId: z.string().optional(),
186+
threadId: z.string().nullable().optional(),
187+
targetId: z.string().nullable().optional(),
188+
// Model info
189+
model: z.string().nullable().optional(),
190+
provider: z.string().nullable().optional(),
191+
// Content
192+
files: z.array(z.string()).optional(),
193+
// Error handling
194+
error: ChatMessageErrorSchema.nullable().optional(),
195+
// Metadata
196+
traceId: z.string().optional(),
197+
fileChunks: z.array(SemanticSearchChunkSchema).optional(),
198+
})
199+
.passthrough();

packages/types/src/user/index.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { z } from 'zod';
33

44
import { Plans } from '../subscription';
55
import { TopicDisplayMode } from '../topic';
6-
import { UserSettings } from '../user/settings';
6+
import { UserSettings } from './settings';
77

88
export interface LobeUser {
99
avatar?: string;
@@ -74,3 +74,15 @@ export const NextAuthAccountSchame = z.object({
7474
provider: z.string(),
7575
providerAccountId: z.string(),
7676
});
77+
78+
export const UserPreferenceSchema = z
79+
.object({
80+
disableInputMarkdownRender: z.boolean().optional(),
81+
enableGroupChat: z.boolean().optional(),
82+
guide: UserGuideSchema.optional(),
83+
hideSyncAlert: z.boolean().optional(),
84+
telemetry: z.boolean().nullable(),
85+
topicDisplayMode: z.nativeEnum(TopicDisplayMode).optional(),
86+
useCmdEnterToSend: z.boolean().optional(),
87+
})
88+
.partial();

packages/types/src/user/settings/index.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { z } from 'zod';
2+
13
import type { LobeAgentSettings } from '../../session';
24
import { UserGeneralConfig } from './general';
35
import { UserHotkeyConfig } from './hotkey';
@@ -18,6 +20,7 @@ export * from './keyVaults';
1820
export * from './modelProvider';
1921
export * from './sync';
2022
export * from './systemAgent';
23+
export * from './tool';
2124
export * from './tts';
2225

2326
/**
@@ -34,3 +37,22 @@ export interface UserSettings {
3437
tool: UserToolConfig;
3538
tts: UserTTSConfig;
3639
}
40+
41+
/**
42+
* Zod schema for partial UserSettings updates
43+
* Uses passthrough to allow any nested settings fields
44+
*/
45+
export const UserSettingsSchema = z
46+
.object({
47+
defaultAgent: z.any().optional(),
48+
general: z.any().optional(),
49+
hotkey: z.any().optional(),
50+
image: z.any().optional(),
51+
keyVaults: z.any().optional(),
52+
languageModel: z.any().optional(),
53+
systemAgent: z.any().optional(),
54+
tool: z.any().optional(),
55+
tts: z.any().optional(),
56+
})
57+
.passthrough()
58+
.partial();

src/server/routers/lambda/__tests__/integration/message.integration.test.ts

Lines changed: 0 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -318,47 +318,6 @@ describe('Message Router Integration Tests', () => {
318318
});
319319
});
320320

321-
describe('batchCreateMessages', () => {
322-
it('should create multiple messages in batch', async () => {
323-
const caller = messageRouter.createCaller(createTestContext(userId));
324-
325-
const messagesToCreate = [
326-
{
327-
content: 'Batch message 1',
328-
role: 'user' as const,
329-
sessionId: testSessionId,
330-
},
331-
{
332-
content: 'Batch message 2',
333-
role: 'assistant' as const,
334-
sessionId: testSessionId,
335-
},
336-
{
337-
content: 'Batch message 3',
338-
role: 'user' as const,
339-
sessionId: testSessionId,
340-
topicId: testTopicId,
341-
},
342-
];
343-
344-
const result = await caller.batchCreateMessages(messagesToCreate);
345-
346-
expect(result.success).toBe(true);
347-
// Note: rowCount might be undefined in PGlite, so we skip this check
348-
// expect(result.added).toBe(3);
349-
350-
// 验证数据库中的消息
351-
const dbMessages = await serverDB
352-
.select()
353-
.from(messages)
354-
.where(eq(messages.sessionId, testSessionId));
355-
356-
expect(dbMessages.length).toBeGreaterThanOrEqual(3);
357-
const topicMessage = dbMessages.find((m) => m.content === 'Batch message 3');
358-
expect(topicMessage?.topicId).toBe(testTopicId);
359-
});
360-
});
361-
362321
describe('removeMessages', () => {
363322
it('should remove multiple messages', async () => {
364323
const caller = messageRouter.createCaller(createTestContext(userId));

src/server/routers/lambda/message.ts

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {
2-
BatchTaskResult,
2+
CreateMessageParamsSchema,
3+
CreateNewMessageParamsSchema,
34
UIChatMessage,
45
UpdateMessageParamsSchema,
56
UpdateMessageRAGParamsSchema,
@@ -27,14 +28,6 @@ const messageProcedure = authedProcedure.use(serverDatabase).use(async (opts) =>
2728
});
2829

2930
export const messageRouter = router({
30-
batchCreateMessages: messageProcedure
31-
.input(z.array(z.any()))
32-
.mutation(async ({ input, ctx }): Promise<BatchTaskResult> => {
33-
const data = await ctx.messageModel.batchCreate(input);
34-
35-
return { added: data.rowCount as number, ids: [], skips: [], success: true };
36-
}),
37-
3831
count: messageProcedure
3932
.input(
4033
z
@@ -64,15 +57,15 @@ export const messageRouter = router({
6457
}),
6558

6659
createMessage: messageProcedure
67-
.input(z.object({}).passthrough().partial())
60+
.input(CreateMessageParamsSchema)
6861
.mutation(async ({ input, ctx }) => {
6962
const data = await ctx.messageModel.create(input as any);
7063

7164
return data.id;
7265
}),
7366

7467
createNewMessage: messageProcedure
75-
.input(z.object({}).passthrough().partial())
68+
.input(CreateNewMessageParamsSchema)
7669
.mutation(async ({ input, ctx }) => {
7770
return ctx.messageModel.createNewMessage(input as any, {
7871
postProcessUrl: (path) => ctx.fileService.getFullFileUrl(path),

src/server/routers/lambda/user.ts

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
import { UserJSON } from '@clerk/backend';
2+
import { enableClerk, isDesktop } from '@lobechat/const';
3+
import {
4+
NextAuthAccountSchame,
5+
UserGuideSchema,
6+
UserInitializationState,
7+
UserPreference,
8+
UserPreferenceSchema,
9+
UserSettings,
10+
UserSettingsSchema,
11+
} from '@lobechat/types';
212
import { v4 as uuidv4 } from 'uuid';
313
import { z } from 'zod';
414

5-
import { enableClerk } from '@/const/auth';
6-
import { isDesktop } from '@/const/version';
715
import { MessageModel } from '@/database/models/message';
816
import { SessionModel } from '@/database/models/session';
917
import { UserModel, UserNotFoundError } from '@/database/models/user';
@@ -16,13 +24,6 @@ import { S3 } from '@/server/modules/S3';
1624
import { FileService } from '@/server/services/file';
1725
import { NextAuthUserService } from '@/server/services/nextAuthUser';
1826
import { UserService } from '@/server/services/user';
19-
import {
20-
NextAuthAccountSchame,
21-
UserGuideSchema,
22-
UserInitializationState,
23-
UserPreference,
24-
} from '@/types/user';
25-
import { UserSettings } from '@/types/user/settings';
2627

2728
const userProcedure = authedProcedure.use(serverDatabase).use(async ({ ctx, next }) => {
2829
return next({
@@ -199,30 +200,28 @@ export const userRouter = router({
199200
return ctx.userModel.updateGuide(input);
200201
}),
201202

202-
updatePreference: userProcedure.input(z.any()).mutation(async ({ ctx, input }) => {
203+
updatePreference: userProcedure.input(UserPreferenceSchema).mutation(async ({ ctx, input }) => {
203204
return ctx.userModel.updatePreference(input);
204205
}),
205206

206-
updateSettings: userProcedure
207-
.input(z.object({}).passthrough())
208-
.mutation(async ({ ctx, input }) => {
209-
const { keyVaults, ...res } = input as Partial<UserSettings>;
207+
updateSettings: userProcedure.input(UserSettingsSchema).mutation(async ({ ctx, input }) => {
208+
const { keyVaults, ...res } = input as Partial<UserSettings>;
210209

211-
// Encrypt keyVaults
212-
let encryptedKeyVaults: string | null = null;
210+
// Encrypt keyVaults
211+
let encryptedKeyVaults: string | null = null;
213212

214-
if (keyVaults) {
215-
// TODO: better to add a validation
216-
const data = JSON.stringify(keyVaults);
217-
const gateKeeper = await KeyVaultsGateKeeper.initWithEnvKey();
213+
if (keyVaults) {
214+
// TODO: better to add a validation
215+
const data = JSON.stringify(keyVaults);
216+
const gateKeeper = await KeyVaultsGateKeeper.initWithEnvKey();
218217

219-
encryptedKeyVaults = await gateKeeper.encrypt(data);
220-
}
218+
encryptedKeyVaults = await gateKeeper.encrypt(data);
219+
}
221220

222-
const nextValue = { ...res, keyVaults: encryptedKeyVaults };
221+
const nextValue = { ...res, keyVaults: encryptedKeyVaults };
223222

224-
return ctx.userModel.updateSetting(nextValue);
225-
}),
223+
return ctx.userModel.updateSetting(nextValue);
224+
}),
226225
});
227226

228227
export type UserRouter = typeof userRouter;

src/services/message/server.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,6 @@ export class ServerService implements IMessageService {
2121
});
2222
};
2323

24-
batchCreateMessages: IMessageService['batchCreateMessages'] = async (messages) => {
25-
return lambdaClient.message.batchCreateMessages.mutate(messages);
26-
};
27-
2824
getMessages: IMessageService['getMessages'] = async (sessionId, topicId, groupId) => {
2925
const data = await lambdaClient.message.getMessages.query({
3026
groupId,

src/services/message/type.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import {
55
ChatTranslate,
66
CreateMessageParams,
77
CreateMessageResult,
8-
DBMessageItem,
98
ModelRankItem,
109
UIChatMessage,
1110
UpdateMessageParams,
@@ -19,7 +18,6 @@ import type { HeatmapsProps } from '@lobehub/charts';
1918
export interface IMessageService {
2019
createMessage(data: CreateMessageParams): Promise<string>;
2120
createNewMessage(data: CreateMessageParams): Promise<CreateMessageResult>;
22-
batchCreateMessages(messages: DBMessageItem[]): Promise<any>;
2321

2422
getMessages(sessionId: string, topicId?: string, groupId?: string): Promise<UIChatMessage[]>;
2523
getGroupMessages(groupId: string, topicId?: string): Promise<UIChatMessage[]>;

0 commit comments

Comments
 (0)