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 {
|
import {
|
||||||
deleteRoleById,
|
deleteRoleById,
|
||||||
findAllRoles,
|
|
||||||
findRoleById,
|
findRoleById,
|
||||||
findRoleByRoleName,
|
findRoleByRoleName,
|
||||||
findRolesByRoleIds,
|
findRolesByRoleIds,
|
||||||
|
@ -35,22 +34,6 @@ jest.spyOn(envSet, 'pool', 'get').mockReturnValue(
|
||||||
describe('roles query', () => {
|
describe('roles query', () => {
|
||||||
const { table, fields } = convertToIdentifiers(Roles);
|
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 () => {
|
it('findRolesByRoleIds', async () => {
|
||||||
const roleIds = [mockRole.id];
|
const roleIds = [mockRole.id];
|
||||||
const expectSql = sql`
|
const expectSql = sql`
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import type { CreateRole, Role } from '@logto/schemas';
|
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 type { OmitAutoSetFields } from '@logto/shared';
|
||||||
import { conditionalSql, convertToIdentifiers } from '@logto/shared';
|
import { conditionalSql, convertToIdentifiers } from '@logto/shared';
|
||||||
import { sql } from 'slonik';
|
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 { buildUpdateWhere } from '#src/database/update-where.js';
|
||||||
import envSet from '#src/env-set/index.js';
|
import envSet from '#src/env-set/index.js';
|
||||||
import { DeletionError } from '#src/errors/SlonikError/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);
|
const { table, fields } = convertToIdentifiers(Roles);
|
||||||
|
|
||||||
export const findAllRoles = async () =>
|
const buildRoleConditions = (search: Search) => {
|
||||||
envSet.pool.any<Role>(sql`
|
const hasSearch = search.matches.length > 0;
|
||||||
select ${sql.join(Object.values(fields), sql`, `)}
|
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}
|
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[]) =>
|
export const findRolesByRoleIds = async (roleIds: string[]) =>
|
||||||
roleIds.length > 0
|
roleIds.length > 0
|
||||||
? envSet.pool.any<Role>(sql`
|
? envSet.pool.any<Role>(sql`
|
||||||
|
|
|
@ -11,7 +11,8 @@ const { mockEsm, mockEsmWithActual } = createMockUtils(jest);
|
||||||
const { findRoleByRoleName, findRoleById, deleteRoleById } = mockEsm(
|
const { findRoleByRoleName, findRoleById, deleteRoleById } = mockEsm(
|
||||||
'#src/queries/roles.js',
|
'#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),
|
findRoleByRoleName: jest.fn(async (): Promise<Role | undefined> => undefined),
|
||||||
insertRole: jest.fn(async (data) => ({
|
insertRole: jest.fn(async (data) => ({
|
||||||
...data,
|
...data,
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import { buildIdGenerator } from '@logto/core-kit';
|
import { buildIdGenerator } from '@logto/core-kit';
|
||||||
import type { ScopeResponse } from '@logto/schemas';
|
import type { ScopeResponse } from '@logto/schemas';
|
||||||
import { Roles } from '@logto/schemas';
|
import { Roles } from '@logto/schemas';
|
||||||
|
import { tryThat } from '@logto/shared';
|
||||||
import { object, string, z } from 'zod';
|
import { object, string, z } from 'zod';
|
||||||
|
|
||||||
import RequestError from '#src/errors/RequestError/index.js';
|
import RequestError from '#src/errors/RequestError/index.js';
|
||||||
import koaGuard from '#src/middleware/koa-guard.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 { findResourcesByIds } from '#src/queries/resource.js';
|
||||||
import {
|
import {
|
||||||
deleteRolesScope,
|
deleteRolesScope,
|
||||||
|
@ -12,10 +14,11 @@ import {
|
||||||
insertRolesScopes,
|
insertRolesScopes,
|
||||||
} from '#src/queries/roles-scopes.js';
|
} from '#src/queries/roles-scopes.js';
|
||||||
import {
|
import {
|
||||||
|
countRoles,
|
||||||
deleteRoleById,
|
deleteRoleById,
|
||||||
findAllRoles,
|
|
||||||
findRoleById,
|
findRoleById,
|
||||||
findRoleByRoleName,
|
findRoleByRoleName,
|
||||||
|
findRoles,
|
||||||
insertRole,
|
insertRole,
|
||||||
updateRoleById,
|
updateRoleById,
|
||||||
} from '#src/queries/roles.js';
|
} from '#src/queries/roles.js';
|
||||||
|
@ -28,16 +31,39 @@ import {
|
||||||
insertUsersRoles,
|
insertUsersRoles,
|
||||||
} from '#src/queries/users-roles.js';
|
} from '#src/queries/users-roles.js';
|
||||||
import assertThat from '#src/utils/assert-that.js';
|
import assertThat from '#src/utils/assert-that.js';
|
||||||
|
import { parseSearchParamsForSearch } from '#src/utils/search.js';
|
||||||
|
|
||||||
import type { AuthedRouter } from './types.js';
|
import type { AuthedRouter } from './types.js';
|
||||||
|
|
||||||
const roleId = buildIdGenerator(21);
|
const roleId = buildIdGenerator(21);
|
||||||
|
|
||||||
export default function roleRoutes<T extends AuthedRouter>(router: T) {
|
export default function roleRoutes<T extends AuthedRouter>(router: T) {
|
||||||
router.get('/roles', async (ctx, next) => {
|
router.get('/roles', koaPagination(), async (ctx, next) => {
|
||||||
ctx.body = await findAllRoles();
|
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(
|
router.post(
|
||||||
|
|
Loading…
Reference in a new issue