Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
b8b9a8f
current draft
Nov 5, 2025
ff42da4
debugging
isabellehuangg Nov 8, 2025
a060abe
cleanup
isabellehuangg Nov 8, 2025
3fd0746
data migration for createdAt and updatedAt
isabellehuangg Nov 12, 2025
f9e0422
Merge branch 'main' into application-status-mutator
isabellehuangg Nov 12, 2025
3b70eeb
use Review type in graphQL schema
isabellehuangg Nov 15, 2025
490d79a
DTO type name change
isabellehuangg Nov 15, 2025
b341617
type change
isabellehuangg Nov 15, 2025
db7354b
linter
isabellehuangg Nov 15, 2025
e4bc1ef
linting: obj destructuring
isabellehuangg Nov 15, 2025
68036aa
lint
isabellehuangg Nov 15, 2025
2b6ce00
add migration file and modify model
Nov 15, 2025
138f52d
added types and update query
gavxue Nov 19, 2025
20804ff
Merge branch 'main' of https://github.com/uwblueprint/website-bp-be i…
gavxue Nov 22, 2025
da8fc41
remove .claude
gavxue Nov 22, 2025
9601191
current draft
Nov 5, 2025
92fa619
debugging
isabellehuangg Nov 8, 2025
dabc4ca
cleanup
isabellehuangg Nov 8, 2025
7663acb
data migration for createdAt and updatedAt
isabellehuangg Nov 12, 2025
0a5f607
use Review type in graphQL schema
isabellehuangg Nov 15, 2025
737132b
DTO type name change
isabellehuangg Nov 15, 2025
6919859
type change
isabellehuangg Nov 15, 2025
d3de80a
linter
isabellehuangg Nov 15, 2025
3f24b84
linting: obj destructuring
isabellehuangg Nov 15, 2025
fd4a9b8
lint
isabellehuangg Nov 15, 2025
5c12c05
added types and update query
gavxue Nov 19, 2025
9c0548c
Merge branch 'INTF25-mutator-update-reviewed-applicant-record' of htt…
gavxue Nov 22, 2025
306c570
fix rebase duplication
gavxue Nov 22, 2025
2b81882
Merge branch 'main' of https://github.com/uwblueprint/website-bp-be i…
gavxue Nov 22, 2025
b8d5593
Merge branch 'main' of https://github.com/uwblueprint/website-bp-be i…
gavxue Nov 26, 2025
8feeab2
add score validation logic
gavxue Nov 26, 2025
ffd3b28
allow partial updates for scores
gavxue Nov 26, 2025
cf26453
Merge branch 'main' of https://github.com/uwblueprint/website-bp-be i…
gavxue Nov 26, 2025
05899c3
Merge branch 'main' of https://github.com/uwblueprint/website-bp-be i…
gavxue Dec 3, 2025
a4fdcaa
fix type import
gavxue Dec 3, 2025
b9bf03b
try catch block
gavxue Dec 6, 2025
42170f9
Merge branch 'main' of https://github.com/uwblueprint/website-bp-be i…
gavxue Feb 11, 2026
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
3 changes: 3 additions & 0 deletions backend/typescript/graphql/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ const executableSchema = makeExecutableSchema({
adminCommentType,
applicantRecordType,
reviewPageType,
reviewedApplicantRecordTypes,
],
resolvers: merge(
authResolvers,
Expand All @@ -62,6 +63,7 @@ const executableSchema = makeExecutableSchema({
adminCommentResolvers,
applicantRecordResolvers,
reviewPageResolvers,
reviewedApplicantRecordResolvers,
),
});

Expand Down Expand Up @@ -93,6 +95,7 @@ const graphQLMiddlewares = {
bulkCreateReviewedApplicantRecord: authorizedByAllRoles(),
deleteReviewedApplicantRecord: authorizedByAllRoles(),
bulkDeleteReviewedApplicantRecord: authorizedByAllRoles(),
updateReviewedApplicantRecord: authorizedByAllRoles(),
createUser: authorizedByAdmin(),
updateUser: authorizedByAdmin(),
deleteUserById: authorizedByAdmin(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
ReviewedApplicantRecordDTO,
CreateReviewedApplicantRecordDTO,
DeleteReviewedApplicantRecordDTO,
UpdateReviewedApplicantRecordDTO,
} from "../../types";
import { getErrorMessage } from "../../utilities/errorUtils";

Expand Down Expand Up @@ -61,6 +62,19 @@ const reviewedApplicantRecordResolvers = {
throw new Error(getErrorMessage(error));
}
},

updateReviewedApplicantRecord: async (
_parent: undefined,
args: { input: UpdateReviewedApplicantRecordDTO },
): Promise<ReviewedApplicantRecordDTO> => {
try {
return await reviewedApplicantRecordService.updateReviewedApplicantRecord(
args.input,
);
} catch (error) {
throw new Error(getErrorMessage(error));
}
},
},
};

