Skip to content

Commit c5a37a9

Browse files
authored
#1791 - Enable Assessments tab view and NOA view for Public Institution Users - Part 5 (#1984)
#1791 - Enable Assessments tab view and NOA view for Public Institution Users - Part 5 (#1984)
1 parent a494bfa commit c5a37a9

File tree

31 files changed

+238
-75
lines changed

31 files changed

+238
-75
lines changed

sources/packages/backend/apps/api/src/auth/decorators/institution/institution-student-data-access.decorator.ts

+15-3
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,19 @@ export const INSTITUTION_HAS_STUDENT_DATA_ACCESS_KEY =
88

99
/**
1010
* Provide context that it's consumer must be
11-
* validated to have access to data of given student.
11+
* validated to have access to the student
12+
* and their application if present.
1213
*/
13-
export const HasStudentDataAccess = (studentIdParamName: string) =>
14-
SetMetadata(INSTITUTION_HAS_STUDENT_DATA_ACCESS_KEY, studentIdParamName);
14+
export const HasStudentDataAccess = (
15+
studentIdParamName: string,
16+
applicationIdParamName?: string,
17+
) =>
18+
SetMetadata(INSTITUTION_HAS_STUDENT_DATA_ACCESS_KEY, {
19+
studentIdParamName,
20+
applicationIdParamName,
21+
});
22+
23+
export interface HasStudentDataAccessParam {
24+
studentIdParamName: string;
25+
applicationIdParamName?: string;
26+
}

sources/packages/backend/apps/api/src/auth/guards/institution/institution-student-data-access.guard.ts

+24-5
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,14 @@ import {
33
CanActivate,
44
ExecutionContext,
55
ForbiddenException,
6+
BadRequestException,
67
} from "@nestjs/common";
78
import { Reflector } from "@nestjs/core";
89
import { IInstitutionUserToken } from "../..";
9-
import { INSTITUTION_HAS_STUDENT_DATA_ACCESS_KEY } from "../../decorators";
10+
import {
11+
HasStudentDataAccessParam,
12+
INSTITUTION_HAS_STUDENT_DATA_ACCESS_KEY,
13+
} from "../../decorators";
1014
import { InstitutionService } from "../../../services";
1115

