diff --git a/backend/typescript/graphql/index.ts b/backend/typescript/graphql/index.ts index 7b758379..09f85ceb 100644 --- a/backend/typescript/graphql/index.ts +++ b/backend/typescript/graphql/index.ts @@ -19,6 +19,8 @@ import reviewDashboardResolvers from "./resolvers/reviewDashboardResolvers"; import reviewDashboardType from "./types/reviewDashboardType"; import adminCommentResolvers from "./resolvers/adminCommentsResolvers"; import adminCommentType from "./types/adminCommentsType"; +import applicantRecordResolvers from "./resolvers/applicantRecordResolvers"; +import applicantRecordType from "./types/applicantRecordType"; import reviewPageType from "./types/reviewPageType"; import reviewPageResolvers from "./resolvers/reviewPageResolvers"; @@ -44,6 +46,7 @@ const executableSchema = makeExecutableSchema({ userType, reviewDashboardType, adminCommentType, + applicantRecordType, reviewPageType, ], resolvers: merge( @@ -53,6 +56,7 @@ const executableSchema = makeExecutableSchema({ userResolvers, reviewDashboardResolvers, adminCommentResolvers, + applicantRecordResolvers, reviewPageResolvers, ), }); diff --git a/backend/typescript/graphql/resolvers/applicantRecordResolvers.ts b/backend/typescript/graphql/resolvers/applicantRecordResolvers.ts new file mode 100644 index 00000000..aaf105d4 --- /dev/null +++ b/backend/typescript/graphql/resolvers/applicantRecordResolvers.ts @@ -0,0 +1,32 @@ +import ApplicantRecordService from "../../services/implementations/applicantRecordService"; +import IApplicantRecordService from "../../services/interfaces/applicantRecordService"; +import { ApplicantRecordDTO } from "../../types"; +import { getErrorMessage } from "../../utilities/errorUtils"; + +const applicantRecordService: IApplicantRecordService = + new ApplicantRecordService(); + +const applicantRecordResolvers = { + Mutation: { + setApplicantRecordFlag: async ( + _parent: undefined, + { + applicantRecordId, + flagValue, + }: { applicantRecordId: string; flagValue: boolean }, + ): Promise => { + try { + const applicantRecord = + await applicantRecordService.setApplicantRecordFlag( + applicantRecordId, + flagValue, + ); + return applicantRecord; + } catch (error: unknown) { + throw new Error(getErrorMessage(error)); + } + }, + }, +}; + +export default applicantRecordResolvers; diff --git a/backend/typescript/graphql/types/applicantRecordType.ts b/backend/typescript/graphql/types/applicantRecordType.ts new file mode 100644 index 00000000..e1702399 --- /dev/null +++ b/backend/typescript/graphql/types/applicantRecordType.ts @@ -0,0 +1,24 @@ +import { gql } from "apollo-server-express"; + +const applicantRecordType = gql` + type ApplicantRecordDTO { + id: String! + applicantId: String! + position: String! + roleSpecificQuestions: [String!]! + choice: Int! + status: String! + skillCategory: String + combined_score: Int + isApplicantFlagged: Boolean! + } + + extend type Mutation { + setApplicantRecordFlag( + applicantRecordId: String! + flagValue: Boolean! + ): ApplicantRecordDTO! + } +`; + +export default applicantRecordType; diff --git a/backend/typescript/migrations/20251112014713-add-createdat-applicant-records.ts b/backend/typescript/migrations/20251112014713-add-createdat-applicant-records.ts new file mode 100644 index 00000000..6ba7b5e7 --- /dev/null +++ b/backend/typescript/migrations/20251112014713-add-createdat-applicant-records.ts @@ -0,0 +1,22 @@ +import { DataType } from "sequelize-typescript"; +import { Migration } from "../umzug"; + +const TABLE_NAME = "applicant_records"; + +export const up: Migration = async ({ context: sequelize }) => { + await sequelize.getQueryInterface().addColumn(TABLE_NAME, "createdAt", { + type: DataType.DATE, + allowNull: false, + defaultValue: new Date(), + }); + await sequelize.getQueryInterface().addColumn(TABLE_NAME, "updatedAt", { + type: DataType.DATE, + allowNull: false, + defaultValue: new Date(), + }); +}; + +export const down: Migration = async ({ context: sequelize }) => { + await sequelize.getQueryInterface().removeColumn(TABLE_NAME, "createdAt"); + await sequelize.getQueryInterface().removeColumn(TABLE_NAME, "updatedAt"); +}; diff --git a/backend/typescript/services/implementations/applicantRecordService.ts b/backend/typescript/services/implementations/applicantRecordService.ts new file mode 100644 index 00000000..21d1c050 --- /dev/null +++ b/backend/typescript/services/implementations/applicantRecordService.ts @@ -0,0 +1,46 @@ +import { ApplicantRecordDTO, PositionTitle } from "../../types"; +import { getErrorMessage } from "../../utilities/errorUtils"; +import logger from "../../utilities/logger"; +import ApplicantRecord from "../../models/applicantRecord.model"; +import IApplicantRecordService from "../interfaces/applicantRecordService"; + +const Logger = logger(__filename); + +class ApplicantRecordService implements IApplicantRecordService { + /* eslint-disable class-methods-use-this */ + async setApplicantRecordFlag( + applicantRecordId: string, + flagValue: boolean, + ): Promise { + try { + const applicantRecord = await ApplicantRecord.findByPk(applicantRecordId); + if (!applicantRecord) { + throw new Error( + `ApplicantRecord with id ${applicantRecordId} not found.`, + ); + } + applicantRecord.isApplicantFlagged = flagValue; + await applicantRecord.save(); + return { + id: String(applicantRecord.id), + applicantId: String(applicantRecord.applicantId), + position: applicantRecord.position as PositionTitle, + roleSpecificQuestions: applicantRecord.roleSpecificQuestions, + choice: applicantRecord.choice, + status: applicantRecord.status, + skillCategory: applicantRecord.skillCategory, + combined_score: applicantRecord.combined_score, + isApplicantFlagged: applicantRecord.isApplicantFlagged, + }; + } catch (error: unknown) { + Logger.error( + `Failed to set applicant record flag. Reason = ${getErrorMessage( + error, + )}`, + ); + throw error; + } + } +} + +export default ApplicantRecordService; diff --git a/backend/typescript/services/interfaces/applicantRecordService.ts b/backend/typescript/services/interfaces/applicantRecordService.ts new file mode 100644 index 00000000..7d7a19d7 --- /dev/null +++ b/backend/typescript/services/interfaces/applicantRecordService.ts @@ -0,0 +1,10 @@ +import { ApplicantRecordDTO } from "../../types"; + +interface IApplicantRecordService { + setApplicantRecordFlag( + applicantRecordId: string, + flagValue: boolean, + ): Promise; +} + +export default IApplicantRecordService; diff --git a/backend/typescript/types.ts b/backend/typescript/types.ts index b423dc77..ff295fa5 100644 --- a/backend/typescript/types.ts +++ b/backend/typescript/types.ts @@ -77,7 +77,7 @@ export type ApplicantDTO = { }; export type ApplicantRecordDTO = { - id: number; + id: string; applicantId: string; position: PositionTitle; // EDIT LATER roleSpecificQuestions: string[]; @@ -85,6 +85,7 @@ export type ApplicantRecordDTO = { status: ApplicationStatus; skillCategory?: SkillCategory; combined_score?: number | null; + isApplicantFlagged: boolean; }; export type ApplicationStatus =