@lobehub/lobehub 2.0.0-next.225 → 2.0.0-next.227

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/changelog/v1.json +18 -0
  3. package/docs/self-hosting/server-database/docker-compose.mdx +11 -0
  4. package/docs/self-hosting/server-database/docker-compose.zh-CN.mdx +11 -0
  5. package/locales/en-US/setting.json +4 -0
  6. package/locales/zh-CN/setting.json +4 -0
  7. package/package.json +2 -2
  8. package/packages/model-bank/src/aiModels/lobehub.ts +28 -0
  9. package/src/app/[variants]/(main)/chat/profile/features/Header/AgentPublishButton/ForkConfirmModal.tsx +67 -0
  10. package/src/app/[variants]/(main)/chat/profile/features/Header/AgentPublishButton/PublishButton.tsx +92 -105
  11. package/src/app/[variants]/(main)/chat/profile/features/Header/AgentPublishButton/index.tsx +13 -48
  12. package/src/app/[variants]/(main)/chat/profile/features/Header/AgentPublishButton/useMarketPublish.ts +69 -93
  13. package/src/business/client/hooks/useBusinessErrorAlertConfig.ts +9 -0
  14. package/src/business/client/hooks/useBusinessErrorContent.ts +13 -0
  15. package/src/business/server/lambda-routers/file.ts +12 -0
  16. package/src/features/Conversation/Error/index.tsx +13 -3
  17. package/src/layout/AuthProvider/MarketAuth/MarketAuthProvider.tsx +11 -28
  18. package/src/layout/AuthProvider/MarketAuth/ProfileSetupModal.tsx +30 -25
  19. package/src/layout/AuthProvider/MarketAuth/useMarketUserProfile.ts +4 -9
  20. package/src/libs/trpc/lambda/middleware/index.ts +1 -0
  21. package/src/libs/trpc/lambda/middleware/marketSDK.ts +68 -0
  22. package/src/locales/default/setting.ts +5 -0
  23. package/src/server/routers/lambda/__tests__/file.test.ts +7 -7
  24. package/src/server/routers/lambda/file.ts +12 -3
  25. package/src/server/routers/lambda/market/agent.ts +504 -0
  26. package/src/server/routers/lambda/market/index.ts +17 -0
  27. package/src/server/routers/lambda/market/oidc.ts +169 -0
  28. package/src/server/routers/lambda/market/social.ts +532 -0
  29. package/src/server/routers/lambda/market/user.ts +123 -0
  30. package/src/services/marketApi.ts +24 -84
  31. package/src/services/social.ts +70 -166