1216
@Injectable()
@@ -18,11 +22,10 @@ export class InstitutionStudentDataAccessGuard implements CanActivate {
1822

1923
async canActivate(context: ExecutionContext): Promise<boolean> {
2024
const institutionStudentDataAccessParam =
21-
this.reflector.getAllAndOverride<string>(
25+
this.reflector.getAllAndOverride<HasStudentDataAccessParam>(
2226
INSTITUTION_HAS_STUDENT_DATA_ACCESS_KEY,
2327
[context.getHandler(), context.getClass()],
2428
);
25-
2629
if (!institutionStudentDataAccessParam) {
2730
return true;
2831
}
@@ -34,14 +37,30 @@ export class InstitutionStudentDataAccessGuard implements CanActivate {
3437
user: IInstitutionUserToken;
3538
params: Record<string, string>;
3639
} = context.switchToHttp().getRequest();
37-
3840
if (user?.isActive) {
39-
const studentId = +params[institutionStudentDataAccessParam];
41+
let applicationId = undefined;
42+
const studentId =
43+
+params[institutionStudentDataAccessParam.studentIdParamName];
44+
if (!studentId) {
45+
// Student id not found in the url.
46+
throw new BadRequestException("Student id not found in the url.");
47+
}
48+
if (institutionStudentDataAccessParam.applicationIdParamName) {
49+
applicationId =
50+
+params[institutionStudentDataAccessParam.applicationIdParamName];
51+
if (!applicationId) {
52+
// Application id not found in the url.
53+
throw new BadRequestException("Application id not found in the url.");
54+
}
55+
}
56+
4057
const hasStudentDataAccess =
4158
await this.institutionService.hasStudentDataAccess(
4259
user.authorizations.institutionId,
4360
studentId,
61+
{ applicationId },
4462
);
63+
4564
if (hasStudentDataAccess) {
4665
return true;
4766
}

sources/packages/backend/apps/api/src/route-controllers/application-exception/_tests_/e2e/application-exception.institution.controller.getException.e2e-spec.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ describe("ApplicationExceptionInstitutionsController(e2e)-getException", () => {
5454
);
5555
const applicationException = application.applicationException;
5656

57-
const endpoint = `/institutions/application-exception/student/${application.student.id}/exception/${applicationException.id}`;
57+
const endpoint = `/institutions/application-exception/student/${application.student.id}/application/${application.id}/exception/${applicationException.id}`;
5858
const institutionUserToken = await getInstitutionToken(
5959
InstitutionTokenTypes.CollegeFUser,
6060
);
@@ -96,7 +96,7 @@ describe("ApplicationExceptionInstitutionsController(e2e)-getException", () => {
9696
{ applicationExceptionStatus: ApplicationExceptionStatus.Approved },
9797
);
9898

99-
const endpoint = `/institutions/application-exception/student/${application.student.id}/exception/${collegeCApplication.applicationException.id}`;
99+
const endpoint = `/institutions/application-exception/student/${application.student.id}/application/${application.id}/exception/${collegeCApplication.applicationException.id}`;
100100
const institutionUserToken = await getInstitutionToken(
101101
InstitutionTokenTypes.CollegeFUser,
102102
);
@@ -129,7 +129,7 @@ describe("ApplicationExceptionInstitutionsController(e2e)-getException", () => {
129129
const collegeCInstitutionUserToken = await getInstitutionToken(
130130
InstitutionTokenTypes.CollegeCUser,
131131
);
132-
const endpoint = `/institutions/application-exception/student/${student.id}/exception/9999999`;
132+
const endpoint = `/institutions/application-exception/student/${student.id}/application/${collegeCApplication.id}/exception/9999999`;
133133

134134
// Act/Assert
135135
await request(app.getHttpServer())
@@ -152,7 +152,7 @@ describe("ApplicationExceptionInstitutionsController(e2e)-getException", () => {
152152
InstitutionTokenTypes.CollegeFUser,
153153
);
154154

155-
const endpoint = `/institutions/application-exception/student/${student.id}/exception/9999999`;
155+
const endpoint = `/institutions/application-exception/student/${student.id}/application/9999999/exception/9999999`;
156156

157157
// Act/Assert
158158
await request(app.getHttpServer())

sources/packages/backend/apps/api/src/route-controllers/application-exception/application-exception.controller.service.ts

+6-4
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export class ApplicationExceptionControllerService {
1818
* @param exceptionId exception to be retrieved.
1919
* @param options options
2020
* - `studentId` student id.
21+
* - `applicationId` application id.
2122
* - `assessDetails`, if true, will return access details.
2223
* @returns student application exception information.
2324
*/
@@ -29,14 +30,15 @@ export class ApplicationExceptionControllerService {
2930
exceptionId: number,
3031
options?: {
3132
studentId?: number;
33+
applicationId?: number;
3234
assessDetails?: boolean;
3335
},
3436
): Promise<T> {
3537
const applicationException =
36-
await this.applicationExceptionService.getExceptionDetails(
37-
exceptionId,
38-
options?.studentId,
39-
);
38+
await this.applicationExceptionService.getExceptionDetails(exceptionId, {
39+
studentId: options?.studentId,
40+
applicationId: options?.applicationId,
41+
});
4042
if (!applicationException) {
4143
throw new NotFoundException("Student application exception not found.");
4244
}

sources/packages/backend/apps/api/src/route-controllers/application-exception/application-exception.institutions.controller.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { ApplicationExceptionControllerService } from "./application-exception.c
1717
@AllowAuthorizedParty(AuthorizedParties.institution)
1818
@Controller("application-exception")
1919
@IsBCPublicInstitution()
20-
@HasStudentDataAccess("studentId")
20+
@HasStudentDataAccess("studentId", "applicationId")
2121
@ApiTags(`${ClientTypeBaseRoute.Institution}-application-exception`)
2222
export class ApplicationExceptionInstitutionsController extends BaseController {
2323
constructor(
@@ -29,20 +29,22 @@ export class ApplicationExceptionInstitutionsController extends BaseController {
2929
/**
3030
* Get a student application exception of a student.
3131
* @param studentId student id.
32+
* @param applicationId application id.
3233
* @param exceptionId exception to be retrieved.
3334
* @returns student application exception information.
3435
*/
35-
@Get("student/:studentId/exception/:exceptionId")
36+
@Get("student/:studentId/application/:applicationId/exception/:exceptionId")
3637
@ApiNotFoundResponse({
3738
description: "Student application exception not found.",
3839
})
3940
async getException(
4041
@Param("studentId", ParseIntPipe) studentId: number,
42+
@Param("applicationId", ParseIntPipe) applicationId: number,
4143
@Param("exceptionId", ParseIntPipe) exceptionId: number,
4244
): Promise<ApplicationExceptionAPIOutDTO> {
4345
return this.applicationExceptionControllerService.getExceptionDetails<ApplicationExceptionAPIOutDTO>(
4446
exceptionId,
45-
{ studentId },
47+
{ studentId, applicationId },
4648
);
4749
}
4850
}

sources/packages/backend/apps/api/src/route-controllers/assessment/_tests_/e2e/assessment.institutions.controller.getAssessmentAwardDetails.e2e-spec.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ describe("AssessmentInstitutionsController(e2e)-getAssessmentAwardDetails", () =
115115
] = disbursementReceiptsValue.grantAmount;
116116
});
117117

118-
const endpoint = `/institutions/assessment/student/${student.id}/assessment/${assessment.id}/award`;
118+
const endpoint = `/institutions/assessment/student/${student.id}/application/${application.id}/assessment/${assessment.id}/award`;
119119
const institutionUserToken = await getInstitutionToken(
120120
InstitutionTokenTypes.CollegeFUser,
121121
);
@@ -172,7 +172,7 @@ describe("AssessmentInstitutionsController(e2e)-getAssessmentAwardDetails", () =
172172
},
173173
);
174174
await db.msfaaNumber.save(currentMSFAA);
175-
await saveFakeApplicationDisbursements(db.dataSource, {
175+
const application = await saveFakeApplicationDisbursements(db.dataSource, {
176176
institution: collegeF,
177177
institutionLocation: collegeFLocation,
178178
student,
@@ -183,7 +183,7 @@ describe("AssessmentInstitutionsController(e2e)-getAssessmentAwardDetails", () =
183183
InstitutionTokenTypes.CollegeFUser,
184184
);
185185

186-
const endpoint = `/institutions/assessment/student/${student.id}/assessment/9999999/award`;
186+
const endpoint = `/institutions/assessment/student/${student.id}/application/${application.id}/assessment/9999999/award`;
187187

188188
// Act/Assert
189189
await request(app.getHttpServer())
@@ -213,7 +213,7 @@ describe("AssessmentInstitutionsController(e2e)-getAssessmentAwardDetails", () =
213213
const collegeCInstitutionUserToken = await getInstitutionToken(
214214
InstitutionTokenTypes.CollegeCUser,
215215
);
216-
const endpoint = `/institutions/assessment/student/${student.id}/assessment/9999999/award`;
216+
const endpoint = `/institutions/assessment/student/${student.id}/application/${collegeCApplication.id}/assessment/9999999/award`;
217217

218218
// Act/Assert
219219
await request(app.getHttpServer())
@@ -236,7 +236,7 @@ describe("AssessmentInstitutionsController(e2e)-getAssessmentAwardDetails", () =
236236
InstitutionTokenTypes.CollegeFUser,
237237
);
238238

239-
const endpoint = `/institutions/assessment/student/${student.id}/assessment/9999999/award`;
239+
const endpoint = `/institutions/assessment/student/${student.id}/application/9999999/assessment/9999999/award`;
240240

241241
// Act/Assert
242242
await request(app.getHttpServer())

sources/packages/backend/apps/api/src/route-controllers/assessment/_tests_/e2e/assessment.institutions.controller.getAssessmentNOA.e2e-spec.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ describe("AssessmentInstitutionsController(e2e)-getAssessmentNOA", () => {
8181
disbursement.valueAmount;
8282
});
8383

84-
const endpoint = `/institutions/assessment/student/${student.id}/assessment/${assessment.id}/noa`;
84+
const endpoint = `/institutions/assessment/student/${student.id}/application/${application.id}/assessment/${assessment.id}/noa`;
8585
const institutionUserToken = await getInstitutionToken(
8686
InstitutionTokenTypes.CollegeFUser,
8787
);
@@ -149,7 +149,7 @@ describe("AssessmentInstitutionsController(e2e)-getAssessmentNOA", () => {
149149
application.currentAssessment.noaApprovalStatus = AssessmentStatus.required;
150150
application.currentAssessment.assessmentData = null;
151151
await db.application.save(application);
152-
const endpoint = `/institutions/assessment/student/${student.id}/assessment/${application.currentAssessment.id}/noa`;
152+
const endpoint = `/institutions/assessment/student/${student.id}/application/${application.id}/assessment/${application.currentAssessment.id}/noa`;
153153
const institutionUserToken = await getInstitutionToken(
154154
InstitutionTokenTypes.CollegeFUser,
155155
);
@@ -177,14 +177,14 @@ describe("AssessmentInstitutionsController(e2e)-getAssessmentNOA", () => {
177177
},
178178
);
179179
await db.msfaaNumber.save(currentMSFAA);
180-
await saveFakeApplicationDisbursements(db.dataSource, {
180+
const application = await saveFakeApplicationDisbursements(db.dataSource, {
181181
institution: collegeF,
182182
institutionLocation: collegeFLocation,
183183
student,
184184
msfaaNumber: currentMSFAA,
185185
});
186186

187-
const endpoint = `/institutions/assessment/student/${student.id}/assessment/9999999/noa`;
187+
const endpoint = `/institutions/assessment/student/${student.id}/application/${application.id}/assessment/9999999/noa`;
188188
const institutionUserToken = await getInstitutionToken(
189189
InstitutionTokenTypes.CollegeFUser,
190190
);
@@ -217,7 +217,7 @@ describe("AssessmentInstitutionsController(e2e)-getAssessmentNOA", () => {
217217
const collegeCInstitutionUserToken = await getInstitutionToken(
218218
InstitutionTokenTypes.CollegeCUser,
219219
);
220-
const endpoint = `/institutions/assessment/student/${student.id}/assessment/9999999/noa`;
220+
const endpoint = `/institutions/assessment/student/${student.id}/application/${collegeCApplication.id}/assessment/9999999/noa`;
221221

222222
// Act/Assert
223223
await request(app.getHttpServer())
@@ -240,7 +240,7 @@ describe("AssessmentInstitutionsController(e2e)-getAssessmentNOA", () => {
240240
InstitutionTokenTypes.CollegeFUser,
241241
);
242242

243-
const endpoint = `/institutions/assessment/student/${student.id}/assessment/9999999/noa`;
243+
const endpoint = `/institutions/assessment/student/${student.id}/application/9999999/assessment/9999999/noa`;
244244

245245
// Act/Assert
246246
await request(app.getHttpServer())

sources/packages/backend/apps/api/src/route-controllers/assessment/assessment.controller.service.ts

+13-5
Original file line numberDiff line numberDiff line change
@@ -46,19 +46,21 @@ export class AssessmentControllerService {
4646
* @param assessmentId assessment id to be retrieved.
4747
* @param options for NOA.
4848
* - `studentId` optional student for authorization when needed.
49+
* - `applicationId` application id,
4950
* - `maskMSFAA` mask MSFAA or not.
5051
* @returns notice of assessment data.
5152
*/
5253
async getAssessmentNOA(
5354
assessmentId: number,
5455
options?: {
5556
studentId?: number;
57+
applicationId?: number;
5658
maskMSFAA?: boolean;
5759
},
5860
): Promise<AssessmentNOAAPIOutDTO> {
5961
const assessment = await this.assessmentService.getAssessmentForNOA(
6062
assessmentId,
61-
options?.studentId,
63+
{ studentId: options?.studentId, applicationId: options?.applicationId },
6264
);
6365

6466
if (!assessment) {
@@ -152,17 +154,23 @@ export class AssessmentControllerService {
152154
* @param assessmentId assessment to which awards details belong to.
153155
* @param includeDocumentNumber when true document number is mapped
154156
* to disbursement dynamic data.
155-
* @param studentId studentId student to whom the award details belong to.
157+
* @param options options,
158+
* - `studentId` studentId student to whom the award details belong to.
159+
* - `applicationId` application is used for authorization purposes to
160+
* ensure that a user has access to the specific application data.
156161
* @returns estimated and actual award details.
157162
*/
158163
async getAssessmentAwardDetails(
159164
assessmentId: number,
160165
includeDocumentNumber = false,
161-
studentId?: number,
166+
options?: {
167+
studentId?: number;
168+
applicationId?: number;
169+
},
162170
): Promise<AwardDetailsAPIOutDTO> {
163171
const assessment = await this.assessmentService.getAssessmentForNOA(
164172
assessmentId,
165-
studentId,
173+
options,
166174
);
167175

168176
if (!assessment) {
@@ -180,7 +188,7 @@ export class AssessmentControllerService {
180188
const disbursementReceipts =
181189
await this.disbursementReceiptService.getDisbursementReceiptByAssessment(
182190
assessmentId,
183-
studentId,
191+
options,
184192
);
185193
if (disbursementReceipts.length) {
186194
finalAward = this.populateDisbursementReceiptAwardValues(

0 commit comments

Comments
 (0)