Skip to content

Commit 42170f9

Browse files
committed
Merge branch 'main' of https://github.com/uwblueprint/website-bp-be into INTF25-mutator-update-reviewed-applicant-record
2 parents b9bf03b + e684b4a commit 42170f9

File tree

13 files changed

+367
-8
lines changed

13 files changed

+367
-8
lines changed

backend/typescript/graphql/resolvers/authResolvers.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,19 @@ const authResolvers = {
5353
);
5454
return isAuthorized;
5555
},
56+
isAuthorizedReviewer: async (
57+
_parent: undefined,
58+
{
59+
accessToken,
60+
applicantRecordId,
61+
}: { accessToken: string; applicantRecordId: string },
62+
): Promise<boolean> => {
63+
const isAuthorized = await authService.isAuthorizedReviewer(
64+
accessToken,
65+
applicantRecordId,
66+
);
67+
return isAuthorized;
68+
},
5669
},
5770
Mutation: {
5871
login: async (

backend/typescript/graphql/resolvers/reviewPageResolvers.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import { ApplicationDTO } from "../../types";
1+
import {
2+
ApplicationDTO,
3+
ReviewedApplicantRecordDTO,
4+
ReviewedApplicantsDTO,
5+
} from "../../types";
26
import ReviewPageService from "../../services/implementations/reviewPageService";
37
import { getErrorMessage } from "../../utilities/errorUtils";
48

@@ -16,6 +20,33 @@ const reviewPageResolvers = {
1620
throw new Error(getErrorMessage(error));
1721
}
1822
},
23+
getReviewedApplicantsByUserId: async (
24+
_parent: undefined,
25+
args: { userId: number },
26+
): Promise<ReviewedApplicantsDTO[]> => {
27+
try {
28+
return await reviewPageService.getReviewedApplicantsByUserId(
29+
args.userId,
30+
);
31+
} catch (error) {
32+
throw new Error(getErrorMessage(error));
33+
}
34+
},
35+
},
36+
Mutation: {
37+
reportReviewConflict: async (
38+
_parent: undefined,
39+
args: { applicantRecordId: string; reviewerId: number },
40+
): Promise<ReviewedApplicantRecordDTO> => {
41+
try {
42+
return await reviewPageService.reportReviewConflict(
43+
args.applicantRecordId,
44+
args.reviewerId,
45+
);
46+
} catch (error) {
47+
throw new Error(getErrorMessage(error));
48+
}
49+
},
1950
},
2051
};
2152