@@ -0,0 +1,169 @@
1
+ import { MarketSDK } from '@lobehub/market-sdk';
2
+ import { TRPCError } from '@trpc/server';
3
+ import debug from 'debug';
4
+ import { z } from 'zod';
5
+
6
+ import { publicProcedure, router } from '@/libs/trpc/lambda';
7
+ import { marketUserInfo, serverDatabase } from '@/libs/trpc/lambda/middleware';
8
+ import { generateTrustedClientToken } from '@/libs/trusted-client';
9
+
10
+ const log = debug('lambda-router:market:oidc');
11
+
12
+ const MARKET_BASE_URL = process.env.NEXT_PUBLIC_MARKET_BASE_URL || 'https://market.lobehub.com';
13
+
14
+ // OIDC procedures are public (used during authentication flow)
15
+ const oidcProcedure = publicProcedure.use(serverDatabase).use(marketUserInfo);
16
+
17
+ export const oidcRouter = router({
18
+ /**
19
+ * Exchange OAuth code for tokens
20
+ * POST /market/oidc/token (with grant_type=authorization_code)
21
+ */
22
+ exchangeAuthorizationCode: oidcProcedure
23
+ .input(
24
+ z.object({
25
+ clientId: z.string(),
26
+ code: z.string(),
27
+ codeVerifier: z.string(),
28
+ redirectUri: z.string(),
29
+ }),
30
+ )
31
+ .mutation(async ({ input }) => {
32
+ log('exchangeAuthorizationCode input: %O', { ...input, code: '[REDACTED]' });
33
+
34
+ const market = new MarketSDK({ baseURL: MARKET_BASE_URL });
35
+
36
+ try {
37
+ const response = await market.auth.exchangeOAuthToken({
38
+ clientId: input.clientId,
39
+ code: input.code,
40
+ codeVerifier: input.codeVerifier,
41
+ grantType: 'authorization_code',
42
+ redirectUri: input.redirectUri,
43
+ });
44
+ return response;
45
+ } catch (error) {
46
+ log('Error exchanging authorization code: %O', error);
47
+ throw new TRPCError({
48
+ cause: error,
49
+ code: 'INTERNAL_SERVER_ERROR',
50
+ message: error instanceof Error ? error.message : 'Failed to exchange authorization code',
51
+ });
52
+ }
53
+ }),
54
+
55
+ /**
56
+ * Get OAuth handoff information
57
+ * GET /market/oidc/handoff?id=xxx
58
+ */
59
+ getOAuthHandoff: oidcProcedure.input(z.object({ id: z.string() })).query(async ({ input }) => {
60
+ log('getOAuthHandoff input: %O', input);
61
+
62
+ const market = new MarketSDK({ baseURL: MARKET_BASE_URL });
63
+
64
+ try {
65
+ const handoff = await market.auth.getOAuthHandoff(input.id);
66
+ return handoff;
67
+ } catch (error) {
68
+ log('Error getting OAuth handoff: %O', error);
69
+ throw new TRPCError({
70
+ cause: error,
71
+ code: 'INTERNAL_SERVER_ERROR',
72
+ message: error instanceof Error ? error.message : 'Failed to get OAuth handoff',
73
+ });
74
+ }
75
+ }),
76
+
77
+ /**
78
+ * Get user info from token or trusted client
79
+ * POST /market/oidc/userinfo
80
+ */
81
+ getUserInfo: oidcProcedure
82
+ .input(z.object({ token: z.string().optional() }))
83
+ .mutation(async ({ input, ctx }) => {
84
+ log('getUserInfo input: token=%s', input.token ? '[REDACTED]' : 'undefined');
85
+
86
+ const market = new MarketSDK({ baseURL: MARKET_BASE_URL });
87
+
88
+ try {
89
+ // If token is provided, use it
90
+ if (input.token) {
91
+ const response = await market.auth.getUserInfo(input.token);
92
+ return response;
93
+ }
94
+
95
+ // Otherwise, try to use trustedClientToken
96
+ if (ctx.marketUserInfo) {
97
+ const trustedClientToken = generateTrustedClientToken(ctx.marketUserInfo);
98
+
99
+ if (trustedClientToken) {
100
+ const userInfoUrl = `${MARKET_BASE_URL}/lobehub-oidc/userinfo`;
101
+ const response = await fetch(userInfoUrl, {
102
+ headers: {
103
+ 'Content-Type': 'application/json',
104
+ 'x-lobe-trust-token': trustedClientToken,
105
+ },
106
+ method: 'GET',
107
+ });
108
+
109
+ if (!response.ok) {
110
+ throw new Error(
111
+ `Failed to fetch user info: ${response.status} ${response.statusText}`,
112
+ );
113
+ }
114
+
115
+ return await response.json();
116
+ }
117
+ }
118
+
119
+ throw new TRPCError({
120
+ code: 'BAD_REQUEST',
121
+ message: 'Token is required for userinfo',
122
+ });
123
+ } catch (error) {
124
+ if (error instanceof TRPCError) throw error;
125
+
126
+ log('Error getting user info: %O', error);
127
+ throw new TRPCError({
128
+ cause: error,
129
+ code: 'INTERNAL_SERVER_ERROR',
130
+ message: error instanceof Error ? error.message : 'Failed to get user info',
131
+ });
132
+ }
133
+ }),
134
+
135
+ /**
136
+ * Refresh access token
137
+ * POST /market/oidc/token (with grant_type=refresh_token)
138
+ */
139
+ refreshToken: oidcProcedure
140
+ .input(
141
+ z.object({
142
+ clientId: z.string().optional(),
143
+ refreshToken: z.string(),
144
+ }),
145
+ )
146
+ .mutation(async ({ input }) => {
147
+ log('refreshToken input: %O', { ...input, refreshToken: '[REDACTED]' });
148
+
149
+ const market = new MarketSDK({ baseURL: MARKET_BASE_URL });
150
+
151
+ try {
152
+ const response = await market.auth.exchangeOAuthToken({
153
+ clientId: input.clientId,
154
+ grantType: 'refresh_token',
155
+ refreshToken: input.refreshToken,
156
+ });
157
+ return response;
158
+ } catch (error) {
159
+ log('Error refreshing token: %O', error);
160
+ throw new TRPCError({
161
+ cause: error,
162
+ code: 'INTERNAL_SERVER_ERROR',
163
+ message: error instanceof Error ? error.message : 'Failed to refresh token',
164
+ });
165
+ }
166
+ }),
167
+ });
168
+
169
+ export type OidcRouter = typeof oidcRouter;
@@ -0,0 +1,532 @@
1
+ import { TRPCError } from '@trpc/server';
2
+ import debug from 'debug';
3
+ import { z } from 'zod';
4
+
5
+ import { authedProcedure, publicProcedure, router } from '@/libs/trpc/lambda';
6
+ import { marketSDK, marketUserInfo, serverDatabase } from '@/libs/trpc/lambda/middleware';
7
+
8
+ const log = debug('lambda-router:market:social');
9
+
10
+ // Authenticated procedure for social actions that require login
11
+ const socialAuthProcedure = authedProcedure.use(serverDatabase).use(marketUserInfo).use(marketSDK);
12
+
13
+ // Public procedure with optional auth for status checks
14
+ const socialPublicProcedure = publicProcedure
15
+ .use(serverDatabase)
16
+ .use(marketUserInfo)
17
+ .use(marketSDK);
18
+
19
+ // Schema definitions
20
+ const targetTypeSchema = z.enum(['agent', 'plugin']);
21
+
22
+ const paginationSchema = z.object({
23
+ limit: z.number().optional(),
24
+ offset: z.number().optional(),
25
+ });
26
+
27
+ export const socialRouter = router({
28
+ // ============================== Favorite Actions ==============================
29
+ /**
30
+ * Add to favorites
31
+ * POST /market/social/favorite
32
+ */
33
+ addFavorite: socialAuthProcedure
34
+ .input(
35
+ z.object({
36
+ identifier: z.string().optional(),
37
+ targetId: z.number().optional(),
38
+ targetType: targetTypeSchema,
39
+ }),
40
+ )
41
+ .mutation(async ({ input, ctx }) => {
42
+ log('addFavorite input: %O', input);
43
+
44
+ try {
45
+ const targetValue = input.identifier ?? input.targetId;
46
+ if (!targetValue) {
47
+ throw new Error('Either identifier or targetId is required');
48
+ }
49
+ await ctx.marketSDK.favorites.addFavorite(input.targetType, targetValue as any);
50
+ return { success: true };
51
+ } catch (error) {
52
+ log('Error adding favorite: %O', error);
53
+ throw new TRPCError({
54
+ cause: error,
55
+ code: 'INTERNAL_SERVER_ERROR',
56
+ message: error instanceof Error ? error.message : 'Failed to add favorite',
57
+ });
58
+ }
59
+ }),
60
+
61
+ /**
62
+ * Check if item is favorited
63
+ * GET /market/social/favorite-status/[targetType]/[targetId]
64
+ */
65
+ checkFavorite: socialPublicProcedure
66
+ .input(
67
+ z.object({
68
+ targetIdOrIdentifier: z.union([z.number(), z.string()]),
69
+ targetType: targetTypeSchema,
70
+ }),
71
+ )
72
+ .query(async ({ input, ctx }) => {
73
+ log('checkFavorite input: %O', input);
74
+
75
+ if (!ctx.marketSDK) {
76
+ return { isFavorited: false };
77
+ }
78
+
79
+ try {
80
+ const result = await ctx.marketSDK.favorites.checkFavorite(
81
+ input.targetType,
82
+ input.targetIdOrIdentifier as any,
83
+ );
84
+ return result;
85
+ } catch (error) {
86
+ log('Error checking favorite: %O', error);
87
+ return { isFavorited: false };
88
+ }
89
+ }),
90
+
91
+ // ============================== Follow Actions ==============================
92
+ /**
93
+ * Check follow status between current user and target user
94
+ * GET /market/social/follow-status/[userId]
95
+ */
96
+ checkFollowStatus: socialPublicProcedure
97
+ .input(z.object({ targetUserId: z.number() }))
98
+ .query(async ({ input, ctx }) => {
99
+ log('checkFollowStatus input: %O', input);
100
+
101
+ // If no auth, return default status
102
+ if (!ctx.marketSDK) {
103
+ return { isFollowing: false, isMutual: false };
104
+ }
105
+
106
+ try {
107
+ const result = await ctx.marketSDK.follows.checkFollowStatus(input.targetUserId);
108
+ return result;
109
+ } catch (error) {
110
+ log('Error checking follow status: %O', error);
111
+ // Return default on error (user might not be authenticated)
112
+ return { isFollowing: false, isMutual: false };
113
+ }
114
+ }),
115
+
116
+ // ============================== Like Actions ==============================
117
+ /**
118
+ * Check if item is liked
119
+ * GET /market/social/like-status/[targetType]/[targetId]
120
+ */
121
+ checkLike: socialPublicProcedure
122
+ .input(
123
+ z.object({
124
+ targetIdOrIdentifier: z.union([z.number(), z.string()]),
125
+ targetType: targetTypeSchema,
126
+ }),
127
+ )
128
+ .query(async ({ input, ctx }) => {
129
+ log('checkLike input: %O', input);
130
+
131
+ if (!ctx.marketSDK) {
132
+ return { isLiked: false };
133
+ }
134
+
135
+ try {
136
+ const result = await ctx.marketSDK.likes.checkLike(
137
+ input.targetType,
138
+ input.targetIdOrIdentifier as any,
139
+ );
140
+ return result;
141
+ } catch (error) {
142
+ log('Error checking like: %O', error);
143
+ return { isLiked: false };
144
+ }
145
+ }),
146
+
147
+ /**
148
+ * Follow a user
149
+ * POST /market/social/follow
150
+ */
151
+ follow: socialAuthProcedure
152
+ .input(z.object({ followingId: z.number() }))
153
+ .mutation(async ({ input, ctx }) => {
154
+ log('follow input: %O', input);
155
+
156
+ try {
157
+ await ctx.marketSDK.follows.follow(input.followingId);
158
+ return { success: true };
159
+ } catch (error) {
160
+ log('Error following user: %O', error);
161
+ throw new TRPCError({
162
+ cause: error,
163
+ code: 'INTERNAL_SERVER_ERROR',
164
+ message: error instanceof Error ? error.message : 'Failed to follow user',
165
+ });
166
+ }
167
+ }),
168
+
169
+ /**
170
+ * Get follow counts for a user
171
+ * GET /market/social/follow-counts/[userId]
172
+ */
173
+ getFollowCounts: socialPublicProcedure
174
+ .input(z.object({ userId: z.number() }))
175
+ .query(async ({ input, ctx }) => {
176
+ log('getFollowCounts input: %O', input);
177
+
178
+ try {
179
+ const [following, followers] = await Promise.all([
180
+ ctx.marketSDK.follows.getFollowing(input.userId, { limit: 1 }),
181
+ ctx.marketSDK.follows.getFollowers(input.userId, { limit: 1 }),
182
+ ]);
183
+
184
+ return {
185
+ followersCount: (followers as any).totalCount || (followers as any).total || 0,
186
+ followingCount: (following as any).totalCount || (following as any).total || 0,
187
+ };
188
+ } catch (error) {
189
+ log('Error getting follow counts: %O', error);
190
+ throw new TRPCError({
191
+ cause: error,
192
+ code: 'INTERNAL_SERVER_ERROR',
193
+ message: error instanceof Error ? error.message : 'Failed to get follow counts',
194
+ });
195
+ }
196
+ }),
197
+
198
+ /**
199
+ * Get followers of a user
200
+ * GET /market/social/followers/[userId]
201
+ */
202
+ getFollowers: socialPublicProcedure
203
+ .input(z.object({ userId: z.number() }).merge(paginationSchema))
204
+ .query(async ({ input, ctx }) => {
205
+ log('getFollowers input: %O', input);
206
+
207
+ try {
208
+ const { userId, ...params } = input;
209
+ const result = await ctx.marketSDK.follows.getFollowers(userId, params);
210
+ return result;
211
+ } catch (error) {
212
+ log('Error getting followers: %O', error);
213
+ throw new TRPCError({
214
+ cause: error,
215
+ code: 'INTERNAL_SERVER_ERROR',
216
+ message: error instanceof Error ? error.message : 'Failed to get followers',
217
+ });
218
+ }
219
+ }),
220
+
221
+ /**
222
+ * Get users that a user is following
223
+ * GET /market/social/following/[userId]
224
+ */
225
+ getFollowing: socialPublicProcedure
226
+ .input(z.object({ userId: z.number() }).merge(paginationSchema))
227
+ .query(async ({ input, ctx }) => {
228
+ log('getFollowing input: %O', input);
229
+
230
+ try {
231
+ const { userId, ...params } = input;
232
+ const result = await ctx.marketSDK.follows.getFollowing(userId, params);
233
+ return result;
234
+ } catch (error) {
235
+ log('Error getting following: %O', error);
236
+ throw new TRPCError({
237
+ cause: error,
238
+ code: 'INTERNAL_SERVER_ERROR',
239
+ message: error instanceof Error ? error.message : 'Failed to get following',
240
+ });
241
+ }
242
+ }),
243
+
244
+ /**
245
+ * Get current user's favorites
246
+ * GET /market/social/favorites
247
+ */
248
+ getMyFavorites: socialAuthProcedure
249
+ .input(paginationSchema.optional())
250
+ .query(async ({ input, ctx }) => {
251
+ log('getMyFavorites input: %O', input);
252
+
253
+ try {
254
+ const result = await ctx.marketSDK.favorites.getMyFavorites(input);
255
+ return result;
256
+ } catch (error) {
257
+ log('Error getting my favorites: %O', error);
258
+ throw new TRPCError({
259
+ cause: error,
260
+ code: 'INTERNAL_SERVER_ERROR',
261
+ message: error instanceof Error ? error.message : 'Failed to get favorites',
262
+ });
263
+ }
264
+ }),
265
+
266
+ /**
267
+ * Get user's favorite agents
268
+ * GET /market/social/favorite-agents/[userId]
269
+ */
270
+ getUserFavoriteAgents: socialPublicProcedure
271
+ .input(z.object({ userId: z.number() }).merge(paginationSchema))
272
+ .query(async ({ input, ctx }) => {
273
+ log('getUserFavoriteAgents input: %O', input);
274
+
275
+ try {
276
+ const { userId, ...params } = input;
277
+ const result = await ctx.marketSDK.favorites.getUserFavoriteAgents(userId, params);
278
+ return result;
279
+ } catch (error) {
280
+ log('Error getting user favorite agents: %O', error);
281
+ throw new TRPCError({
282
+ cause: error,
283
+ code: 'INTERNAL_SERVER_ERROR',
284
+ message: error instanceof Error ? error.message : 'Failed to get favorite agents',
285
+ });
286
+ }
287
+ }),
288
+
289
+ /**
290
+ * Get user's favorite plugins
291
+ * GET /market/social/favorite-plugins/[userId]
292
+ */
293
+ getUserFavoritePlugins: socialPublicProcedure
294
+ .input(z.object({ userId: z.number() }).merge(paginationSchema))
295
+ .query(async ({ input, ctx }) => {
296
+ log('getUserFavoritePlugins input: %O', input);
297
+
298
+ try {
299
+ const { userId, ...params } = input;
300
+ const result = await ctx.marketSDK.favorites.getUserFavoritePlugins(userId, params);
301
+ return result;
302
+ } catch (error) {
303
+ log('Error getting user favorite plugins: %O', error);
304
+ throw new TRPCError({
305
+ cause: error,
306
+ code: 'INTERNAL_SERVER_ERROR',
307
+ message: error instanceof Error ? error.message : 'Failed to get favorite plugins',
308
+ });
309
+ }
310
+ }),
311
+
312
+ /**
313
+ * Get user's all favorites
314
+ * GET /market/social/user-favorites/[userId]
315
+ */
316
+ getUserFavorites: socialPublicProcedure
317
+ .input(z.object({ userId: z.number() }).merge(paginationSchema))
318
+ .query(async ({ input, ctx }) => {
319
+ log('getUserFavorites input: %O', input);
320
+
321
+ try {
322
+ const { userId, ...params } = input;
323
+ const result = await ctx.marketSDK.favorites.getUserFavorites(userId, params);
324
+ return result;
325
+ } catch (error) {
326
+ log('Error getting user favorites: %O', error);
327
+ throw new TRPCError({
328
+ cause: error,
329
+ code: 'INTERNAL_SERVER_ERROR',
330
+ message: error instanceof Error ? error.message : 'Failed to get user favorites',
331
+ });
332
+ }
333
+ }),
334
+
335
+ /**
336
+ * Get user's liked agents
337
+ * GET /market/social/liked-agents/[userId]
338
+ */
339
+ getUserLikedAgents: socialPublicProcedure
340
+ .input(z.object({ userId: z.number() }).merge(paginationSchema))
341
+ .query(async ({ input, ctx }) => {
342
+ log('getUserLikedAgents input: %O', input);
343
+
344
+ try {
345
+ const { userId, ...params } = input;
346
+ const result = await ctx.marketSDK.likes.getUserLikedAgents(userId, params);
347
+ return result;
348
+ } catch (error) {
349
+ log('Error getting user liked agents: %O', error);
350
+ throw new TRPCError({
351
+ cause: error,
352
+ code: 'INTERNAL_SERVER_ERROR',
353
+ message: error instanceof Error ? error.message : 'Failed to get liked agents',
354
+ });
355
+ }
356
+ }),
357
+
358
+ /**
359
+ * Get user's liked plugins
360
+ * GET /market/social/liked-plugins/[userId]
361
+ */
362
+ getUserLikedPlugins: socialPublicProcedure
363
+ .input(z.object({ userId: z.number() }).merge(paginationSchema))
364
+ .query(async ({ input, ctx }) => {
365
+ log('getUserLikedPlugins input: %O', input);
366
+
367
+ try {
368
+ const { userId, ...params } = input;
369
+ const result = await ctx.marketSDK.likes.getUserLikedPlugins(userId, params);
370
+ return result;
371
+ } catch (error) {
372
+ log('Error getting user liked plugins: %O', error);
373
+ throw new TRPCError({
374
+ cause: error,
375
+ code: 'INTERNAL_SERVER_ERROR',
376
+ message: error instanceof Error ? error.message : 'Failed to get liked plugins',
377
+ });
378
+ }
379
+ }),
380
+
381
+ /**
382
+ * Like an item
383
+ * POST /market/social/like
384
+ */
385
+ like: socialAuthProcedure
386
+ .input(
387
+ z.object({
388
+ identifier: z.string().optional(),
389
+ targetId: z.number().optional(),
390
+ targetType: targetTypeSchema,
391
+ }),
392
+ )
393
+ .mutation(async ({ input, ctx }) => {
394
+ log('like input: %O', input);
395
+
396
+ try {
397
+ const targetValue = input.identifier ?? input.targetId;
398
+ if (!targetValue) {
399
+ throw new Error('Either identifier or targetId is required');
400
+ }
401
+ await ctx.marketSDK.likes.like(input.targetType, targetValue as any);
402
+ return { success: true };
403
+ } catch (error) {
404
+ log('Error liking item: %O', error);
405
+ throw new TRPCError({
406
+ cause: error,
407
+ code: 'INTERNAL_SERVER_ERROR',
408
+ message: error instanceof Error ? error.message : 'Failed to like item',
409
+ });
410
+ }
411
+ }),
412
+
413
+ /**
414
+ * Remove from favorites
415
+ * POST /market/social/unfavorite
416
+ */
417
+ removeFavorite: socialAuthProcedure
418
+ .input(
419
+ z.object({
420
+ identifier: z.string().optional(),
421
+ targetId: z.number().optional(),
422
+ targetType: targetTypeSchema,
423
+ }),
424
+ )
425
+ .mutation(async ({ input, ctx }) => {
426
+ log('removeFavorite input: %O', input);
427
+
428
+ try {
429
+ const targetValue = input.identifier ?? input.targetId;
430
+ if (!targetValue) {
431
+ throw new Error('Either identifier or targetId is required');
432
+ }
433
+ await ctx.marketSDK.favorites.removeFavorite(input.targetType, targetValue as any);
434
+ return { success: true };
435
+ } catch (error) {
436
+ log('Error removing favorite: %O', error);
437
+ throw new TRPCError({
438
+ cause: error,
439
+ code: 'INTERNAL_SERVER_ERROR',
440
+ message: error instanceof Error ? error.message : 'Failed to remove favorite',
441
+ });
442
+ }
443
+ }),
444
+
445
+ /**
446
+ * Toggle like on an item
447
+ * POST /market/social/toggle-like
448
+ */
449
+ toggleLike: socialAuthProcedure
450
+ .input(
451
+ z.object({
452
+ identifier: z.string().optional(),
453
+ targetId: z.number().optional(),
454
+ targetType: targetTypeSchema,
455
+ }),
456
+ )
457
+ .mutation(async ({ input, ctx }) => {
458
+ log('toggleLike input: %O', input);
459
+
460
+ try {
461
+ const targetValue = input.identifier ?? input.targetId;
462
+ if (!targetValue) {
463
+ throw new Error('Either identifier or targetId is required');
464
+ }
465
+ const result = await ctx.marketSDK.likes.toggleLike(input.targetType, targetValue as any);
466
+ return result;
467
+ } catch (error) {
468
+ log('Error toggling like: %O', error);
469
+ throw new TRPCError({
470
+ cause: error,
471
+ code: 'INTERNAL_SERVER_ERROR',
472
+ message: error instanceof Error ? error.message : 'Failed to toggle like',
473
+ });
474
+ }
475
+ }),
476
+
477
+ /**
478
+ * Unfollow a user
479
+ * POST /market/social/unfollow
480
+ */
481
+ unfollow: socialAuthProcedure
482
+ .input(z.object({ followingId: z.number() }))
483
+ .mutation(async ({ input, ctx }) => {
484
+ log('unfollow input: %O', input);
485
+
486
+ try {
487
+ await ctx.marketSDK.follows.unfollow(input.followingId);
488
+ return { success: true };
489
+ } catch (error) {
490
+ log('Error unfollowing user: %O', error);
491
+ throw new TRPCError({
492
+ cause: error,
493
+ code: 'INTERNAL_SERVER_ERROR',
494
+ message: error instanceof Error ? error.message : 'Failed to unfollow user',
495
+ });
496
+ }
497
+ }),
498
+
499
+ /**
500
+ * Unlike an item
501
+ * POST /market/social/unlike
502
+ */
503
+ unlike: socialAuthProcedure
504
+ .input(
505
+ z.object({
506
+ identifier: z.string().optional(),
507
+ targetId: z.number().optional(),
508
+ targetType: targetTypeSchema,
509
+ }),
510
+ )
511
+ .mutation(async ({ input, ctx }) => {
512
+ log('unlike input: %O', input);
513
+
514
+ try {
515
+ const targetValue = input.identifier ?? input.targetId;
516
+ if (!targetValue) {
517
+ throw new Error('Either identifier or targetId is required');
518
+ }
519
+ await ctx.marketSDK.likes.unlike(input.targetType, targetValue as any);
520
+ return { success: true };
521
+ } catch (error) {
522
+ log('Error unliking item: %O', error);
523
+ throw new TRPCError({
524
+ cause: error,
525
+ code: 'INTERNAL_SERVER_ERROR',
526
+ message: error instanceof Error ? error.message : 'Failed to unlike item',
527
+ });
528
+ }
529
+ }),
530
+ });
531
+
532
+ export type SocialRouter = typeof socialRouter;