Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ryan/update social routes improvement #422

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions api/controllers/AdminController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
CreateMilestoneResponse,
CreateBonusResponse,
UploadBannerResponse,
GetAllEmailsResponse,
GetAllNamesEmailsResponse,
SubmitAttendanceForUsersResponse,
ModifyUserAccessLevelResponse,
GetAllUserAccessLevelsResponse,
Expand Down Expand Up @@ -41,10 +41,10 @@ export class AdminController {
}

@Get('/email')
async getAllEmails(@AuthenticatedUser() user: UserModel): Promise<GetAllEmailsResponse> {
async getAllNamesEmails(@AuthenticatedUser() user: UserModel): Promise<GetAllNamesEmailsResponse> {
if (!PermissionsService.canSeeAllUserEmails(user)) throw new ForbiddenError();
const emails = await this.userAccountService.getAllEmails();
return { error: null, emails };
const namesEmails = await this.userAccountService.getAllNamesEmails();
return { error: null, namesEmails };
}

@Post('/milestone')
Expand Down
14 changes: 7 additions & 7 deletions api/controllers/UserController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,26 +104,26 @@ export class UserController {
}

@Post('/socialMedia')
async insertSocialMediaForUser(@Body() insertSocialMediaRequest: InsertSocialMediaRequest,
async insertSocialMediasForUser(@Body() insertSocialMediaRequests: InsertSocialMediaRequest[],
@AuthenticatedUser() user: UserModel): Promise<InsertSocialMediaResponse> {
const userSocialMedia = await this.userSocialMediaService
.insertSocialMediaForUser(user, insertSocialMediaRequest.socialMedia);
.insertSocialMediasForUser(user, insertSocialMediaRequests.map(request => request.socialMedia));
return { error: null, userSocialMedia: userSocialMedia.getPublicSocialMedia() };
}

@Patch('/socialMedia/:uuid')
async updateSocialMediaForUser(@Params() params: UuidParam,
@Body() updateSocialMediaRequest: UpdateSocialMediaRequest,
async updateSocialMediasForUser(@Params() params: UuidParam[],
@Body() updateSocialMediaRequests: UpdateSocialMediaRequest[],
@AuthenticatedUser() user: UserModel): Promise<UpdateSocialMediaResponse> {
const userSocialMedia = await this.userSocialMediaService
.updateSocialMediaByUuid(user, params.uuid, updateSocialMediaRequest.socialMedia);
.updateSocialMediasByUuid(user, params.map(param => param.uuid), updateSocialMediaRequests.map(request => request.socialMedia));
return { error: null, userSocialMedia: userSocialMedia.getPublicSocialMedia() };
}

@Delete('/socialMedia/:uuid')
async deleteSocialMediaForUser(@Params() params: UuidParam,
async deleteSocialMediaForUser(@Params() params: UuidParam[],
@AuthenticatedUser() user: UserModel): Promise<DeleteSocialMediaResponse> {
await this.userSocialMediaService.deleteSocialMediaByUuid(user, params.uuid);
for (const uuid of params) await this.userSocialMediaService.deleteSocialMediasByUuid(user, params.map(param => param.uuid));
return { error: null };
}
}
8 changes: 4 additions & 4 deletions repositories/UserRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,12 @@ export class UserRepository extends BaseRepository<UserModel> {
return this.repository.findOne({ accessCode });
}

public async getAllEmails(): Promise<string[]> {
const emailsRaw = await this.repository
public async getAllNamesEmails(): Promise<string[]> {
const namesEmailsRaw = await this.repository
.createQueryBuilder()
.select('email')
.select(['email', 'UserModel.firstName', 'UserModel.lastName'])
.getRawMany();
return emailsRaw.map((emailRaw) => emailRaw.email);
return namesEmailsRaw.map((nameEmailRaw) => `${nameEmailRaw.UserModel_firstName} ${nameEmailRaw.UserModel_lastName} (${nameEmailRaw.email})`);
}

public static async generateHash(pass: string): Promise<string> {
Expand Down
6 changes: 3 additions & 3 deletions repositories/UserSocialMediaRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ export class UserSocialMediaRepository extends BaseRepository<UserSocialMediaMod
}

public async upsertSocialMedia(userSocialMedia: UserSocialMediaModel,
changes?: Partial<UserSocialMediaModel>): Promise<UserSocialMediaModel> {
if (changes) userSocialMedia = UserSocialMediaModel.merge(userSocialMedia, changes);
changes?: Partial<UserSocialMediaModel>[]): Promise<UserSocialMediaModel> {
if (changes) for (const change of changes) userSocialMedia = UserSocialMediaModel.merge(userSocialMedia, change);
return this.repository.save(userSocialMedia);
}

public async deleteSocialMedia(userSocialMedia: UserSocialMediaModel): Promise<UserSocialMediaModel> {
public async deleteSocialMedia(userSocialMedia: UserSocialMediaModel[]): Promise<UserSocialMediaModel[]> {
return this.repository.remove(userSocialMedia);
}

Expand Down
4 changes: 2 additions & 2 deletions services/UserAccountService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,10 +167,10 @@ export default class UserAccountService {
});
}

public async getAllEmails(): Promise<string[]> {
public async getAllNamesEmails(): Promise<string[]> {
return this.transactions.readOnly(async (txn) => Repositories
.user(txn)
.getAllEmails());
.getAllNamesEmails());
}

/**
Expand Down
53 changes: 32 additions & 21 deletions services/UserSocialMediaService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,42 +22,53 @@ export default class UserSocialMediaService {
return userSocialMedia;
}

public async insertSocialMediaForUser(user: UserModel, socialMedia: SocialMedia) {
const addedSocialMedia = await this.transactions.readWrite(async (txn) => {
public async insertSocialMediasForUser(user: UserModel, socialMedias: SocialMedia[]) {
const addedSocialMedias = await this.transactions.readWrite(async (txn) => {
const userSocialMediaRepository = Repositories.userSocialMedia(txn);
const isNewSocialMediaType = await userSocialMediaRepository.isNewSocialMediaTypeForUser(user, socialMedia.type);
if (!isNewSocialMediaType) {
throw new UserError('Social media URL of this type has already been created for this user');
for (const socialMedia of socialMedias) {
const isNewSocialMediaType = await userSocialMediaRepository.isNewSocialMediaTypeForUser(user, socialMedia.type);
if (!isNewSocialMediaType) {
throw new UserError('Social media URL of this type has already been created for this user');
}
}
return userSocialMediaRepository.upsertSocialMedia(UserSocialMediaModel.create({ ...socialMedia, user }));
return userSocialMediaRepository.upsertSocialMedia(UserSocialMediaModel.create({ ...socialMedias, user }));
});
return addedSocialMedia;
return addedSocialMedias;
}

public async updateSocialMediaByUuid(user: UserModel,
uuid: Uuid,
changes: Partial<UserSocialMediaModel>): Promise<UserSocialMediaModel> {
public async updateSocialMediasByUuid(user: UserModel,
uuids: Uuid[],
changes: Partial<UserSocialMediaModel>[]): Promise<UserSocialMediaModel> {
const updatedSocialMedia = await this.transactions.readWrite(async (txn) => {
const userSocialMediaRepository = Repositories.userSocialMedia(txn);
const socialMedia = await userSocialMediaRepository.findByUuid(uuid);
if (!socialMedia) throw new NotFoundError('Social media URL not found');
if (user.uuid !== socialMedia.user.uuid) {
throw new ForbiddenError('User cannot update a social media URL of another user');
const validSocials = [];
for (const uuid of uuids) {
const socialMedia = await userSocialMediaRepository.findByUuid(uuid);
if (!socialMedia) throw new NotFoundError('Social media URL not found');
if (user.uuid !== socialMedia.user.uuid) {
throw new ForbiddenError('User cannot update a social media URL of another user');
}
validSocials.push(socialMedia);
}
return userSocialMediaRepository.upsertSocialMedia(socialMedia, changes);
return validSocials.map((socialMedia, index) => updatedSocialMedia.upsertSocialMedia(socialMedia, changes[index]));
});
return updatedSocialMedia;
}

public async deleteSocialMediaByUuid(user: UserModel, uuid: Uuid): Promise<UserSocialMediaModel> {
public async deleteSocialMediasByUuid(user: UserModel, uuids: Uuid[]): Promise<UserSocialMediaModel[]> {
const updatedSocialMedia = await this.transactions.readWrite(async (txn) => {
const userSocialMediaRepository = Repositories.userSocialMedia(txn);
const socialMedia = await userSocialMediaRepository.findByUuid(uuid);
if (!socialMedia) throw new NotFoundError('Social media URL not found');
if (user.uuid !== socialMedia.user.uuid) {
throw new ForbiddenError('User cannot delete a social media URL of another user');
const validSocials = [];
for (const uuid of uuids) {
const socialMedia = await userSocialMediaRepository.findByUuid(uuid);
if (!socialMedia) throw new NotFoundError('Social media URL not found');
if (user.uuid !== socialMedia.user.uuid) {
throw new ForbiddenError('User cannot delete a social media URL of another user');
}
validSocials.push(socialMedia);
}
return userSocialMediaRepository.deleteSocialMedia(socialMedia);
for (const social of validSocials) userSocialMediaRepository.deleteSocialMedia(social);
return userSocialMediaRepository.getSocialMediaForUser(user);
});
return updatedSocialMedia;
}
Expand Down
9 changes: 5 additions & 4 deletions tests/admin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,19 +131,20 @@ 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 namesEmails = users.map((user) => `${user.firstName} ${user.lastName} (${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).getAllNamesEmails(admin);
expect(expect.arrayContaining(response.namesEmails)).toEqual([...namesEmails,
`${admin.firstName} ${admin.lastName} (${admin.email})`]);
});
});

Expand Down
4 changes: 2 additions & 2 deletions types/ApiResponses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ export interface UploadBannerResponse extends ApiResponse {
banner: string;
}

export interface GetAllEmailsResponse extends ApiResponse {
emails: string[];
export interface GetAllNamesEmailsResponse extends ApiResponse {
namesEmails: string[];
}

export interface SubmitAttendanceForUsersResponse extends ApiResponse {
Expand Down
Loading