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

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.
@@ -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;
@@ -0,0 +1,123 @@
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:user');
9
+
10
+ // Authenticated procedure for user profile updates
11
+ const userAuthProcedure = authedProcedure.use(serverDatabase).use(marketUserInfo).use(marketSDK);
12
+
13
+ // Public procedure for viewing user profiles
14
+ const userPublicProcedure = publicProcedure.use(serverDatabase).use(marketUserInfo).use(marketSDK);
15
+
16
+ // Schema definitions
17
+ const socialLinksSchema = z.object({
18
+ github: z.string().optional(),
19
+ twitter: z.string().optional(),
20
+ website: z.string().optional(),
21
+ });
22
+
23
+ const userMetaSchema = z.object({
24
+ bannerUrl: z.string().optional(),
25
+ description: z.string().optional(),
26
+ socialLinks: socialLinksSchema.optional(),
27
+ });
28
+
29
+ const updateUserProfileSchema = z.object({
30
+ avatarUrl: z.string().optional(),
31
+ displayName: z.string().optional(),
32
+ meta: userMetaSchema.optional(),
33
+ userName: z.string().optional(),
34
+ });
35
+
36
+ export const userRouter = router({
37
+ /**
38
+ * Get user profile by username
39
+ * GET /market/user/[username]
40
+ */
41
+ getUserByUsername: userPublicProcedure
42
+ .input(z.object({ username: z.string() }))
43
+ .query(async ({ input, ctx }) => {
44
+ log('getUserByUsername input: %O', input);
45
+
46
+ try {
47
+ const response = await ctx.marketSDK.user.getUserInfo(input.username);
48
+
49
+ if (!response?.user) {
50
+ throw new TRPCError({
51
+ code: 'NOT_FOUND',
52
+ message: `User not found: ${input.username}`,
53
+ });
54
+ }
55
+
56
+ const { user } = response;
57
+
58
+ return {
59
+ avatarUrl: user.avatarUrl || null,
60
+ bannerUrl: user.meta?.bannerUrl || null,
61
+ createdAt: user.createdAt,
62
+ description: user.meta?.description || null,
63
+ displayName: user.displayName || null,
64
+ id: user.id,
65
+ namespace: user.namespace,
66
+ socialLinks: user.meta?.socialLinks || null,
67
+ type: user.type || null,
68
+ userName: user.userName || null,
69
+ };
70
+ } catch (error) {
71
+ if (error instanceof TRPCError) throw error;
72
+
73
+ log('Error getting user profile: %O', error);
74
+ throw new TRPCError({
75
+ cause: error,
76
+ code: 'INTERNAL_SERVER_ERROR',
77
+ message: error instanceof Error ? error.message : 'Failed to get user profile',
78
+ });
79
+ }
80
+ }),
81
+
82
+ /**
83
+ * Update current user's profile
84
+ * PUT /market/user/me
85
+ */
86
+ updateUserProfile: userAuthProcedure
87
+ .input(updateUserProfileSchema)
88
+ .mutation(async ({ input, ctx }) => {
89
+ log('updateUserProfile input: %O', input);
90
+
91
+ try {
92
+ // Ensure meta is at least an empty object
93
+ const normalizedPayload = {
94
+ ...input,
95
+ meta: input.meta ?? {},
96
+ };
97
+
98
+ const response = await ctx.marketSDK.user.updateUserInfo(normalizedPayload);
99
+ return response;
100
+ } catch (error) {
101
+ log('Error updating user profile: %O', error);
102
+
103
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
104
+ const isUserNameTaken = errorMessage.toLowerCase().includes('already taken');
105
+
106
+ if (isUserNameTaken) {
107
+ throw new TRPCError({
108
+ cause: error,
109
+ code: 'CONFLICT',
110
+ message: 'Username is already taken',
111
+ });
112
+ }
113
+
114
+ throw new TRPCError({
115
+ cause: error,
116
+ code: 'INTERNAL_SERVER_ERROR',
117
+ message: errorMessage,
118
+ });
119
+ }
120
+ }),
121
+ });
122
+
123
+ export type UserRouter = typeof userRouter;