add staff user list endpoint

This commit is contained in:
Marshal
2026-08-03 12:32:10 +00:00
parent c12108d953
commit 106b07a02b
4 changed files with 133 additions and 0 deletions

View File

@@ -0,0 +1,69 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { PaginatedResponse } from '@edr/types';
import { User } from '@tria-plc/iamapi-common/entities/iam/user/user.entity';
import { Repository } from 'typeorm';
import { ListUsersQueryDto } from './dto/list-users-query.dto';
import { paginateQuery } from '../../common/utils/pagination.util';
/**
* Read-only listing of `iam.users` for backoffice pickers.
*
* Exists because `@tria-plc/iamapi-common@1.0.0`'s `GET /users/filter` pairs a
* `@QueryParams()` pagination DTO with a plain `@Query()` DTO that does not
* declare `skip`/`take`/`orderBy`; the global whitelist pipe then 400s on the
* very params the route's own paginator reads. Drop this once IAM ships a fix.
*/
@Injectable()
export class ListUsersService {
constructor(
@InjectRepository(User) private readonly users: Repository<User>,
) {}
findAll(query: ListUsersQueryDto): Promise<PaginatedResponse<User>> {
const sortBy = query.sortBy ?? 'username';
const qb = this.users
.createQueryBuilder('user')
// Explicit select: never widen this to `user` — the entity's lazy
// relations include credentials and sessions.
.select([
'user.id',
'user.name',
'user.username',
'user.email',
'user.phoneNumber',
'user.userType',
'user.status',
'user.isActive',
'user.createdAt',
])
.orderBy(`user.${sortBy}`, query.sortOrder ?? 'ASC');
if (query.userType) {
qb.andWhere('user.userType = :userType', { userType: query.userType });
}
if (query.userStatus) {
qb.andWhere('user.status = :userStatus', { userStatus: query.userStatus });
}
if (query.isActive !== undefined) {
qb.andWhere('user.isActive = :isActive', { isActive: query.isActive });
}
if (query.search) {
// `name` is localized jsonb ({ en, am, … }), not a string — match its
// values rather than casting the whole object to text.
qb.andWhere(
`(user.username ILIKE :search
OR user.email ILIKE :search
OR user.phone_number ILIKE :search
OR EXISTS (
SELECT 1 FROM jsonb_each_text(user.name) AS n(k, v)
WHERE n.v ILIKE :search
))`,
{ search: `%${query.search}%` },
);
}
return paginateQuery(qb, query);
}
}