Skip to content

Commit e34f512

Browse files
WIP: applicant record service
1 parent 83b1a51 commit e34f512

File tree

7 files changed

+203
-4
lines changed

7 files changed

+203
-4
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import ApplicantRecord from "../../models/applicantRecord.model";
2+
import ApplicantRecordService from "../../services/implementations/applicantRecordService";
3+
import { PositionTitle } from "../../types";
4+
5+
const applicantRecordService = new ApplicantRecordService();
6+
7+
const applicantRecordResolvers = {
8+
Query: {
9+
getApplicantRecords: async (
10+
_parent: undefined,
11+
{ positions }: { positions: PositionTitle[] },
12+
): Promise<ApplicantRecord[]> => {
13+
return applicantRecordService.getApplicantRecords(positions);
14+
},
15+
getApplicantRecordById: (
16+
_parent: undefined,
17+
{ id }: { id: string },
18+
): Promise<ApplicantRecord> => {
19+
return applicantRecordService.getApplicantRecordById(id);
20+
},
21+
},
22+
};
23+
24+
export default applicantRecordResolvers;
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { gql } from "apollo-server-express";
2+
3+
const applicantRecordType = gql`
4+
enum ApplicationStatus {
5+
Applied
6+
InReview
7+
Reviewed
8+
Interview
9+
InterviewComplete
10+
Offer
11+
NotConsidered
12+
}
13+
14+
enum SkillCategory {
15+
Junior
16+
Intermediate
17+
Senior
18+
}
19+
20+
type ApplicantDTO {
21+
id: ID!
22+
firstName: String!
23+
lastName: String!
24+
email: String!
25+
academicOrCoop: String!
26+
academicYear: String!
27+
heardFrom: String!
28+
locationPreference: String!
29+
program: String!
30+
pronouns: String!
31+
resumeUrl: String!
32+
timesApplied: Int!
33+
shortAnswerQuestions: [String!]!
34+
term: String!
35+
submittedAt: String!
36+
}
37+
38+
type UserDTO {
39+
id: ID!
40+
firstName: String!
41+
lastName: String!
42+
email: String!
43+
position: String
44+
role: Role!
45+
}
46+
47+
type ReviewedApplicantRecordDTO {
48+
applicantRecordId: ID!
49+
reviewer: UserDTO!
50+
review: JSON
51+
status: ReviewStatus!
52+
}
53+
54+
type ApplicantRecordDTO {
55+
id: ID!
56+
applicant: ApplicantDTO!
57+
position: String!
58+
roleSpecificQuestions: [String!]!
59+
choice: Int!
60+
status: ApplicationStatus!
61+
skillCategory: SkillCategory
62+
extraInfo: JSON
63+
reviews: [ReviewedApplicantRecordDTO!]!
64+
}
65+
66+
extend type Query {
67+
getApplicantRecords(positions: [String!]!): [ApplicantRecordDTO!]!
68+
getApplicantRecordById(id: ID!): ApplicantRecordDTO!
69+
}
70+
`;
71+
72+
export default applicantRecordType;

backend/typescript/models/applicantRecord.model.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
/* eslint import/no-cycle: 0 */
22

33
import {
4+
BelongsTo,
45
Column,
56
DataType,
67
ForeignKey,
8+
HasMany,
79
Model,
810
Table,
911
} from "sequelize-typescript";
@@ -14,14 +16,14 @@ import {
1416
} from "../types";
1517
import Applicant from "./applicant.model";
1618
import Position from "./position.model";
19+
import ReviewedApplicantRecord from "./reviewedApplicantRecord.model";
1720

