0
Fork 0
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:
wangsijie 2023-01-08 20:16:40 +08:00 committed by GitHub
parent 40980b3ada
commit 67b1885b12
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 66 additions and 26 deletions

View file

@ -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`

View file

@ -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`

View file

@ -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,

View file

@ -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(