diff --git a/src/gql.ts b/src/gql.ts index 395bf82..85e03f5 100644 --- a/src/gql.ts +++ b/src/gql.ts @@ -24,6 +24,12 @@ const typeDefs = gql` QUESTION_AND_ANSWER } + enum UserSortOption { + EMAIL_ASC + NAME_ASC + NUMBER_OF_QUIZZES_COMPLETED_WITH_DESC + } + type PageInfo { hasNextPage: Boolean startCursor: String @@ -133,7 +139,10 @@ const typeDefs = gql` filters: QuizFilters ): QuizConnection quiz(id: String!): QuizDetails - users(first: Int, after: String): UserConnection + """ + Get a paged list of users. + """ + users(first: Int, after: String, sortedBy: UserSortOption): UserConnection me: UserDetails } diff --git a/src/models.ts b/src/models.ts index aba9133..0bbb1a0 100644 --- a/src/models.ts +++ b/src/models.ts @@ -54,3 +54,5 @@ export interface CreateQuizResult { link: string; }[]; } + +export type UserSortOption = 'EMAIL_ASC' | 'NAME_ASC' | 'NUMBER_OF_QUIZZES_COMPLETED_WITH_DESC'; diff --git a/src/persistence/persistence.ts b/src/persistence/persistence.ts index c792870..a2aaef1 100644 --- a/src/persistence/persistence.ts +++ b/src/persistence/persistence.ts @@ -1,7 +1,7 @@ -import { PrismaClient, Quiz as QuizPersistence, QuizImage as QuizImagePersistence, Role } from '@prisma/client'; +import { PrismaClient, Quiz as QuizPersistence, QuizImage as QuizImagePersistence, Role, User } from '@prisma/client'; import { types } from 'pg'; import { v4 as uuidv4 } from 'uuid'; -import { QuizFilters } from '../models'; +import { QuizFilters, UserSortOption } from '../models'; export interface PersistenceResult { data: T[]; @@ -256,8 +256,20 @@ class Persistence { return roles.map((r) => r.role); } - async getUsersWithRole({ role, afterId, limit }: { role: Role; afterId?: string; limit: number }) { - const result = await this.getPrismaClient().user.findMany({ + async getUsersWithRole({ + role, + afterId, + limit, + sortedBy, + currentUserId, + }: { + role: Role; + afterId?: string; + limit: number; + sortedBy: UserSortOption; + currentUserId: string; + }) { + const pagedWhereQuery = { ...getPagedQuery(limit, afterId), where: { roles: { @@ -266,10 +278,45 @@ class Persistence { }, }, }, - orderBy: { - email: 'asc', - }, - }); + }; + + let result; + switch (sortedBy) { + case 'EMAIL_ASC': + result = await this.getPrismaClient().user.findMany({ + ...pagedWhereQuery, + orderBy: { + email: 'asc', + }, + }); + break; + case 'NAME_ASC': + result = await this.getPrismaClient().user.findMany({ + ...pagedWhereQuery, + orderBy: { + name: 'asc', + }, + }); + break; + case 'NUMBER_OF_QUIZZES_COMPLETED_WITH_DESC': + default: + /** + * Prisma does not yet support ordering by a relation with anything other than count. + * We need to do something quite a bit more complex. + */ + result = (await this.getPrismaClient().$queryRaw` +select id, email, name from + ( + select "user".*, count(quiz_completion.id) as completions from "user" + left outer join quiz_completion_user as their_completion on "user".id = their_completion.user_id + left outer join quiz_completion on their_completion.quiz_completion_id = quiz_completion.id + left outer join quiz_completion_user as my_completion on my_completion.quiz_completion_id = quiz_completion.id and my_completion.user_id = ${currentUserId} + group by "user".id + ) as completions_with_current_user +order by completions_with_current_user.completions desc; + `) as User[]; + break; + } return slicePagedResults(result, limit, afterId !== undefined); } diff --git a/src/resolvers/userResolvers.ts b/src/resolvers/userResolvers.ts index b2c88c2..fcb690e 100644 --- a/src/resolvers/userResolvers.ts +++ b/src/resolvers/userResolvers.ts @@ -1,7 +1,7 @@ import { User as UserPersistence } from '@prisma/client'; import { QuizlordContext } from '..'; -import { User, UserDetails } from '../models'; +import { User, UserDetails, UserSortOption } from '../models'; import { persistence } from '../persistence/persistence'; import { base64Decode, base64Encode, PagedResult, requireUserRole } from './helpers'; @@ -14,7 +14,11 @@ function userPersistenceToUser(user: UserPersistence): User { export async function users( _: unknown, - { first = 100, after }: { first: number; after?: string }, + { + first = 100, + after, + sortedBy = 'NUMBER_OF_QUIZZES_COMPLETED_WITH_DESC', + }: { first: number; after?: string; sortedBy: UserSortOption }, context: QuizlordContext, ): Promise> { requireUserRole(context, 'USER'); @@ -23,6 +27,8 @@ export async function users( role: 'USER', afterId, limit: first, + sortedBy, + currentUserId: context.userId, }); const edges = data.map((user) => ({ node: userPersistenceToUser(user),