mirror of
https://github.com/logto-io/logto.git
synced 2025-01-20 21:32:31 -05:00
feat: add case insensitive user search (#2300)
Co-authored-by: wangsijie <wangsijie94@gmail.com> Co-authored-by: Charles Zhao <charleszhao@silverhand.io> Co-authored-by: wangsijie <wangsijie@silverhand.io>
This commit is contained in:
parent
492ce312be
commit
938608e72b
3 changed files with 94 additions and 20 deletions
|
@ -240,7 +240,7 @@ describe('user query', () => {
|
|||
const expectSql = sql`
|
||||
select count(*)
|
||||
from ${table}
|
||||
where ${fields.primaryEmail} like $1 or ${fields.primaryPhone} like $2 or ${fields.username} like $3 or ${fields.name} like $4
|
||||
where ${fields.primaryEmail} ilike $1 or ${fields.primaryPhone} ilike $2 or ${fields.username} ilike $3 or ${fields.name} ilike $4
|
||||
`;
|
||||
|
||||
mockQuery.mockImplementationOnce(async (sql, values) => {
|
||||
|
@ -259,7 +259,7 @@ describe('user query', () => {
|
|||
select count(*)
|
||||
from ${table}
|
||||
where not (${fields.roleNames}::jsonb?$1)
|
||||
and (${fields.primaryEmail} like $2 or ${fields.primaryPhone} like $3 or ${fields.username} like $4 or ${fields.name} like $5)
|
||||
and (${fields.primaryEmail} ilike $2 or ${fields.primaryPhone} ilike $3 or ${fields.username} ilike $4 or ${fields.name} ilike $5)
|
||||
`;
|
||||
|
||||
mockQuery.mockImplementationOnce(async (sql, values) => {
|
||||
|
@ -278,6 +278,24 @@ describe('user query', () => {
|
|||
await expect(countUsers(search, true)).resolves.toEqual(dbvalue);
|
||||
});
|
||||
|
||||
it('countUsers with isCaseSensitive', async () => {
|
||||
const search = 'foo';
|
||||
const expectSql = sql`
|
||||
select count(*)
|
||||
from ${table}
|
||||
where ${fields.primaryEmail} like $1 or ${fields.primaryPhone} like $2 or ${fields.username} like $3 or ${fields.name} like $4
|
||||
`;
|
||||
|
||||
mockQuery.mockImplementationOnce(async (sql, values) => {
|
||||
expectSqlAssert(sql, expectSql.sql);
|
||||
expect(values).toEqual([`%${search}%`, `%${search}%`, `%${search}%`, `%${search}%`]);
|
||||
|
||||
return createMockQueryResult([dbvalue]);
|
||||
});
|
||||
|
||||
await expect(countUsers(search, undefined, true)).resolves.toEqual(dbvalue);
|
||||
});
|
||||
|
||||
it('findUsers', async () => {
|
||||
const search = 'foo';
|
||||
const limit = 100;
|
||||
|
@ -285,9 +303,9 @@ describe('user query', () => {
|
|||
const expectSql = sql`
|
||||
select ${sql.join(Object.values(fields), sql`,`)}
|
||||
from ${table}
|
||||
where ${fields.primaryEmail} like $1 or ${fields.primaryPhone} like $2 or ${
|
||||
where ${fields.primaryEmail} ilike $1 or ${fields.primaryPhone} ilike $2 or ${
|
||||
fields.username
|
||||
} like $3 or ${fields.name} like $4
|
||||
} ilike $3 or ${fields.name} ilike $4
|
||||
limit $5
|
||||
offset $6
|
||||
`;
|
||||
|
@ -317,9 +335,9 @@ describe('user query', () => {
|
|||
select ${sql.join(Object.values(fields), sql`,`)}
|
||||
from ${table}
|
||||
where not (${fields.roleNames}::jsonb?$1)
|
||||
and (${fields.primaryEmail} like $2 or ${fields.primaryPhone} like $3 or ${
|
||||
and (${fields.primaryEmail} ilike $2 or ${fields.primaryPhone} ilike $3 or ${
|
||||
fields.username
|
||||
} like $4 or ${fields.name} like $5)
|
||||
} ilike $4 or ${fields.name} ilike $5)
|
||||
limit $6
|
||||
offset $7
|
||||
`;
|
||||
|
@ -342,6 +360,37 @@ describe('user query', () => {
|
|||
await expect(findUsers(limit, offset, search, true)).resolves.toEqual([dbvalue]);
|
||||
});
|
||||
|
||||
it('findUsers with isCaseSensitive', async () => {
|
||||
const search = 'foo';
|
||||
const limit = 100;
|
||||
const offset = 1;
|
||||
const expectSql = sql`
|
||||
select ${sql.join(Object.values(fields), sql`,`)}
|
||||
from ${table}
|
||||
where ${fields.primaryEmail} like $1 or ${fields.primaryPhone} like $2 or ${
|
||||
fields.username
|
||||
} like $3 or ${fields.name} like $4
|
||||
limit $5
|
||||
offset $6
|
||||
`;
|
||||
|
||||
mockQuery.mockImplementationOnce(async (sql, values) => {
|
||||
expectSqlAssert(sql, expectSql.sql);
|
||||
expect(values).toEqual([
|
||||
`%${search}%`,
|
||||
`%${search}%`,
|
||||
`%${search}%`,
|
||||
`%${search}%`,
|
||||
limit,
|
||||
offset,
|
||||
]);
|
||||
|
||||
return createMockQueryResult([dbvalue]);
|
||||
});
|
||||
|
||||
await expect(findUsers(limit, offset, search, undefined, true)).resolves.toEqual([dbvalue]);
|
||||
});
|
||||
|
||||
it('updateUserById', async () => {
|
||||
const username = 'Joe';
|
||||
const id = 'foo';
|
||||
|
|
|
@ -84,44 +84,64 @@ export const hasUserWithIdentity = async (target: string, userId: string) =>
|
|||
`
|
||||
);
|
||||
|
||||
const buildUserSearchConditionSql = (search: string) => {
|
||||
const buildUserSearchConditionSql = (search: string, isCaseSensitive = false) => {
|
||||
const searchFields = [fields.primaryEmail, fields.primaryPhone, fields.username, fields.name];
|
||||
const conditions = searchFields.map((filedName) => sql`${filedName} like ${'%' + search + '%'}`);
|
||||
|
||||
return sql`${sql.join(conditions, sql` or `)}`;
|
||||
return sql`${sql.join(
|
||||
searchFields.map(
|
||||
(filedName) =>
|
||||
sql`${filedName} ${isCaseSensitive ? sql`like` : sql`ilike`} ${'%' + search + '%'}`
|
||||
),
|
||||
sql` or `
|
||||
)}`;
|
||||
};
|
||||
|
||||
const buildUserConditions = (search?: string, hideAdminUser?: boolean) => {
|
||||
const buildUserConditions = (
|
||||
search?: string,
|
||||
hideAdminUser?: boolean,
|
||||
isCaseSensitive?: boolean
|
||||
) => {
|
||||
if (hideAdminUser) {
|
||||
return sql`
|
||||
where not (${fields.roleNames}::jsonb?${UserRole.Admin})
|
||||
${conditionalSql(search, (search) => sql`and (${buildUserSearchConditionSql(search)})`)}
|
||||
${conditionalSql(
|
||||
search,
|
||||
(search) => sql`and (${buildUserSearchConditionSql(search, isCaseSensitive)})`
|
||||
)}
|
||||
`;
|
||||
}
|
||||
|
||||
return sql`
|
||||
${conditionalSql(search, (search) => sql`where ${buildUserSearchConditionSql(search)}`)}
|
||||
${conditionalSql(
|
||||
search,
|
||||
(search) => sql`where ${buildUserSearchConditionSql(search, isCaseSensitive)}`
|
||||
)}
|
||||
`;
|
||||
};
|
||||
|
||||
export const countUsers = async (search?: string, hideAdminUser?: boolean) =>
|
||||
export const countUsers = async (
|
||||
search?: string,
|
||||
hideAdminUser?: boolean,
|
||||
isCaseSensitive?: boolean
|
||||
) =>
|
||||
envSet.pool.one<{ count: number }>(sql`
|
||||
select count(*)
|
||||
from ${table}
|
||||
${buildUserConditions(search, hideAdminUser)}
|
||||
${buildUserConditions(search, hideAdminUser, isCaseSensitive)}
|
||||
`);
|
||||
|
||||
export const findUsers = async (
|
||||
limit: number,
|
||||
offset: number,
|
||||
search?: string,
|
||||
hideAdminUser?: boolean
|
||||
hideAdminUser?: boolean,
|
||||
isCaseSensitive?: boolean
|
||||
) =>
|
||||
envSet.pool.any<User>(
|
||||
sql`
|
||||
select ${sql.join(Object.values(fields), sql`,`)}
|
||||
from ${table}
|
||||
${buildUserConditions(search, hideAdminUser)}
|
||||
${buildUserConditions(search, hideAdminUser, isCaseSensitive)}
|
||||
limit ${limit}
|
||||
offset ${offset}
|
||||
`
|
||||
|
|
|
@ -27,18 +27,23 @@ export default function adminUserRoutes<T extends AuthedRouter>(router: T) {
|
|||
'/users',
|
||||
koaPagination(),
|
||||
koaGuard({
|
||||
query: object({ search: string().optional(), hideAdminUser: literal('true').optional() }),
|
||||
query: object({
|
||||
search: string().optional(),
|
||||
hideAdminUser: literal('true').optional(),
|
||||
isCaseSensitive: literal('true').optional(),
|
||||
}),
|
||||
}),
|
||||
async (ctx, next) => {
|
||||
const { limit, offset } = ctx.pagination;
|
||||
const {
|
||||
query: { search, hideAdminUser: _hideAdminUser },
|
||||
query: { search, hideAdminUser: _hideAdminUser, isCaseSensitive: _isCaseSensitive },
|
||||
} = ctx.guard;
|
||||
|
||||
const hideAdminUser = _hideAdminUser === 'true';
|
||||
const isCaseSensitive = _isCaseSensitive === 'true';
|
||||
const [{ count }, users] = await Promise.all([
|
||||
countUsers(search, hideAdminUser),
|
||||
findUsers(limit, offset, search, hideAdminUser),
|
||||
countUsers(search, hideAdminUser, isCaseSensitive),
|
||||
findUsers(limit, offset, search, hideAdminUser, isCaseSensitive),
|
||||
]);
|
||||
|
||||
ctx.pagination.totalCount = count;
|
||||
|
|
Loading…
Add table
Reference in a new issue