From baa24d14dc2a1b9ca5574246e6b01311a9cd9009 Mon Sep 17 00:00:00 2001 From: kkatusic Date: Tue, 3 Dec 2024 15:00:14 +0100 Subject: [PATCH 1/2] Feat/Generating public user data --- src/entities/user.ts | 15 ++++++++++++++ src/resolvers/userResolver.ts | 37 ++++++++++++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/entities/user.ts b/src/entities/user.ts index da7d6fac1..c8e33e524 100644 --- a/src/entities/user.ts +++ b/src/entities/user.ts @@ -263,3 +263,18 @@ export class User extends BaseEntity { return `givethId-${this.id}`; } } + +@ObjectType() +export class UserPublicData extends BaseEntity { + @Field(_type => String, { nullable: true }) + firstName?: string; + + @Field(_type => String, { nullable: true }) + lastName?: string; + + @Field(_type => String, { nullable: true }) + name?: string; + + @Field(_type => String, { nullable: true }) + walletAddress?: string; +} diff --git a/src/resolvers/userResolver.ts b/src/resolvers/userResolver.ts index 305afea18..4629bd937 100644 --- a/src/resolvers/userResolver.ts +++ b/src/resolvers/userResolver.ts @@ -2,6 +2,7 @@ import { Arg, Ctx, Field, + Int, Mutation, ObjectType, Query, @@ -9,7 +10,7 @@ import { } from 'type-graphql'; import { Repository } from 'typeorm'; -import { User } from '../entities/user'; +import { User, UserPublicData } from '../entities/user'; import { AccountVerificationInput } from './types/accountVerificationInput'; import { ApolloContext } from '../types/ApolloContext'; import { i18n, translationErrorMessagesKeys } from '../utils/errorMessages'; @@ -43,6 +44,15 @@ class UserRelatedAddressResponse { hasDonated: boolean; } +@ObjectType() +class AllUsersPublicData { + @Field(_type => [UserPublicData]) + users: UserPublicData[]; + + @Field(_type => Number) + totalCount: number; +} + @Resolver(_of => User) export class UserResolver { constructor(private readonly userRepository: Repository) { @@ -422,4 +432,29 @@ export class UserResolver { return 'VERIFICATION_SUCCESS'; } + + @Query(_returns => AllUsersPublicData) + async allUsersBasicData( + @Arg('limit', _type => Int, { nullable: true }) limit: number = 50, + @Arg('skip', _type => Int, { nullable: true }) skip: number = 0, + ): Promise { + const query = this.userRepository + .createQueryBuilder('user') + .select([ + 'user.firstName', + 'user.lastName', + 'user.name', + 'user.walletAddress', + ]) + .skip(skip) + .take(limit); + + // Execute the query and fetch results + const [users, totalCount] = await query.getManyAndCount(); + + return { + users, + totalCount, + }; + } } From fd8ea0e776682b11a336c31162d579527346637f Mon Sep 17 00:00:00 2001 From: kkatusic Date: Thu, 5 Dec 2024 13:53:02 +0100 Subject: [PATCH 2/2] added tests for querying user basic data --- src/resolvers/userResolver.test.ts | 81 ++++++++++++++++++++++++++++++ test/graphqlQueries.ts | 15 ++++++ 2 files changed, 96 insertions(+) diff --git a/src/resolvers/userResolver.test.ts b/src/resolvers/userResolver.test.ts index 58156e8ea..b11490f9b 100644 --- a/src/resolvers/userResolver.test.ts +++ b/src/resolvers/userResolver.test.ts @@ -17,6 +17,7 @@ import { SEED_DATA, } from '../../test/testUtils'; import { + allUsersBasicDataQuery, refreshUserScores, updateUser, userByAddress, @@ -34,6 +35,7 @@ describe('updateUser() test cases', updateUserTestCases); describe('userByAddress() test cases', userByAddressTestCases); describe('refreshUserScores() test cases', refreshUserScoresTestCases); describe('userEmailVerification() test cases', userEmailVerification); +describe('allUsersBasicData() test cases', allUsersBasicData); // TODO I think we can delete addUserVerification query // describe('addUserVerification() test cases', addUserVerificationTestCases); function refreshUserScoresTestCases() { @@ -1169,3 +1171,82 @@ function userEmailVerification() { }); }); } + +function allUsersBasicData() { + describe('allUsersBasicData Test Cases', () => { + it('should return the default number of users when no limit or skip is provided', async () => { + // Create sample users + await saveUserDirectlyToDb(generateRandomEtheriumAddress()); + await saveUserDirectlyToDb(generateRandomEtheriumAddress()); + + // Execute the query + const result = await axios.post(graphqlUrl, { + query: allUsersBasicDataQuery, + }); + + // Assertions + const { users, totalCount } = result.data.data.allUsersBasicData; + assert.isArray(users, 'Users should be an array'); + assert.isTrue(users.length > 0, 'Should return at least one user'); + assert.isNumber(totalCount, 'Total count should be a number'); + }); + + it('should return a limited number of users when a limit is specified', async () => { + // Create sample users + await saveUserDirectlyToDb(generateRandomEtheriumAddress()); + await saveUserDirectlyToDb(generateRandomEtheriumAddress()); + + // Execute the query with a limit + const result = await axios.post(graphqlUrl, { + query: allUsersBasicDataQuery, + variables: { limit: 1 }, + }); + + // Assertions + const { users } = result.data.data.allUsersBasicData; + assert.isArray(users, 'Users should be an array'); + assert.equal(users.length, 1, 'Should return only one user'); + }); + + it('should skip the specified number of users', async () => { + await saveUserDirectlyToDb(generateRandomEtheriumAddress()); + await saveUserDirectlyToDb(generateRandomEtheriumAddress()); + + // Execute the query with skip + const result = await axios.post(graphqlUrl, { + query: allUsersBasicDataQuery, + variables: { skip: 1, limit: 1 }, + }); + + // Assertions + const { users } = result.data.data.allUsersBasicData; + assert.isArray(users, 'Users should be an array'); + assert.equal(users.length, 1, 'Should return only one user'); + }); + + it('should not include sensitive fields like email in the response', async () => { + // Execute the query + const result = await axios.post(graphqlUrl, { + query: allUsersBasicDataQuery, + variables: { skip: 0, limit: 10 }, + }); + + // Assertions + const { users } = result.data.data.allUsersBasicData; + assert.isArray(users, 'Users should be an array'); + assert.isTrue(users.length > 0, 'Should return at least one user'); + + // If any users exist, ensure email and password is not part of the response + if (users.length > 0) { + assert.isUndefined( + users[0].email, + 'Email should not be included in the response', + ); + assert.isUndefined( + users[0].password, + 'Password should not be included in the response', + ); + } + }); + }); +} diff --git a/test/graphqlQueries.ts b/test/graphqlQueries.ts index a4e10f339..2cb780bfb 100644 --- a/test/graphqlQueries.ts +++ b/test/graphqlQueries.ts @@ -2609,3 +2609,18 @@ export const fetchRecurringDonationsByDateQuery = ` } } `; + +// GraphQL query for fetching all users' basic data +export const allUsersBasicDataQuery = ` + query ($limit: Int, $skip: Int) { + allUsersBasicData(limit: $limit, skip: $skip) { + users { + firstName + lastName + name + walletAddress + } + totalCount + } + } +`;