backend/typescript/graphql/types/authType.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ const authType = gql`
2525
extend type Query {
2626
login(email: String!, password: String!): loginOK!
2727
isAuthorizedByRole(accessToken: String!, roles: [Role!]!): Boolean!
28+
isAuthorizedReviewer(
29+
accessToken: String!
30+
applicantRecordId: String!
31+
): Boolean!
2832
}
2933
3034
extend type Mutation {

backend/typescript/graphql/types/reviewPageType.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,32 @@ const reviewPageType = gql`
2525
timestamp: Int
2626
}
2727
28+
type ReviewedApplicantsDTO {
29+
applicantRecordId: String!
30+
reviewStatus: String!
31+
applicantFirstName: String!
32+
applicantLastName: String!
33+
}
34+
35+
type ReviewedApplicantRecordDTO {
36+
applicantRecordId: String!
37+
reviewerId: Int!
38+
review: Review!
39+
status: String!
40+
score: Int
41+
reviewerHasConflict: Boolean!
42+
}
43+
44+
extend type Mutation {
45+
reportReviewConflict(
46+
applicantRecordId: String!
47+
reviewerId: Int!
48+
): ReviewedApplicantRecordDTO!
49+
}
50+
2851
extend type Query {
2952
reviewApplicantPage(applicantRecordId: String!): ApplicationDTO!
53+
getReviewedApplicantsByUserId(userId: Int!): [ReviewedApplicantsDTO!]!
3054
}
3155
`;
3256

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { DataTypes } from "sequelize";
2+
import { Migration } from "../umzug";
3+
4+
const TABLE_NAME = "firebase_files";
5+
6+
export const up: Migration = async ({ context: sequelize }) => {
7+
await sequelize.getQueryInterface().createTable(TABLE_NAME, {
8+
id: {
9+
type: DataTypes.UUID,
10+
defaultValue: DataTypes.UUIDV4,
11+
allowNull: false,
12+
primaryKey: true,
13+
},
14+
storagePath: {
15+
type: DataTypes.STRING,
16+
allowNull: false,
17+
},
18+
originalFileName: {
19+
type: DataTypes.STRING,
20+
allowNull: false,
21+
},
22+
uploadedUserId: {
23+
type: DataTypes.INTEGER,
24+
allowNull: false,
25+
references: {
26+
model: "users",
27+
key: "id",
28+
},
29+
},
30+
sizeBytes: {
31+
type: DataTypes.BIGINT,
32+
allowNull: false,
33+
},
34+
createdAt: {
35+
type: DataTypes.DATE,
36+
allowNull: false,
37+
defaultValue: DataTypes.NOW,
38+
},
39+
updatedAt: {
40+
type: DataTypes.DATE,
41+
allowNull: false,
42+
defaultValue: DataTypes.NOW,
43+
},
44+
});
45+
};
46+
47+
export const down: Migration = async ({ context: sequelize }) => {
48+
await sequelize.getQueryInterface().dropTable(TABLE_NAME);
49+
};
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import {
2+
Column,
3+
DataType,
4+
ForeignKey,
5+
Model,
6+
Table,
7+
} from "sequelize-typescript";
8+
import User from "./user.model";
9+
10+
@Table({ tableName: "firebase_files" })
11+
export default class File extends Model {
12+
@Column({
13+
type: DataType.UUID,
14+
defaultValue: DataType.UUIDV4,
15+
primaryKey: true,
16+
})
17+
id!: string;
18+
19+
@Column({ type: DataType.STRING, allowNull: false })
20+
storagePath!: string;
21+
22+
@Column({
23+
type: DataType.STRING,
24+
allowNull: false,
25+
})
26+
originalFileName!: string;
27+
28+
@ForeignKey(() => User)
29+
@Column({
30+
type: DataType.INTEGER,
31+
allowNull: false,
32+
})
33+
uploadedUserId!: number;
34+
35+
@Column({ type: DataType.BIGINT, allowNull: false })
36+
sizeBytes!: bigint;
37+
38+
@Column({ type: DataType.DATE, allowNull: false })
39+
createdAt!: Date;
40+
41+
@Column({ type: DataType.DATE, allowNull: false })
42+
updatedAt!: Date;
43+
}

backend/typescript/models/reviewedApplicantRecord.model.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,10 @@ export default class ReviewedApplicantRecord extends Model {
5959

6060
@BelongsTo(() => User, { foreignKey: "reviewerId", targetKey: "id" })
6161
user?: NonAttribute<User>;
62+
63+
@BelongsTo(() => ApplicantRecord, {
64+
foreignKey: "applicantRecordId",
65+
targetKey: "id",
66+
})
67+
applicantRecord?: NonAttribute<ApplicantRecord>;
6268
}

backend/typescript/models/user.model.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export default class User extends Model {
3939

4040
@HasMany(() => ReviewedApplicantRecord, {
4141
foreignKey: "reviewerId",
42-
as: "user",
42+
sourceKey: "id",
4343
})
4444
reviewedApplicantRecords?: NonAttribute<ReviewedApplicantRecord[]>;
4545
}

backend/typescript/services/implementations/authService.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { AuthDTO, Role, Token } from "../../types";
77
import { getErrorMessage } from "../../utilities/errorUtils";
88
import FirebaseRestClient from "../../utilities/firebaseRestClient";
99
import logger from "../../utilities/logger";
10+
import ReviewedApplicantRecord from "../../models/reviewedApplicantRecord.model";
1011

1112
const Logger = logger(__filename);
1213

@@ -272,6 +273,41 @@ class AuthService implements IAuthService {
272273
throw error;
273274
}
274275
}
276+
277+
async isAuthorizedReviewer(
278+
accessToken: string,
279+
applicantRecordId: string,
280+
): Promise<boolean> {
281+
try {
282+
const decodedIdToken: firebaseAdmin.auth.DecodedIdToken =
283+
await firebaseAdmin.auth().verifyIdToken(accessToken, true);
284+
const userId = await this.userService.getUserIdByAuthId(
285+
decodedIdToken.uid,
286+
);
287+
288+
const firebaseUser = await firebaseAdmin
289+
.auth()
290+
.getUser(decodedIdToken.uid);
291+
292+
if (!firebaseUser.emailVerified) {
293+
return false;
294+
}
295+
296+
const reviewedApplicantRecord = await ReviewedApplicantRecord.findOne({
297+
where: {
298+
applicantRecordId,
299+
reviewerId: Number(userId),
300+
},
301+
});
302+
303+
return reviewedApplicantRecord !== null;
304+
} catch (error) {
305+
Logger.error(
306+
`Failed to verify if user is authorized reviewer for applicant record ${applicantRecordId}`,
307+
);
308+
throw error;
309+
}
310+
}
275311
}
276312

277313
export default AuthService;

0 commit comments

Comments
 (0)