diff --git a/apps/server/src/api/v2/users/[id]/GET.ts b/apps/server/src/api/v2/users/[id]/GET.ts index 9b463f16d..f76192c86 100644 --- a/apps/server/src/api/v2/users/[id]/GET.ts +++ b/apps/server/src/api/v2/users/[id]/GET.ts @@ -1,22 +1,25 @@ +import { getUserInformation } from '@buster/database'; +import { GetUserByIdRequestSchema, type GetUserByIdResponse } from '@buster/server-shared/user'; +import { zValidator } from '@hono/zod-validator'; import { Hono } from 'hono'; -import suggestedPromptsRoutes from './suggested-prompts'; +import { HTTPException } from 'hono/http-exception'; +import { standardErrorHandler } from '../../../../utils/response'; const app = new Hono() - .get('/', (c) => { + .get('/', zValidator('param', GetUserByIdRequestSchema), async (c) => { const userId = c.req.param('id'); + const authenticatedUser = c.get('busterUser'); - // Stub data for individual user - const stubUser = { - id: userId, - name: 'Example User', - email: `user${userId}@example.com`, - role: 'user', - createdAt: '2024-01-01T00:00:00Z', - updatedAt: '2024-01-01T00:00:00Z', - }; + if (authenticatedUser.id !== userId) { + throw new HTTPException(403, { + message: 'You are not authorized to access this user', + }); + } - return c.json(stubUser); + const userInfo: GetUserByIdResponse = await getUserInformation(userId); + + return c.json(userInfo); }) - .route('/suggested-prompts', suggestedPromptsRoutes); + .onError(standardErrorHandler); export default app; diff --git a/packages/database/src/queries/users/user-queries.ts b/packages/database/src/queries/users/user-queries.ts index 7c8c23368..3c0a6abab 100644 --- a/packages/database/src/queries/users/user-queries.ts +++ b/packages/database/src/queries/users/user-queries.ts @@ -1,4 +1,5 @@ import { and, eq, isNull } from 'drizzle-orm'; +import { z } from 'zod'; import { db } from '../../connection'; import { users, usersToOrganizations } from '../../schema'; import type { User } from './user'; @@ -6,6 +7,15 @@ import type { User } from './user'; // Use the full User type from the schema internally type FullUser = typeof users.$inferSelect; +export const UserInfoByIdResponseSchema = z.object({ + id: z.string().uuid(), + name: z.string().nullable(), + email: z.string().email(), + role: z.string(), + status: z.string(), +}); +export type UserInfoByIdResponse = z.infer; + /** * Converts a full user to the public User type */ @@ -142,3 +152,31 @@ export async function addUserToOrganization( throw error; } } + +/** + * Get comprehensive user information including datasets and permissions + * This function replaces the complex Rust implementation with TypeScript + */ +export async function getUserInformation(userId: string): Promise { + // Get user basic info and organization relationship + const userInfo = await db + .select({ + id: users.id, + email: users.email, + name: users.name, + role: usersToOrganizations.role, + status: usersToOrganizations.status, + organizationId: usersToOrganizations.organizationId, + }) + .from(users) + .innerJoin(usersToOrganizations, eq(users.id, usersToOrganizations.userId)) + .where(and(eq(users.id, userId), isNull(usersToOrganizations.deletedAt))) + .limit(1); + + if (userInfo.length === 0 || !userInfo[0]) { + throw new Error(`User not found: ${userId}`); + } + + const user: UserInfoByIdResponse = userInfo[0]; + return user; +} diff --git a/packages/server-shared/src/user/users.types.ts b/packages/server-shared/src/user/users.types.ts index 0681ef0df..a00504fec 100644 --- a/packages/server-shared/src/user/users.types.ts +++ b/packages/server-shared/src/user/users.types.ts @@ -1,3 +1,4 @@ +import type { UserInfoByIdResponse } from '@buster/database'; import { z } from 'zod'; import type { UserFavorite } from './favorites.types'; import type { UserOrganizationRole } from './roles.types'; @@ -19,3 +20,10 @@ export const UserSchema = z.object({ }); export type User = z.infer; + +export const GetUserByIdRequestSchema = z.object({ + id: z.string().uuid(), +}); + +export type GetUserByIdRequest = z.infer; +export type GetUserByIdResponse = UserInfoByIdResponse;