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:
parent
900b87b9a5
commit
849c7fb784
6 changed files with 61 additions and 24 deletions
|
@ -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}`)}
|
||||
|
|
|
@ -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]);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -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([
|
||||
{
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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 () => {
|
||||
|
|
Loading…
Add table
Reference in a new issue