1821
@Table({ tableName: "applicant_records" })
1922
export default class ApplicantRecord extends Model {
2023
@Column({
21-
type: DataType.INTEGER,
24+
type: DataType.STRING,
2225
primaryKey: true,
2326
unique: true,
24-
autoIncrement: true,
2527
})
2628
id!: string;
2729

@@ -47,4 +49,10 @@ export default class ApplicantRecord extends Model {
4749

4850
@Column({ type: DataType.JSONB, allowNull: true })
4951
extraInfo!: ApplicantRecordExtraInfo;
52+
53+
@HasMany(() => ReviewedApplicantRecord, "applicantRecordId")
54+
reviews!: ReviewedApplicantRecord[];
55+
56+
@BelongsTo(() => Applicant, "applicantId")
57+
applicant!: Applicant;
5058
}

backend/typescript/models/reviewedApplicantRecord.model.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/* eslint import/no-cycle: 0 */
22

33
import {
4+
BelongsTo,
45
Column,
56
DataType,
67
ForeignKey,
@@ -29,4 +30,10 @@ export default class ReviewedApplicantRecord extends Model {
2930
defaultValue: ReviewStatusEnum.TODO,
3031
})
3132
status!: ReviewStatus;
33+
34+
@BelongsTo(() => ApplicantRecord, "applicantRecordId")
35+
applicantRecord!: ApplicantRecord;
36+
37+
@BelongsTo(() => User, "reviewerId")
38+
reviewer!: User;
3239
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { Op } from "sequelize";
2+
import { PositionTitle } from "../../types";
3+
import IApplicantRecordService from "../interfaces/applicantRecordService";
4+
import logger from "../../utilities/logger";
5+
import ApplicantRecord from "../../models/applicantRecord.model";
6+
import { getErrorMessage } from "../../utilities/errorUtils";
7+
import Applicant from "../../models/applicant.model";
8+
import ReviewedApplicantRecord from "../../models/reviewedApplicantRecord.model";
9+
import User from "../../models/user.model";
10+
11+
const Logger = logger(__filename);
12+
13+
class ApplicantRecordService implements IApplicantRecordService {
14+
/* eslint-disable class-methods-use-this */
15+
16+
async getApplicantRecords(
17+
positions: PositionTitle[],
18+
): Promise<ApplicantRecord[]> {
19+
try {
20+
return await ApplicantRecord.findAll({
21+
where: {
22+
position: { [Op.in]: positions },
23+
},
24+
include: [
25+
{
26+
model: Applicant,
27+
as: "applicant",
28+
},
29+
{
30+
model: ReviewedApplicantRecord,
31+
as: "reviews",
32+
include: [
33+
{
34+
model: User,
35+
as: "reviewer",
36+
},
37+
],
38+
},
39+
],
40+
});
41+
} catch (error: unknown) {
42+
Logger.error(
43+
`Failed to get applicant records. Reason = ${getErrorMessage(error)}`,
44+
);
45+
throw error;
46+
}
47+
}
48+
49+
async getApplicantRecordById(id: string): Promise<ApplicantRecord> {
50+
try {
51+
const applicantRecord = await ApplicantRecord.findByPk(id, {
52+
include: [
53+
{
54+
model: Applicant,
55+
as: "applicant",
56+
},
57+
],
58+
});
59+
if (!applicantRecord) {
60+
throw new Error(`Applicant record with id ${id} not found.`);
61+
}
62+
return applicantRecord;
63+
} catch (error: unknown) {
64+
Logger.error(
65+
`Failed to get applicant record. Reason = ${getErrorMessage(error)}`,
66+
);
67+
throw error;
68+
}
69+
}
70+
}
71+
72+
export default ApplicantRecordService;
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import ApplicantRecord from "../../models/applicantRecord.model";
2+
import { PositionTitle } from "../../types";
3+
4+
interface IApplicantRecordService {
5+
/**
6+
* Retrieve full applicant records by positions.
7+
*/
8+
getApplicantRecords(positions: PositionTitle[]): Promise<ApplicantRecord[]>;
9+
10+
/**
11+
* Retrieve a full applicant record by id.
12+
*/
13+
getApplicantRecordById(id: string): Promise<ApplicantRecord>;
14+
}
15+
16+
export default IApplicantRecordService;

backend/typescript/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,9 @@ export type ApplicantDTO = {
9797
};
9898

9999
export type ApplicantRecordDTO = {
100-
id: number;
100+
id: string;
101101
applicantId: string;
102-
position: PositionTitle; // EDIT LATER
102+
position: PositionTitle;
103103
roleSpecificQuestions: string[];
104104
choice: number;
105105
status: ApplicationStatus;

0 commit comments

Comments
 (0)