diff --git a/server/src/internal/platform/handlers/handleListPlatformUsers.ts b/server/src/internal/platform/handlers/handleListPlatformUsers.ts index b1c9add8c..df25f1866 100644 --- a/server/src/internal/platform/handlers/handleListPlatformUsers.ts +++ b/server/src/internal/platform/handlers/handleListPlatformUsers.ts @@ -7,7 +7,7 @@ import { organizations, user as userTable, } from "@autumn/shared"; -import { and, eq, inArray } from "drizzle-orm"; +import { eq } from "drizzle-orm"; import { createRoute } from "@/honoMiddlewares/routeHandler.js"; /** @@ -23,23 +23,46 @@ export const listPlatformUsers = createRoute({ const shouldExpandOrgs = query.expand?.includes("organizations"); + // Build query with conditional joins + let baseQuery = db + .select({ + userId: userTable.id, + userName: userTable.name, + userEmail: userTable.email, + userCreatedAt: userTable.createdAt, + ...(shouldExpandOrgs && { + orgSlug: organizations.slug, + orgName: organizations.name, + orgCreatedAt: organizations.createdAt, + }), + }) + .from(userTable); + + // Conditionally add joins only when expanding orgs + if (shouldExpandOrgs) { + baseQuery = baseQuery + .innerJoin(member, eq(member.userId, userTable.id)) + .innerJoin( + organizations, + eq(organizations.id, member.organizationId), + ) as typeof baseQuery; + } + + // Apply filters and pagination + const results = await baseQuery + .where( + shouldExpandOrgs + ? eq(organizations.created_by, org.id) + : eq(userTable.createdBy, org.id), + ) + .limit(query.limit) + .offset(query.offset); + + logger.info(`Found ${results.length} platform users`); + if (!shouldExpandOrgs) { - // Simple case: just get users without organizations (no join needed) - const usersData = await db - .select({ - userId: userTable.id, - userName: userTable.name, - userEmail: userTable.email, - userCreatedAt: userTable.createdAt, - }) - .from(userTable) - .where(eq(userTable.createdBy, org.id)) - .limit(query.limit) - .offset(query.offset); - - logger.info(`Found ${usersData.length} platform users`); - - const users: ApiPlatformUser[] = usersData.map((userData) => ({ + // Simple case: just map users directly + const users: ApiPlatformUser[] = results.map((userData) => ({ name: userData.userName, email: userData.userEmail, created_at: userData.userCreatedAt.getTime(), @@ -53,52 +76,7 @@ export const listPlatformUsers = createRoute({ }); } - // Complex case: get users WITH their organizations - // First, get the paginated user IDs (no join needed - users have created_by) - const paginatedUsers = await db - .select({ - userId: userTable.id, - }) - .from(userTable) - .where(eq(userTable.createdBy, org.id)) - .limit(query.limit) - .offset(query.offset); - - if (paginatedUsers.length === 0) { - return c.json({ - list: [], - total: 0, - limit: query.limit, - offset: query.offset, - }); - } - - const userIds = paginatedUsers.map((u) => u.userId); - - // Now fetch all user and org data in one query - const allData = await db - .select({ - userId: userTable.id, - userName: userTable.name, - userEmail: userTable.email, - userCreatedAt: userTable.createdAt, - orgSlug: organizations.slug, - orgName: organizations.name, - orgCreatedAt: organizations.createdAt, - }) - .from(userTable) - .innerJoin(member, eq(member.userId, userTable.id)) - .innerJoin(organizations, eq(organizations.id, member.organizationId)) - .where( - and( - eq(organizations.created_by, org.id), - inArray(userTable.id, userIds), - ), - ); - - logger.info(`Found ${allData.length} user-org relationships`); - - // Group data by user + // Complex case: group organizations by user const usersMap = new Map< string, { @@ -109,7 +87,7 @@ export const listPlatformUsers = createRoute({ } >(); - for (const row of allData) { + for (const row of results) { if (!usersMap.has(row.userId)) { usersMap.set(row.userId, { name: row.userName, @@ -122,7 +100,7 @@ export const listPlatformUsers = createRoute({ const userData = usersMap.get(row.userId)!; // Limit to 100 organizations per user - if (userData.organizations.length < 100) { + if (userData.organizations.length < 100 && row.orgSlug) { // Remove the master org slug prefix from the organization slug let cleanedSlug = row.orgSlug; const prefix = `${org.id}_`; @@ -137,16 +115,14 @@ export const listPlatformUsers = createRoute({ userData.organizations.push({ slug: cleanedSlug, - name: row.orgName, - created_at: row.orgCreatedAt.getTime(), + name: row.orgName!, + created_at: row.orgCreatedAt!.getTime(), }); } } - // Convert map to array, preserving pagination order - const users: ApiPlatformUser[] = userIds - .map((userId) => usersMap.get(userId)) - .filter((user): user is NonNullable => user !== undefined); + // Convert map to array + const users: ApiPlatformUser[] = Array.from(usersMap.values()); return c.json({ list: users,