diff --git a/api/controllers/AdminController.ts b/api/controllers/AdminController.ts index 6ab76108..b1fb41c2 100644 --- a/api/controllers/AdminController.ts +++ b/api/controllers/AdminController.ts @@ -12,7 +12,7 @@ import { CreateMilestoneResponse, CreateBonusResponse, UploadBannerResponse, - GetAllEmailsResponse, + GetAllNamesAndEmailsResponse, SubmitAttendanceForUsersResponse, ModifyUserAccessLevelResponse, GetAllUserAccessLevelsResponse, @@ -41,10 +41,10 @@ export class AdminController { } @Get('/email') - async getAllEmails(@AuthenticatedUser() user: UserModel): Promise { + async getAllNamesAndEmails(@AuthenticatedUser() user: UserModel): Promise { if (!PermissionsService.canSeeAllUserEmails(user)) throw new ForbiddenError(); - const emails = await this.userAccountService.getAllEmails(); - return { error: null, emails }; + const namesAndEmails = await this.userAccountService.getAllNamesAndEmails(); + return { error: null, namesAndEmails }; } @Post('/milestone') diff --git a/package.json b/package.json index bb1c6409..79b11202 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@acmucsd/membership-portal", - "version": "3.6.0", + "version": "3.6.1", "description": "REST API for ACM UCSD's membership portal.", "main": "index.d.ts", "files": [ diff --git a/repositories/UserRepository.ts b/repositories/UserRepository.ts index 95eb5934..b113d97b 100644 --- a/repositories/UserRepository.ts +++ b/repositories/UserRepository.ts @@ -2,7 +2,7 @@ import { EntityRepository, In } from 'typeorm'; import * as bcrypt from 'bcrypt'; import { Activity } from '../types/internal'; import { UserModel } from '../models/UserModel'; -import { Uuid } from '../types'; +import { Uuid, NameAndEmail } from '../types'; import { BaseRepository } from './BaseRepository'; @EntityRepository(UserModel) @@ -50,12 +50,16 @@ export class UserRepository extends BaseRepository { return this.repository.findOne({ accessCode }); } - public async getAllEmails(): Promise { - const emailsRaw = await this.repository + public async getAllNamesAndEmails(): Promise { + const namesAndEmailsRaw = await this.repository .createQueryBuilder() - .select('email') + .select(['email', 'UserModel.firstName', 'UserModel.lastName']) .getRawMany(); - return emailsRaw.map((emailRaw) => emailRaw.email); + const namesAndEmailsFormatted: NameAndEmail[] = namesAndEmailsRaw.map((nameAndEmailRaw) => ({ firstName: + nameAndEmailRaw.UserModel_firstName, + lastName: nameAndEmailRaw.UserModel_lastName, + email: nameAndEmailRaw.email })); + return namesAndEmailsFormatted; } public static async generateHash(pass: string): Promise { diff --git a/services/UserAccountService.ts b/services/UserAccountService.ts index d59e2d96..ab0dc926 100644 --- a/services/UserAccountService.ts +++ b/services/UserAccountService.ts @@ -20,6 +20,7 @@ import { UserPatches, UserState, PrivateProfile, + NameAndEmail, } from '../types'; import { UserRepository } from '../repositories/UserRepository'; import { UserModel } from '../models/UserModel'; @@ -181,10 +182,10 @@ export default class UserAccountService { }); } - public async getAllEmails(): Promise { + public async getAllNamesAndEmails(): Promise { return this.transactions.readOnly(async (txn) => Repositories .user(txn) - .getAllEmails()); + .getAllNamesAndEmails()); } /** diff --git a/tests/admin.test.ts b/tests/admin.test.ts index 8c697a58..dc1ff81b 100644 --- a/tests/admin.test.ts +++ b/tests/admin.test.ts @@ -1,6 +1,6 @@ import { BadRequestError, ForbiddenError } from 'routing-controllers'; import { In } from 'typeorm'; -import { ActivityScope, ActivityType, SubmitAttendanceForUsersRequest, UserAccessType } from '../types'; +import { ActivityScope, ActivityType, SubmitAttendanceForUsersRequest, UserAccessType, NameAndEmail } from '../types'; import { ControllerFactory } from './controllers'; import { DatabaseConnection, EventFactory, PortalState, UserFactory } from './data'; import { UserModel } from '../models/UserModel'; @@ -131,19 +131,24 @@ describe('retroactive attendance submission', () => { }); }); -describe('email retrieval', () => { +describe('names and emails retrieval', () => { test('gets all the emails of stored users', async () => { const conn = await DatabaseConnection.get(); const users = UserFactory.create(5); - const emails = users.map((user) => user.email.toLowerCase()); + const namesAndEmails: NameAndEmail[] = users.map((user) => ({ firstName: user.firstName, + lastName: user.lastName, + email: + user.email.toLowerCase() })); const admin = UserFactory.fake({ accessType: UserAccessType.ADMIN }); await new PortalState() .createUsers(...users, admin) .write(); - const response = await ControllerFactory.admin(conn).getAllEmails(admin); - expect(expect.arrayContaining(response.emails)).toEqual([...emails, admin.email]); + const response = await ControllerFactory.admin(conn).getAllNamesAndEmails(admin); + const expected: NameAndEmail = { firstName: admin.firstName, lastName: admin.lastName, email: admin.email }; + expect(expect.arrayContaining(response.namesAndEmails)).toEqual([...namesAndEmails, + expected]); }); }); diff --git a/types/ApiResponses.ts b/types/ApiResponses.ts index f0479224..d3dfa353 100644 --- a/types/ApiResponses.ts +++ b/types/ApiResponses.ts @@ -6,7 +6,6 @@ import { import { MerchItemOptionMetadata, Uuid } from '.'; // RESPONSE TYPES - export interface CustomErrorBody { name: string; message: string; @@ -31,8 +30,8 @@ export interface UploadBannerResponse extends ApiResponse { banner: string; } -export interface GetAllEmailsResponse extends ApiResponse { - emails: string[]; +export interface GetAllNamesAndEmailsResponse extends ApiResponse { + namesAndEmails: NameAndEmail[]; } export interface SubmitAttendanceForUsersResponse extends ApiResponse { @@ -325,6 +324,11 @@ export interface FulfillMerchOrderResponse extends ApiResponse { } // USER +export interface NameAndEmail { + firstName: string; + lastName: string; + email: string; +} export interface PublicActivity { type: ActivityType,