diff --git a/backend/src/graphql/resolver/AdminResolver.ts b/backend/src/graphql/resolver/AdminResolver.ts index f7f13ec55..4b9c72afb 100644 --- a/backend/src/graphql/resolver/AdminResolver.ts +++ b/backend/src/graphql/resolver/AdminResolver.ts @@ -2,7 +2,7 @@ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ import { Resolver, Query, Arg, Args, Authorized, Mutation, Ctx } from 'type-graphql' -import { getCustomRepository, Raw } from '@dbTools/typeorm' +import { getCustomRepository, ObjectLiteral, Raw } from '@dbTools/typeorm' import { UserAdmin, SearchUsersResult } from '../model/UserAdmin' import { PendingCreation } from '../model/PendingCreation' import { CreatePendingCreations } from '../model/CreatePendingCreations' @@ -35,8 +35,26 @@ export class AdminResolver { @Args() { searchText, currentPage = 1, pageSize = 25, notActivated = false }: SearchUsersArgs, ): Promise { const userRepository = getCustomRepository(UserRepository) - const users = await userRepository.findBySearchCriteria(searchText) - let adminUsers = await Promise.all( + + const filterCriteria: ObjectLiteral[] = [] + if (notActivated) { + filterCriteria.push({ emailChecked: false }) + } + // prevent overfetching data from db, select only needed columns + // prevent reading and transmitting data from db at least 300 Bytes + // one of my example dataset shrink down from 342 Bytes to 42 Bytes, that's ~88% saved db bandwith + const userFields = ['id', 'firstName', 'lastName', 'email', 'emailChecked'] + const [users, count] = await userRepository.findBySearchCriteriaPagedFiltered( + userFields.map((fieldName) => { + return 'user.' + fieldName + }), + searchText, + filterCriteria, + currentPage, + pageSize, + ) + + const adminUsers = await Promise.all( users.map(async (user) => { const adminUser = new UserAdmin() adminUser.userId = user.id @@ -56,6 +74,7 @@ export class AdminResolver { updatedAt: 'DESC', createdAt: 'DESC', }, + select: ['updatedAt', 'createdAt'], }, ) if (emailOptIn) { @@ -69,11 +88,9 @@ export class AdminResolver { return adminUser }), ) - if (notActivated) adminUsers = adminUsers.filter((u) => !u.emailChecked) - const first = (currentPage - 1) * pageSize return { - userCount: adminUsers.length, - userList: adminUsers.slice(first, first + pageSize), + userCount: count, + userList: adminUsers, } } diff --git a/backend/src/typeorm/repository/User.ts b/backend/src/typeorm/repository/User.ts index 3155053d9..66f285978 100644 --- a/backend/src/typeorm/repository/User.ts +++ b/backend/src/typeorm/repository/User.ts @@ -1,4 +1,4 @@ -import { EntityRepository, Repository } from '@dbTools/typeorm' +import { Brackets, EntityRepository, ObjectLiteral, Repository } from '@dbTools/typeorm' import { User } from '@entity/User' @EntityRepository(User) @@ -17,17 +17,31 @@ export class UserRepository extends Repository { .getMany() } - async findBySearchCriteria(searchCriteria: string): Promise { - return this.createQueryBuilder('user') + async findBySearchCriteriaPagedFiltered( + select: string[], + searchCriteria: string, + filterCriteria: ObjectLiteral[], + currentPage: number, + pageSize: number, + ): Promise<[User[], number]> { + return await this.createQueryBuilder('user') + .select(select) .withDeleted() .where( - 'user.firstName like :name or user.lastName like :lastName or user.email like :email', - { - name: `%${searchCriteria}%`, - lastName: `%${searchCriteria}%`, - email: `%${searchCriteria}%`, - }, + new Brackets((qb) => { + qb.where( + 'user.firstName like :name or user.lastName like :lastName or user.email like :email', + { + name: `%${searchCriteria}%`, + lastName: `%${searchCriteria}%`, + email: `%${searchCriteria}%`, + }, + ) + }), ) - .getMany() + .andWhere(filterCriteria) + .take(pageSize) + .skip((currentPage - 1) * pageSize) + .getManyAndCount() } }