mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
feat(core): add search and pagination for roles (#2836)
This commit is contained in:
parent
40980b3ada
commit
67b1885b12
4 changed files with 66 additions and 26 deletions
|
@ -10,7 +10,6 @@ import { expectSqlAssert } from '#src/utils/test-utils.js';
|
|||
|
||||
import {
|
||||
deleteRoleById,
|
||||
findAllRoles,
|
||||
findRoleById,
|
||||
findRoleByRoleName,
|
||||
findRolesByRoleIds,
|
||||
|
@ -35,22 +34,6 @@ jest.spyOn(envSet, 'pool', 'get').mockReturnValue(
|
|||
describe('roles query', () => {
|
||||
const { table, fields } = convertToIdentifiers(Roles);
|
||||
|
||||
it('findAllRoles', async () => {
|
||||
const expectSql = sql`
|
||||
select ${sql.join(Object.values(fields), sql`, `)}
|
||||
from ${table}
|
||||
`;
|
||||
|
||||
mockQuery.mockImplementationOnce(async (sql, values) => {
|
||||
expectSqlAssert(sql, expectSql.sql);
|
||||
expect(values).toEqual([]);
|
||||
|
||||
return createMockQueryResult([mockRole]);
|
||||
});
|
||||
|
||||
await expect(findAllRoles()).resolves.toEqual([mockRole]);
|
||||
});
|
||||
|
||||
it('findRolesByRoleIds', async () => {
|
||||
const roleIds = [mockRole.id];
|
||||
const expectSql = sql`
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import type { CreateRole, Role } from '@logto/schemas';
|
||||
import { Roles } from '@logto/schemas';
|
||||
import { SearchJointMode, Roles } from '@logto/schemas';
|
||||
import type { OmitAutoSetFields } from '@logto/shared';
|
||||
import { conditionalSql, convertToIdentifiers } from '@logto/shared';
|
||||
import { sql } from 'slonik';
|
||||
|
@ -9,14 +9,44 @@ import { buildInsertInto } from '#src/database/insert-into.js';
|
|||
import { buildUpdateWhere } from '#src/database/update-where.js';
|
||||
import envSet from '#src/env-set/index.js';
|
||||
import { DeletionError } from '#src/errors/SlonikError/index.js';
|
||||
import type { Search } from '#src/utils/search.js';
|
||||
import { buildConditionsFromSearch } from '#src/utils/search.js';
|
||||
|
||||
const { table, fields } = convertToIdentifiers(Roles);
|
||||
|
||||
export const findAllRoles = async () =>
|
||||
envSet.pool.any<Role>(sql`
|
||||
select ${sql.join(Object.values(fields), sql`, `)}
|
||||
const buildRoleConditions = (search: Search) => {
|
||||
const hasSearch = search.matches.length > 0;
|
||||
const searchFields = [Roles.fields.id, Roles.fields.name, Roles.fields.description];
|
||||
|
||||
return conditionalSql(
|
||||
hasSearch,
|
||||
() => sql`where ${buildConditionsFromSearch(search, searchFields)}`
|
||||
);
|
||||
};
|
||||
|
||||
export const defaultUserSearch = { matches: [], isCaseSensitive: false, joint: SearchJointMode.Or };
|
||||
|
||||
export const countRoles = async (search: Search = defaultUserSearch) =>
|
||||
envSet.pool.one<{ count: number }>(sql`
|
||||
select count(*)
|
||||
from ${table}
|
||||
${buildRoleConditions(search)}
|
||||
`);
|
||||
|
||||
export const findRoles = async (limit: number, offset: number, search: Search) =>
|
||||
envSet.pool.any<Role>(
|
||||
sql`
|
||||
select ${sql.join(
|
||||
Object.values(fields).map((field) => sql`${table}.${field}`),
|
||||
sql`,`
|
||||
)}
|
||||
from ${table}
|
||||
${buildRoleConditions(search)}
|
||||
limit ${limit}
|
||||
offset ${offset}
|
||||
`
|
||||
);
|
||||
|
||||
export const findRolesByRoleIds = async (roleIds: string[]) =>
|
||||
roleIds.length > 0
|
||||
? envSet.pool.any<Role>(sql`
|
||||
|
|
|
@ -11,7 +11,8 @@ const { mockEsm, mockEsmWithActual } = createMockUtils(jest);
|
|||
const { findRoleByRoleName, findRoleById, deleteRoleById } = mockEsm(
|
||||
'#src/queries/roles.js',
|
||||
() => ({
|
||||
findAllRoles: jest.fn(async (): Promise<Role[]> => [mockRole]),
|
||||
findRoles: jest.fn(async (): Promise<Role[]> => [mockRole]),
|
||||
countRoles: jest.fn(async () => ({ count: 10 })),
|
||||
findRoleByRoleName: jest.fn(async (): Promise<Role | undefined> => undefined),
|
||||
insertRole: jest.fn(async (data) => ({
|
||||
...data,
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import { buildIdGenerator } from '@logto/core-kit';
|
||||
import type { ScopeResponse } from '@logto/schemas';
|
||||
import { Roles } from '@logto/schemas';
|
||||
import { tryThat } from '@logto/shared';
|
||||
import { object, string, z } 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 { findResourcesByIds } from '#src/queries/resource.js';
|
||||
import {
|
||||
deleteRolesScope,
|
||||
|
@ -12,10 +14,11 @@ import {
|
|||
insertRolesScopes,
|
||||
} from '#src/queries/roles-scopes.js';
|
||||
import {
|
||||
countRoles,
|
||||
deleteRoleById,
|
||||
findAllRoles,
|
||||
findRoleById,
|
||||
findRoleByRoleName,
|
||||
findRoles,
|
||||
insertRole,
|
||||
updateRoleById,
|
||||
} from '#src/queries/roles.js';
|
||||
|
@ -28,16 +31,39 @@ import {
|
|||
insertUsersRoles,
|
||||
} from '#src/queries/users-roles.js';
|
||||
import assertThat from '#src/utils/assert-that.js';
|
||||
import { parseSearchParamsForSearch } from '#src/utils/search.js';
|
||||
|
||||
import type { AuthedRouter } from './types.js';
|
||||
|
||||
const roleId = buildIdGenerator(21);
|
||||
|
||||
export default function roleRoutes<T extends AuthedRouter>(router: T) {
|
||||
router.get('/roles', async (ctx, next) => {
|
||||
ctx.body = await findAllRoles();
|
||||
router.get('/roles', koaPagination(), async (ctx, next) => {
|
||||
const { limit, offset } = ctx.pagination;
|
||||
const { searchParams } = ctx.request.URL;
|
||||
|
||||
return next();
|
||||
return tryThat(
|
||||
async () => {
|
||||
const search = parseSearchParamsForSearch(searchParams);
|
||||
|
||||
const [{ count }, roles] = await Promise.all([
|
||||
countRoles(search),
|
||||
findRoles(limit, offset, search),
|
||||
]);
|
||||
|
||||
// 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;
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
router.post(
|
||||
|
|
Loading…
Reference in a new issue