From 225ccfed0a6fa4db5b250955096c1be9382332c0 Mon Sep 17 00:00:00 2001 From: wangsijie Date: Mon, 9 Jan 2023 13:03:28 +0800 Subject: [PATCH] feat(core): count role users and featured users (#2866) --- packages/core/src/queries/users-roles.ts | 12 ++++++++++-- packages/core/src/routes/role.test.ts | 22 +++++++++++++++++++--- packages/core/src/routes/role.ts | 19 +++++++++++++++++-- packages/schemas/src/types/index.ts | 1 + packages/schemas/src/types/role.ts | 6 ++++++ 5 files changed, 53 insertions(+), 7 deletions(-) create mode 100644 packages/schemas/src/types/role.ts diff --git a/packages/core/src/queries/users-roles.ts b/packages/core/src/queries/users-roles.ts index 977209c5b..35cf0329e 100644 --- a/packages/core/src/queries/users-roles.ts +++ b/packages/core/src/queries/users-roles.ts @@ -1,6 +1,6 @@ import type { UsersRole } from '@logto/schemas'; import { UsersRoles } from '@logto/schemas'; -import { convertToIdentifiers } from '@logto/shared'; +import { conditionalSql, convertToIdentifiers } from '@logto/shared'; import { sql } from 'slonik'; import envSet from '#src/env-set/index.js'; @@ -14,11 +14,19 @@ export const findUsersRolesByUserId = async (userId: string) => where ${fields.userId}=${userId} `); -export const findUsersRolesByRoleId = async (roleId: string) => +export const findUsersRolesByRoleId = async (roleId: string, limit?: number) => envSet.pool.any(sql` select ${sql.join(Object.values(fields), sql`,`)} from ${table} where ${fields.roleId}=${roleId} + ${conditionalSql(limit, (value) => sql`limit ${value}`)} + `); + +export const countUsersRolesByRoleId = async (roleId: string) => + envSet.pool.one<{ count: number }>(sql` + select count(*) + from ${table} + where ${fields.roleId}=${roleId} `); export const findFirstUsersRolesByRoleIdAndUserIds = async (roleId: string, userIds: string[]) => diff --git a/packages/core/src/routes/role.test.ts b/packages/core/src/routes/role.test.ts index e43d5b118..deb743aa0 100644 --- a/packages/core/src/routes/role.test.ts +++ b/packages/core/src/routes/role.test.ts @@ -51,8 +51,10 @@ const { findUsersRolesByRoleId, deleteUsersRolesByUserIdAndRoleId, findFirstUsersRolesByRoleIdAndUserIds, + countUsersRolesByRoleId, } = await mockEsmWithActual('#src/queries/users-roles.js', () => ({ insertUsersRoles: jest.fn(), + countUsersRolesByRoleId: jest.fn(), findUsersRolesByRoleId: jest.fn(), findFirstUsersRolesByRoleIdAndUserIds: jest.fn(), deleteUsersRolesByUserIdAndRoleId: jest.fn(), @@ -62,10 +64,24 @@ const roleRoutes = await pickDefault(import('./role.js')); describe('role routes', () => { const roleRequester = createRequester({ authedRoutes: roleRoutes }); - it('GET /roles', async () => { - const response = await roleRequester.get('/roles'); + it('GET /roles?page=1', async () => { + countUsersRolesByRoleId.mockResolvedValueOnce({ count: 1 }); + findUsersByIds.mockResolvedValueOnce([mockUser]); + findUsersRolesByRoleId.mockResolvedValueOnce([]); + const response = await roleRequester.get('/roles?page=1&page_size=20'); expect(response.status).toEqual(200); - expect(response.body).toEqual([mockRole]); + expect(response.body).toEqual([ + { + ...mockRole, + usersCount: 1, + featuredUsers: [ + { + id: mockUser.id, + avatar: mockUser.avatar, + }, + ], + }, + ]); }); it('POST /roles', async () => { diff --git a/packages/core/src/routes/role.ts b/packages/core/src/routes/role.ts index a2ae77075..c02d416b2 100644 --- a/packages/core/src/routes/role.ts +++ b/packages/core/src/routes/role.ts @@ -1,5 +1,5 @@ import { buildIdGenerator } from '@logto/core-kit'; -import type { ScopeResponse } from '@logto/schemas'; +import type { RoleResponse, ScopeResponse } from '@logto/schemas'; import { Roles } from '@logto/schemas'; import { tryThat } from '@logto/shared'; import { object, string, z } from 'zod'; @@ -25,6 +25,7 @@ import { import { findScopeById, findScopesByIds } from '#src/queries/scope.js'; import { findUserById, findUsersByIds } from '#src/queries/user.js'; import { + countUsersRolesByRoleId, deleteUsersRolesByUserIdAndRoleId, findFirstUsersRolesByRoleIdAndUserIds, findUsersRolesByRoleId, @@ -57,9 +58,23 @@ export default function roleRoutes(router: T) { findRoles(search, limit, offset), ]); + const rolesResponse: RoleResponse[] = await Promise.all( + roles.map(async (role) => { + const { count } = await countUsersRolesByRoleId(role.id); + const usersRoles = await findUsersRolesByRoleId(role.id, 3); + const users = await findUsersByIds(usersRoles.map(({ userId }) => userId)); + + return { + ...role, + usersCount: count, + featuredUsers: users.map(({ id, avatar }) => ({ id, avatar })), + }; + }) + ); + // Return totalCount to pagination middleware ctx.pagination.totalCount = count; - ctx.body = roles; + ctx.body = rolesResponse; return next(); }, diff --git a/packages/schemas/src/types/index.ts b/packages/schemas/src/types/index.ts index 51ff41cff..b5330f7d9 100644 --- a/packages/schemas/src/types/index.ts +++ b/packages/schemas/src/types/index.ts @@ -7,3 +7,4 @@ export * from './interactions.js'; export * from './search.js'; export * from './resource.js'; export * from './scope.js'; +export * from './role.js'; diff --git a/packages/schemas/src/types/role.ts b/packages/schemas/src/types/role.ts new file mode 100644 index 000000000..7a1868149 --- /dev/null +++ b/packages/schemas/src/types/role.ts @@ -0,0 +1,6 @@ +import type { Role, User } from '../db-entries/index.js'; + +export type RoleResponse = Role & { + usersCount: number; + featuredUsers: Array>; +};