diff --git a/backend/src/auth/__test__/auth.service.spec.ts b/backend/src/auth/__test__/auth.service.spec.ts index 71f46329..a9ca4258 100644 --- a/backend/src/auth/__test__/auth.service.spec.ts +++ b/backend/src/auth/__test__/auth.service.spec.ts @@ -110,6 +110,9 @@ describe('AuthService', () => { }).compile(); service = module.get(AuthService); + (service as any).grantService = { + updateGrantsByPOC: vi.fn().mockResolvedValue(undefined), + }; }); // ── register ──────────────────────────────────────────────────────────────── diff --git a/backend/src/auth/auth.module.ts b/backend/src/auth/auth.module.ts index 236c1913..a1d1a2ac 100644 --- a/backend/src/auth/auth.module.ts +++ b/backend/src/auth/auth.module.ts @@ -2,6 +2,7 @@ import { Module } from '@nestjs/common'; import { JwtModule } from '@nestjs/jwt'; import { AuthController } from './auth.controller'; import { AuthService } from './auth.service'; +import { GrantModule } from '../grant/grant.module'; @Module({ imports: [ @@ -9,14 +10,10 @@ import { AuthService } from './auth.service'; secret: process.env.JWT_SECRET, signOptions: { expiresIn: '1h' }, }), + GrantModule, ], controllers: [AuthController], - providers: [ - { - provide: AuthService, - useClass: AuthService, - }, - ], + providers: [AuthService], exports: [AuthService], }) export class AuthModule {} \ No newline at end of file diff --git a/backend/src/auth/auth.service.ts b/backend/src/auth/auth.service.ts index c13507e0..45fee677 100644 --- a/backend/src/auth/auth.service.ts +++ b/backend/src/auth/auth.service.ts @@ -9,6 +9,7 @@ import { group, table } from "console"; import * as crypto from "crypto"; import { User } from "../../../middle-layer/types/User"; import { UserStatus } from "../../../middle-layer/types/UserStatus"; +import { GrantService } from '../grant/grant.service'; import { HttpException, HttpStatus, @@ -37,7 +38,7 @@ export class AuthService { .digest("base64"); } - constructor() { + constructor(private readonly grantService: GrantService) { try { this.logger.log("Starting AuthService constructor..."); this.logger.log("AWS module:", typeof AWS); @@ -882,6 +883,42 @@ async updateProfile( throw new InternalServerErrorException("Failed to update user data in database"); } + // ── Step 3: Update grants where user is BCAN POC ────────────────────── + try { + await this.grantService.updateGrantsByPOC( + currentEmail, + newEmail, + `${firstName} ${lastName}`, + ); + this.logger.log(`Grants updated for new POC info`); + } catch (grantError: any) { + this.logger.error(`Failed to update grants, rolling back profile changes`, grantError); + + // Rollback DynamoDB + await this.dynamoDb.update({ + TableName: tableName, + Key: { email: currentEmail }, + UpdateExpression: "SET firstName = :firstName, lastName = :lastName, email = :email", + ExpressionAttributeValues: { + ":firstName": existingUser.firstName, + ":lastName": existingUser.lastName, + ":email": currentEmail, + }, + ReturnValues: "NONE", + }).promise(); + this.logger.log(`DynamoDB rolled back to original values`); + + // Rollback Cognito if email changed + if (isEmailChanging) { + await this.cognito.updateUserAttributes({ + AccessToken: accessToken, + UserAttributes: [{ Name: "email", Value: currentEmail }], + }).promise(); + this.logger.log(`Cognito rolled back to ${currentEmail}`); + } + + throw new InternalServerErrorException("Failed to update grants. All changes have been rolled back."); + } } catch (error) { if (error instanceof HttpException) { throw error; diff --git a/backend/src/grant/grant.module.ts b/backend/src/grant/grant.module.ts index cd670d73..0d6e97d5 100644 --- a/backend/src/grant/grant.module.ts +++ b/backend/src/grant/grant.module.ts @@ -6,5 +6,6 @@ import { NotificationsModule } from '../notifications/notification.module'; imports: [NotificationsModule], controllers: [GrantController], providers: [GrantService], + exports: [GrantService], }) export class GrantModule { } \ No newline at end of file diff --git a/backend/src/grant/grant.service.ts b/backend/src/grant/grant.service.ts index 1614c13d..4cd5f151 100644 --- a/backend/src/grant/grant.service.ts +++ b/backend/src/grant/grant.service.ts @@ -641,4 +641,36 @@ export class GrantService { const diffMs = +new Date(deadline) - +new Date(alertTime); return Math.round(diffMs / (1000 * 60 * 60 * 24)); } + + // Updates the POC of grants after that POC changes their contact info + async updateGrantsByPOC( + currentEmail: string, + newEmail: string, + newName: string, + ): Promise { + this.logger.log(`Updating grants where bcan_poc email is ${currentEmail}`); + + const tableName = process.env.DYNAMODB_GRANT_TABLE_NAME || 'TABLE_FAILURE'; + if (tableName === 'TABLE_FAILURE') { + throw new InternalServerErrorException('Server configuration error: DynamoDB table name not configured'); + } + + const data = await this.dynamoDb.scan({ TableName: tableName }).promise(); + const grants = (data.Items as Grant[]) || []; + + const affectedGrants = grants.filter( + (g) => g.bcan_poc?.POC_email?.toLowerCase() === currentEmail.toLowerCase() + ); + + this.logger.log(`Found ${affectedGrants.length} grants to update`); + + for (const grant of affectedGrants) { + await this.updateGrant({ + ...grant, + bcan_poc: { POC_name: newName, POC_email: newEmail }, + }); + } + + this.logger.log(`Successfully updated ${affectedGrants.length} grants for new POC info`); + } } \ No newline at end of file diff --git a/frontend/src/main-page/settings/Settings.tsx b/frontend/src/main-page/settings/Settings.tsx index dd653299..ad90040f 100644 --- a/frontend/src/main-page/settings/Settings.tsx +++ b/frontend/src/main-page/settings/Settings.tsx @@ -13,6 +13,7 @@ import ChangePasswordModal, { ChangePasswordFormValues } from "./ChangePasswordM import { getAppStore } from "../../external/bcanSatchel/store"; import { setActiveUsers, updateUserProfile } from "../../external/bcanSatchel/actions"; import { User } from "../../../../middle-layer/types/User"; +import { fetchGrants } from "../grants/filter-bar/processGrantData"; import { InputField } from "../../sign-up"; const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; @@ -104,6 +105,7 @@ function Settings() { ]); updateUserProfile(updatedUser); setPersonalInfo(editForm); + await fetchGrants(); setIsEditingPersonalInfo(false); setPersonalInfoError(null);