Expand Down
11 changes: 11 additions & 0 deletions backend/typescript/graphql/types/reviewedApplicantRecordTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@ const reviewedApplicantRecordTypes = gql`
reviewerId: Int!
}

input UpdateReviewedApplicantRecordInput {
applicantRecordId: ID!
reviewerId: Int!
review: ReviewInput
status: String
}

extend type Mutation {
createReviewedApplicantRecord(
input: CreateReviewedApplicantRecordInput!
Expand All @@ -61,6 +68,10 @@ const reviewedApplicantRecordTypes = gql`
bulkDeleteReviewedApplicantRecord(
inputs: [DeleteReviewedApplicantRecord!]!
): [ReviewedApplicantRecord!]!

updateReviewedApplicantRecord(
input: UpdateReviewedApplicantRecordInput!
): ReviewedApplicantRecord!
}
`;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,45 @@
import { sequelize } from "../../models";
import ReviewedApplicantRecord from "../../models/reviewedApplicantRecord.model";
import ApplicantRecord from "../../models/applicantRecord.model";
import {
ReviewedApplicantRecordDTO,
CreateReviewedApplicantRecordDTO,
DeleteReviewedApplicantRecordDTO,
UpdateReviewedApplicantRecordDTO,
Review,
} from "../../types";
import { getErrorMessage } from "../../utilities/errorUtils";
import logger from "../../utilities/logger";
import IReviewApplicantRecordService from "../interfaces/IReviewedApplicantRecordService";

const Logger = logger(__filename);

function validateReviewScores(review: Review | undefined): void {
if (!review) return;

const scores = {
passionFSG: review.passionFSG,
teamPlayer: review.teamPlayer,
desireToLearn: review.desireToLearn,
skill: review.skill,
};

Object.entries(scores).forEach(([field, value]) => {
if (value !== undefined && (value < 1 || value > 5)) {
throw new Error(
`Invalid score for ${field}: ${value}. Scores must be between 1 and 5.`,
);
}
});
}

class ReviewedApplicantRecordService implements IReviewApplicantRecordService {
/* eslint-disable class-methods-use-this */
async createReviewedApplicantRecord(
dto: CreateReviewedApplicantRecordDTO,
): Promise<ReviewedApplicantRecordDTO> {
try {
validateReviewScores(dto.review);
const record = await ReviewedApplicantRecord.create(dto);
return record.toJSON() as ReviewedApplicantRecordDTO;
} catch (error: unknown) {
Expand All @@ -33,6 +56,10 @@ class ReviewedApplicantRecordService implements IReviewApplicantRecordService {
createReviewedApplicantRecordDTOs: CreateReviewedApplicantRecordDTO[],
): Promise<ReviewedApplicantRecordDTO[]> {
try {
createReviewedApplicantRecordDTOs.forEach((dto) => {
validateReviewScores(dto.review);
});

const reviewedApplicantRecords = await sequelize.transaction(
async (t) => {
const records = await ReviewedApplicantRecord.bulkCreate(
Expand Down Expand Up @@ -121,6 +148,90 @@ class ReviewedApplicantRecordService implements IReviewApplicantRecordService {
throw error;
}
}

/* eslint-disable class-methods-use-this */
async updateReviewedApplicantRecord({
applicantRecordId,
reviewerId,
review,
status,
}: UpdateReviewedApplicantRecordDTO): Promise<ReviewedApplicantRecordDTO> {
try {
const updatedRecord = await sequelize.transaction(async (t) => {
const reviewedRecord = await ReviewedApplicantRecord.findOne({
where: { applicantRecordId, reviewerId },
transaction: t,
});

if (!reviewedRecord) {
throw new Error(
`ReviewedApplicantRecord not found for applicantRecordId: ${applicantRecordId} and reviewerId: ${reviewerId}`,
);
}

const oldReviewedScore = reviewedRecord.score || 0;

if (review !== undefined) {
validateReviewScores(review);

reviewedRecord.review = {
...reviewedRecord.review,
...review,
};

const { passionFSG, teamPlayer, desireToLearn, skill } =
reviewedRecord.review;

let calculatedScore = 0;
if (passionFSG !== undefined) calculatedScore += passionFSG;
if (teamPlayer !== undefined) calculatedScore += teamPlayer;
if (desireToLearn !== undefined) calculatedScore += desireToLearn;
if (skill !== undefined) calculatedScore += skill;
reviewedRecord.score = calculatedScore;

if (review.skillCategory !== undefined) {
reviewedRecord.skillCategory = review.skillCategory;
}
}

if (status !== undefined) {
reviewedRecord.status = status;
}

await reviewedRecord.save({ transaction: t });

const newReviewedScore = reviewedRecord.score || 0;

const applicantRecord = await ApplicantRecord.findOne({
where: { id: applicantRecordId },
transaction: t,
});

if (!applicantRecord) {
throw new Error(
`ApplicantRecord not found for applicantRecordId: ${applicantRecordId}`,
);
}

const oldCombinedScore = applicantRecord.combined_score || 0;
applicantRecord.combined_score =
oldCombinedScore - oldReviewedScore + newReviewedScore;

await applicantRecord.save({ transaction: t });

return reviewedRecord;
});

return updatedRecord.toJSON() as ReviewedApplicantRecordDTO;
} catch (error: unknown) {
Logger.error(
`Failed to update reviewed applicant record. Reason = ${getErrorMessage(
error,
)}`,
);
throw error;
}
}
}

export default ReviewedApplicantRecordService;
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
ReviewedApplicantRecordDTO,
CreateReviewedApplicantRecordDTO,
DeleteReviewedApplicantRecordDTO,
UpdateReviewedApplicantRecordDTO,
} from "../../types";

interface IReviewApplicantRecordService {
Expand Down Expand Up @@ -37,6 +38,15 @@ interface IReviewApplicantRecordService {
bulkDeleteReviewedApplicantRecord(
deleteReviewedApplicantRecords: DeleteReviewedApplicantRecordDTO[],
): Promise<ReviewedApplicantRecordDTO[]>;

/**
* Updates the review content and/or status of a ReviewedApplicantRecord
* Also updates the combined score in the ApplicantRecord table
* @Param updateReviewedApplicantRecordDTO data to update reviewed applicant record
*/
updateReviewedApplicantRecord(
updateReviewedApplicantRecordDTO: UpdateReviewedApplicantRecordDTO,
): Promise<ReviewedApplicantRecordDTO>;
}

export default IReviewApplicantRecordService;
7 changes: 7 additions & 0 deletions backend/typescript/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,13 @@ export type DeleteReviewedApplicantRecordDTO = {
reviewerId: number;
};

export type UpdateReviewedApplicantRecordDTO = {
applicantRecordId: string;
reviewerId: number;
review?: Review;
status?: ReviewStatus;
};

export type ReviewDetails = {
reviewerFirstName: string;
reviewerLastName: string;
Expand Down
Loading