0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-04-14 23:11:31 -05:00

feat(core): add pagination and search to user roles api (#2923)

This commit is contained in:
wangsijie 2023-01-12 17:40:45 +08:00 committed by GitHub
parent 900b87b9a5
commit 849c7fb784
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 61 additions and 24 deletions

View file

@ -28,7 +28,10 @@ const buildRoleConditions = (search: Search) => {
export const defaultUserSearch = { matches: [], isCaseSensitive: false, joint: SearchJointMode.Or };
export const createRolesQueries = (pool: CommonQueryMethods) => {
const countRoles = async (search: Search = defaultUserSearch, excludeRoleIds: string[] = []) =>
const countRoles = async (
search: Search = defaultUserSearch,
{ excludeRoleIds = [], roleIds = [] }: { excludeRoleIds?: string[]; roleIds?: string[] } = {}
) =>
pool.one<{ count: number }>(sql`
select count(*)
from ${table}
@ -37,14 +40,19 @@ export const createRolesQueries = (pool: CommonQueryMethods) => {
excludeRoleIds,
(value) => sql`and ${fields.id} not in (${sql.join(value, sql`, `)})`
)}
${conditionalSql(
roleIds,
(value) =>
sql`and ${fields.id} in (${value.length > 0 ? sql.join(value, sql`, `) : sql`null`})`
)}
${buildRoleConditions(search)}
`);
const findRoles = async (
search: Search,
excludeRoleIds: string[] = [],
limit?: number,
offset?: number
offset?: number,
{ excludeRoleIds = [], roleIds }: { excludeRoleIds?: string[]; roleIds?: string[] } = {}
) =>
pool.any<Role>(
sql`
@ -55,6 +63,11 @@ export const createRolesQueries = (pool: CommonQueryMethods) => {
excludeRoleIds,
(value) => sql`and ${fields.id} not in (${sql.join(value, sql`, `)})`
)}
${conditionalSql(
roleIds,
(value) =>
sql`and ${fields.id} in (${value.length > 0 ? sql.join(value, sql`, `) : sql`null`})`
)}
${buildRoleConditions(search)}
${conditionalSql(limit, (value) => sql`limit ${value}`)}
${conditionalSql(offset, (value) => sql`offset ${value}`)}

View file

@ -11,6 +11,8 @@ const users = { findUserById: jest.fn() };
const roles = {
findRolesByRoleIds: jest.fn(),
findRoleById: jest.fn(),
countRoles: jest.fn(async () => ({ count: 1 })),
findRoles: jest.fn(async () => [mockRole]),
};
const { findRolesByRoleIds } = roles;
@ -30,7 +32,6 @@ describe('user role routes', () => {
it('GET /users/:id/roles', async () => {
findUsersRolesByUserId.mockResolvedValueOnce([]);
findRolesByRoleIds.mockResolvedValueOnce([mockRole]);
const response = await roleRequester.get(`/users/${mockUser.id}/roles`);
expect(response.status).toEqual(200);
expect(response.body).toEqual([mockRole]);

View file

@ -1,8 +1,11 @@
import { tryThat } from '@logto/shared';
import { object, string } from 'zod';
import RequestError from '#src/errors/RequestError/index.js';
import koaGuard from '#src/middleware/koa-guard.js';
import koaPagination from '#src/middleware/koa-pagination.js';
import assertThat from '#src/utils/assert-that.js';
import { parseSearchParamsForSearch } from '#src/utils/search.js';
import type { AuthedRouter, RouterInitArgs } from './types.js';
@ -10,13 +13,14 @@ export default function adminUserRoleRoutes<T extends AuthedRouter>(
...[router, { queries }]: RouterInitArgs<T>
) {
const {
roles: { findRolesByRoleIds, findRoleById },
roles: { findRoleById, countRoles, findRoles },
users: { findUserById },
usersRoles: { deleteUsersRolesByUserIdAndRoleId, findUsersRolesByUserId, insertUsersRoles },
} = queries;
router.get(
'/users/:userId/roles',
koaPagination(),
koaGuard({
params: object({ userId: string() }),
}),
@ -24,14 +28,37 @@ export default function adminUserRoleRoutes<T extends AuthedRouter>(
const {
params: { userId },
} = ctx.guard;
const { limit, offset } = ctx.pagination;
const { searchParams } = ctx.request.URL;
await findUserById(userId);
const usersRoles = await findUsersRolesByUserId(userId);
const roles = await findRolesByRoleIds(usersRoles.map(({ roleId }) => roleId));
ctx.body = roles;
return tryThat(
async () => {
const search = parseSearchParamsForSearch(searchParams);
return next();
const usersRoles = await findUsersRolesByUserId(userId);
const roleIds = usersRoles.map(({ roleId }) => roleId);
const [{ count }, roles] = await Promise.all([
countRoles(search, { roleIds }),
findRoles(search, limit, offset, { roleIds }),
]);
// Return totalCount to pagination middleware
ctx.pagination.totalCount = count;
ctx.body = roles;
return next();
},
(error) => {
if (error instanceof TypeError) {
throw new RequestError(
{ code: 'request.invalid_input', details: error.message },
error
);
}
throw error;
}
);
}
);

View file

@ -78,11 +78,11 @@ const tenantContext = new MockTenant(undefined, {
describe('role routes', () => {
const roleRequester = createRequester({ authedRoutes: roleRoutes, tenantContext });
it('GET /roles?page=1', async () => {
it('GET /roles', async () => {
countUsersRolesByRoleId.mockResolvedValueOnce({ count: 1 });
findUsersByIds.mockResolvedValueOnce([mockUser]);
findUsersRolesByRoleId.mockResolvedValueOnce([]);
const response = await roleRequester.get('/roles?page=1&page_size=20');
const response = await roleRequester.get('/roles');
expect(response.status).toEqual(200);
expect(response.body).toEqual([
{

View file

@ -41,8 +41,8 @@ export default function roleRoutes<T extends AuthedRouter>(
},
} = queries;
router.get('/roles', koaPagination({ isOptional: true }), async (ctx, next) => {
const { limit, offset, disabled } = ctx.pagination;
router.get('/roles', koaPagination(), async (ctx, next) => {
const { limit, offset } = ctx.pagination;
const { searchParams } = ctx.request.URL;
return tryThat(
@ -52,15 +52,9 @@ export default function roleRoutes<T extends AuthedRouter>(
const usersRoles = excludeUserId ? await findUsersRolesByUserId(excludeUserId) : [];
const excludeRoleIds = usersRoles.map(({ roleId }) => roleId);
if (disabled) {
ctx.body = await findRoles(search, excludeRoleIds);
return next();
}
const [{ count }, roles] = await Promise.all([
countRoles(search, excludeRoleIds),
findRoles(search, excludeRoleIds, limit, offset),
countRoles(search, { excludeRoleIds }),
findRoles(search, limit, offset, { excludeRoleIds }),
]);
const rolesResponse: RoleResponse[] = await Promise.all(

View file

@ -2,6 +2,7 @@ import { adminConsoleAdminRoleId } from '@logto/schemas';
import { HTTPError } from 'got';
import { assignRolesToUser, getUserRoles, deleteRoleFromUser } from '#src/api/index.js';
import { createRole } from '#src/api/role.js';
import { createUserByAdmin } from '#src/helpers/index.js';
describe('admin console user management (roles)', () => {
@ -14,10 +15,11 @@ describe('admin console user management (roles)', () => {
it('should assign role to user and get list successfully', async () => {
const user = await createUserByAdmin();
const role = await createRole();
await assignRolesToUser(user.id, [adminConsoleAdminRoleId]);
await assignRolesToUser(user.id, [role.id]);
const roles = await getUserRoles(user.id);
expect(roles[0]).toHaveProperty('id', adminConsoleAdminRoleId);
expect(roles[0]).toHaveProperty('id', role.id);
});
it('should delete role from user successfully', async () => {