Skip to content
8 changes: 8 additions & 0 deletions backend/typescript/graphql/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import userResolvers from "./resolvers/userResolvers";
import userType from "./types/userType";
import reviewDashboardResolvers from "./resolvers/reviewDashboardResolvers";
import reviewDashboardType from "./types/reviewDashboardType";
import reviewedApplicantRecordTypes from "./types/reviewedApplicantRecordTypes";
import reviewedApplicantRecordResolvers from "./resolvers/reviewedApplicantRecordResolver";
import adminCommentResolvers from "./resolvers/adminCommentsResolvers";
import adminCommentType from "./types/adminCommentsType";
import reviewPageType from "./types/reviewPageType";
Expand All @@ -43,6 +45,7 @@ const executableSchema = makeExecutableSchema({
simpleEntityType,
userType,
reviewDashboardType,
reviewedApplicantRecordTypes,
adminCommentType,
reviewPageType,
],
Expand All @@ -52,6 +55,7 @@ const executableSchema = makeExecutableSchema({
simpleEntityResolvers,
userResolvers,
reviewDashboardResolvers,
reviewedApplicantRecordResolvers,
adminCommentResolvers,
reviewPageResolvers,
),
Expand Down Expand Up @@ -79,6 +83,10 @@ const graphQLMiddlewares = {
createSimpleEntity: authorizedByAllRoles(),
updateSimpleEntity: authorizedByAllRoles(),
deleteSimpleEntity: authorizedByAllRoles(),
createReviewedApplicantRecord: authorizedByAllRoles(),
bulkCreateReviewedApplicantRecord: authorizedByAllRoles(),
deleteReviewedApplicantRecord: authorizedByAllRoles(),
bulkDeleteReviewedApplicantRecord: authorizedByAllRoles(),
createUser: authorizedByAdmin(),
updateUser: authorizedByAdmin(),
deleteUserById: authorizedByAdmin(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import ReviewedApplicantRecordService from "../../services/implementations/reviewedApplicantRecordService";
import {
ReviewedApplicantRecordDTO,
CreateReviewedApplicantRecordDTO,
DeleteReviewedApplicantRecordDTO,
} from "../../types";
import { getErrorMessage } from "../../utilities/errorUtils";

const reviewedApplicantRecordService = new ReviewedApplicantRecordService();

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

bulkCreateReviewedApplicantRecord: async (
_parent: undefined,
args: { inputs: CreateReviewedApplicantRecordDTO[] },
): Promise<ReviewedApplicantRecordDTO[]> => {
try {
return await reviewedApplicantRecordService.bulkCreateReviewedApplicantRecord(
args.inputs,
);
} catch (error) {
throw new Error(getErrorMessage(error));
}
},

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

bulkDeleteReviewedApplicantRecord: async (
_parent: undefined,
args: { inputs: DeleteReviewedApplicantRecordDTO[] },
): Promise<ReviewedApplicantRecordDTO[]> => {
try {
return await reviewedApplicantRecordService.bulkDeleteReviewedApplicantRecord(
args.inputs,
);
} catch (error) {
throw new Error(getErrorMessage(error));
}
},
},
};

export default reviewedApplicantRecordResolvers;
66 changes: 66 additions & 0 deletions backend/typescript/graphql/types/reviewedApplicantRecordTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { gql } from "apollo-server-express";

const reviewedApplicantRecordTypes = gql`
enum SkillCategory {
JUNIOR
INTERMEDIATE
SENIOR
}

type Review {
passionFSG: Int
teamPlayer: Int
desireToLearn: Int
skill: Int
skillCategory: SkillCategory
}

input ReviewInput {
passionFSG: Int
teamPlayer: Int
desireToLearn: Int
skill: Int
skillCategory: SkillCategory
}

type ReviewedApplicantRecord {
applicantRecordId: ID!
reviewerId: Int!
review: Review
status: String
score: Int
reviewerHasConflict: Boolean
}

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

input DeleteReviewedApplicantRecord {
applicantRecordId: ID!
reviewerId: Int!
}

extend type Mutation {
createReviewedApplicantRecord(
input: CreateReviewedApplicantRecordInput!
): ReviewedApplicantRecord!

bulkCreateReviewedApplicantRecord(
inputs: [CreateReviewedApplicantRecordInput!]!
): [ReviewedApplicantRecord!]!

deleteReviewedApplicantRecord(
input: DeleteReviewedApplicantRecord!
): ReviewedApplicantRecord!

bulkDeleteReviewedApplicantRecord(
inputs: [DeleteReviewedApplicantRecord!]!
): [ReviewedApplicantRecord!]!
}
`;

export default reviewedApplicantRecordTypes;
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { DataType } from "sequelize-typescript";
import { Migration } from "../umzug";

const TABLE_NAME = "reviewed_applicant_records";

export const up: Migration = async ({ context: sequelize }) => {
await sequelize.getQueryInterface().addColumn(TABLE_NAME, "createdAt", {
type: DataType.DATE,
allowNull: false,
defaultValue: DataType.NOW,
});

await sequelize.getQueryInterface().addColumn(TABLE_NAME, "updatedAt", {
type: DataType.DATE,
allowNull: false,
defaultValue: DataType.NOW,
});
};

export const down: Migration = async ({ context: sequelize }) => {
await sequelize.getQueryInterface().removeColumn(TABLE_NAME, "createdAt");
await sequelize.getQueryInterface().removeColumn(TABLE_NAME, "updatedAt");
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { sequelize } from "../../models";
import ReviewedApplicantRecord from "../../models/reviewedApplicantRecord.model";
import {
ReviewedApplicantRecordDTO,
CreateReviewedApplicantRecordDTO,
DeleteReviewedApplicantRecordDTO,
} from "../../types";
import { getErrorMessage } from "../../utilities/errorUtils";
import logger from "../../utilities/logger";
import IReviewApplicantRecordService from "../interfaces/IReviewedApplicantRecordService";

const Logger = logger(__filename);

class ReviewedApplicantRecordService implements IReviewApplicantRecordService {
/* eslint-disable class-methods-use-this */
async createReviewedApplicantRecord(
dto: CreateReviewedApplicantRecordDTO,
): Promise<ReviewedApplicantRecordDTO> {
try {
const record = await ReviewedApplicantRecord.create(dto);
return record.toJSON() as ReviewedApplicantRecordDTO;
} catch (error: unknown) {
Logger.error(
`Failed to create reviewed applicant record. Reason = ${getErrorMessage(
error,
)}`,
);
throw error;
}
}

async bulkCreateReviewedApplicantRecord(
createReviewedApplicantRecordDTOs: CreateReviewedApplicantRecordDTO[],
): Promise<ReviewedApplicantRecordDTO[]> {
try {
const reviewedApplicantRecords = await sequelize.transaction(
async (t) => {
const records = await ReviewedApplicantRecord.bulkCreate(
createReviewedApplicantRecordDTOs,
{ transaction: t },
);
return records;
},
);

return reviewedApplicantRecords.map(
(record) => record.toJSON() as ReviewedApplicantRecordDTO,
);
} catch (error: unknown) {
Logger.error(
`Failed to bulk create reviewed applicant records. Reason = ${getErrorMessage(
error,
)}`,
);
throw error;
}
}

async deleteReviewedApplicantRecord(
deleteReviewedApplicantRecord: DeleteReviewedApplicantRecordDTO,
): Promise<ReviewedApplicantRecordDTO> {
try {
const { applicantRecordId } = deleteReviewedApplicantRecord;
const { reviewerId } = deleteReviewedApplicantRecord;
const record = await ReviewedApplicantRecord.findOne({
where: { applicantRecordId, reviewerId },
});

if (!record) {
throw new Error("ReviewedApplicantRecord not found, delete failed");
}

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

async bulkDeleteReviewedApplicantRecord(
deleteReviewedApplicantRecords: DeleteReviewedApplicantRecordDTO[],
): Promise<ReviewedApplicantRecordDTO[]> {
try {
const deletedRecords = await sequelize.transaction(async (t) => {
const records = await Promise.all(
deleteReviewedApplicantRecords.map(
({ applicantRecordId, reviewerId }) =>
ReviewedApplicantRecord.findOne({
where: { applicantRecordId, reviewerId },
transaction: t,
}),
),
);

if (records.some((r) => !r)) {
throw new Error("Not all records were found, bulk delete failed");
}

const existingRecords = records as ReviewedApplicantRecord[];
await Promise.all(
existingRecords.map((r) => r.destroy({ transaction: t })),
);

return existingRecords;
});

return deletedRecords.map(
(r) => r.toJSON() as ReviewedApplicantRecordDTO,
);
} catch (error: unknown) {
Logger.error(
`Failed to bulk delete reviewed applicant records. Reason = ${getErrorMessage(
error,
)}`,
);
throw error;
}
}
}

export default ReviewedApplicantRecordService;
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import {
ReviewedApplicantRecordDTO,
CreateReviewedApplicantRecordDTO,
DeleteReviewedApplicantRecordDTO,
} from "../../types";

interface IReviewApplicantRecordService {
/**
* Creates a single reviewed applicant record entry
* @Param createReviewedApplicantRecordDTO data to create reviewed applicant record
*/
createReviewedApplicantRecord(
createReviewedApplicantRecordDTO: CreateReviewedApplicantRecordDTO,
): Promise<ReviewedApplicantRecordDTO>;

/**
* Creates multiple reviewed applicant record entries in bulk
* @Param createReviewedApplicantRecordDTOs array of data to create reviewed applicant records
*/
bulkCreateReviewedApplicantRecord(
createReviewedApplicantRecordDTOs: CreateReviewedApplicantRecordDTO[],
): Promise<ReviewedApplicantRecordDTO[]>;

/**
* Deletes a single reviewed applicant record entry
* @Param applicantRecordId the ID of applicant record to delete
* @Param reviewerId the ID of the reviewer
*/
deleteReviewedApplicantRecord(
deleteReviewedApplicantRecord: DeleteReviewedApplicantRecordDTO,
): Promise<ReviewedApplicantRecordDTO>;

/**
* Deletes multiple reviewed applicant record entries in bulk
* @Param deleteReviewedApplicantRecord array of data to delete reviewed applicant records
*/
bulkDeleteReviewedApplicantRecord(
deleteReviewedApplicantRecords: DeleteReviewedApplicantRecordDTO[],
): Promise<ReviewedApplicantRecordDTO[]>;
}

export default IReviewApplicantRecordService;
12 changes: 12 additions & 0 deletions backend/typescript/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,18 @@ export type ReviewedApplicantRecordDTO = {
reviewerHasConflict: boolean;
};

export type CreateReviewedApplicantRecordDTO = {
applicantRecordId: string;
reviewerId: number;
review?: Review;
reviewerHasConflict?: boolean;
};

export type DeleteReviewedApplicantRecordDTO = {
applicantRecordId: string;
reviewerId: number;
};

export type AdminCommentDTO = {
id: string;
userId: number;
Expand Down
Loading