Skip to content
47 changes: 47 additions & 0 deletions backend/typescript/graphql/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { makeExecutableSchema, gql } from "apollo-server-express";
import { applyMiddleware } from "graphql-middleware";
import { merge } from "lodash";
import { GraphQLScalarType, Kind } from "graphql";

import {
isAuthorizedByEmail,
Expand All @@ -17,6 +18,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 @@ -34,24 +37,60 @@ const mutation = gql`
}
`;

const scalarTypes = gql`
scalar JSON
`;

const JSONScalar = new GraphQLScalarType({
name: "JSON",
description: "JSON scalar type",
serialize: (value) => value,
parseValue: (value) => value,
parseLiteral: (ast) => {
switch (ast.kind) {
case Kind.STRING:
case Kind.BOOLEAN:
return ast.value;
case Kind.INT:
case Kind.FLOAT:
return parseFloat(ast.value);
case Kind.OBJECT:
return ast.fields.reduce((obj: any, field: any) => {
obj[field.name.value] = JSONScalar.parseLiteral(field.value, {});
return obj;
}, {});
case Kind.LIST:
return ast.values.map((value: any) => JSONScalar.parseLiteral(value, {}));
default:
return null;
}
},
});

const executableSchema = makeExecutableSchema({
typeDefs: [
query,
mutation,
scalarTypes,
authType,
entityType,
simpleEntityType,
userType,
reviewDashboardType,
reviewedApplicantRecordTypes
adminCommentType,
reviewPageType,
],
resolvers: merge(
{
JSON: JSONScalar,
},
authResolvers,
entityResolvers,
simpleEntityResolvers,
userResolvers,
reviewDashboardResolvers,
reviewedApplicantRecordResolvers,
adminCommentResolvers,
reviewPageResolvers,
),
Expand Down Expand Up @@ -79,6 +118,14 @@ const graphQLMiddlewares = {
createSimpleEntity: authorizedByAllRoles(),
updateSimpleEntity: authorizedByAllRoles(),
deleteSimpleEntity: authorizedByAllRoles(),
changeRating: authorizedByAllRoles(),
changeSkillCategory: authorizedByAllRoles(),
updateApplications: authorizedByAllRoles(),
modifyFinalComments: authorizedByAllRoles(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is legacy code I deleted in another PR... would you mind merging to main to get rid of this LOL, sorry I forgot everyone to pull main when I merged that PR in

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,63 @@
import ReviewedApplicantRecordService from "../../services/implementations/reviewedApplicantRecordService";
import {
ReviewedApplicantRecordDTO,
CreateReviewedApplicantRecordDTO,
DeleteReviewedApplicantRecord
} 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: DeleteReviewedApplicantRecord },
): Promise<ReviewedApplicantRecordDTO> => {
try {
return await reviewedApplicantRecordService.deleteReviewedApplicantRecord(
args.input
);
} catch (error) {
throw new Error(getErrorMessage(error));
}
},

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

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

const reviewedApplicantRecordTypes = gql`
type ReviewedApplicantRecord {
applicantRecordId: ID!
reviewerId: Int!
review: JSON
status: String
score: Int
reviewerHasConflict: Boolean
}

input CreateReviewedApplicantRecordInput {
applicantRecordId: ID!
reviewerId: Int!
review: JSON
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,74 @@
import { sequelize } from "../../models";
import ReviewedApplicantRecord from "../../models/reviewedApplicantRecord.model";
import {
ReviewedApplicantRecordDTO,
CreateReviewedApplicantRecordDTO,
DeleteReviewedApplicantRecord
} from "../../types";

import IReviewApplicantRecordService from "../interfaces/IReviewedApplicantRecordService";

class ReviewedApplicantRecordService implements IReviewApplicantRecordService {

async createReviewedApplicantRecord(
dto: CreateReviewedApplicantRecordDTO
): Promise<ReviewedApplicantRecordDTO> {
const record = await ReviewedApplicantRecord.create(dto);
return record.toJSON() as ReviewedApplicantRecordDTO;
}

async bulkCreateReviewedApplicantRecord(
createReviewedApplicantRecordDTOs: CreateReviewedApplicantRecordDTO[]
): Promise<ReviewedApplicantRecordDTO[]> {
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
);
}

async deleteReviewedApplicantRecord(deleteReviewedApplicantRecord: DeleteReviewedApplicantRecord): Promise<ReviewedApplicantRecordDTO> {
const applicantRecordId = deleteReviewedApplicantRecord.applicantRecordId;
const reviewerId = deleteReviewedApplicantRecord.reviewerId;
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;
}

async bulkDeleteReviewedApplicantRecord(deleteReviewedApplicantRecords: DeleteReviewedApplicantRecord[]): Promise<ReviewedApplicantRecordDTO[]> {
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);
}
}

export default ReviewedApplicantRecordService;
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import {
ReviewedApplicantRecordDTO,
CreateReviewedApplicantRecordDTO,
DeleteReviewedApplicantRecord
} 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: DeleteReviewedApplicantRecord
): Promise<ReviewedApplicantRecordDTO>;

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

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

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

export type DeleteReviewedApplicantRecord = {
applicantRecordId: string;
reviewerId: number;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: just to keep with naming conventions, this type should be DeleteReviewedApplicantRecord

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DeleteReviewedApplicantRecordDTO

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