Skip to content

Commit 5941375

Browse files
author
ruiichen
committed
- first changes
1 parent 7867d17 commit 5941375

File tree

9 files changed

+161
-7
lines changed

9 files changed

+161
-7
lines changed

backend/typescript/graphql/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import userType from "./types/userType";
1818
import dashboardType from "./types/dashboardType";
1919
import dashboardResolvers from "./resolvers/dashboardResolvers";
2020
import reviewType from "./types/reviewType";
21+
import reviewPageType from "./types/reviewPageType";
22+
import reviewPageResolvers from "./resolvers/reviewPageResolvers";
2123

2224
const query = gql`
2325
type Query {
@@ -41,13 +43,15 @@ const executableSchema = makeExecutableSchema({
4143
simpleEntityType,
4244
userType,
4345
dashboardType,
46+
reviewPageType,
4447
],
4548
resolvers: merge(
4649
authResolvers,
4750
entityResolvers,
4851
simpleEntityResolvers,
4952
userResolvers,
5053
dashboardResolvers,
54+
reviewPageResolvers,
5155
),
5256
});
5357

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { ApplicationDTO } from "../../types";
2+
import ReviewPageService from "../../services/implementations/reviewPageService";
3+
import { getErrorMessage } from "../../utilities/errorUtils";
4+
5+
const reviewPageService = new ReviewPageService();
6+
7+
const reviewPageResolvers = {
8+
Query: {
9+
reviewApplicantPage: async (
10+
_parent: undefined,
11+
args: { reviewedApplicantRecordId: number },
12+
): Promise<ApplicationDTO[]> => {
13+
try {
14+
return await reviewPageService.getReviewPage(
15+
args.reviewedApplicantRecordId,
16+
);
17+
} catch (error) {
18+
throw new Error(getErrorMessage(error));
19+
}
20+
},
21+
},
22+
};
23+
24+
export default reviewPageResolvers;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { gql } from "apollo-server-express";
2+
3+
const reviewPageType = gql`
4+
extend type Query {
5+
reviewApplicantPage(reviewedApplicantRecordId: Int!): [ApplicationDTO!]!
6+
}
7+
`;
8+
9+
export default reviewPageType;

backend/typescript/models/applicant.model.ts

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

3-
import { Column, DataType, Model, Table } from "sequelize-typescript";
3+
import { Column, DataType, HasMany, Model, Table } from "sequelize-typescript";
4+
import { NonAttribute } from "sequelize";
5+
import ApplicantRecord from "./applicantRecord.model";
46

57
@Table({ tableName: "applicants" })
68
export default class Applicant extends Model {
@@ -10,7 +12,7 @@ export default class Applicant extends Model {
1012
unique: true,
1113
autoIncrement: true,
1214
})
13-
id!: string;
15+
id!: number;
1416

1517
@Column({ type: DataType.STRING })
1618
academicOrCoop!: string;
@@ -53,4 +55,10 @@ export default class Applicant extends Model {
5355

5456
@Column({ type: DataType.DATE })
5557
submittedAt!: Date;
58+
59+
@HasMany(() => ApplicantRecord, {
60+
foreignKey: "id",
61+
as: "applicant",
62+
})
63+
applicantRecords?: NonAttribute<ApplicantRecord[]>;
5664
}

backend/typescript/models/applicantRecord.model.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,37 @@
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";
12+
import { NonAttribute } from "sequelize";
1013
import {
1114
ApplicantRecordExtraInfo,
1215
ApplicationStatus,
1316
SkillCategory,
1417
} from "../types";
1518
import Applicant from "./applicant.model";
1619
import Position from "./position.model";
20+
import ReviewedApplicantRecord from "./reviewedApplicantRecord.model";
1721

1822
@Table({ tableName: "applicant_records" })
1923
export default class ApplicantRecord extends Model {
2024
@Column({
21-
type: DataType.INTEGER,
25+
type: DataType.STRING,
2226
primaryKey: true,
2327
unique: true,
2428
autoIncrement: true,
2529
})
2630
id!: string;
2731

2832
@ForeignKey(() => Applicant)
29-
@Column({ type: DataType.STRING })
30-
applicantId!: string;
33+
@Column({ type: DataType.INTEGER })
34+
applicantId!: number;
3135

3236
@ForeignKey(() => Position)
3337
@Column({ type: DataType.STRING })
@@ -57,4 +61,10 @@ export default class ApplicantRecord extends Model {
5761

5862
@Column({ type: DataType.BOOLEAN, defaultValue: false })
5963
isApplicantFlagged!: boolean;
64+
65+
@BelongsTo(() => Applicant, "applicantId")
66+
applicant?: NonAttribute<Applicant>;
67+
68+
@HasMany(() => ReviewedApplicantRecord, "applicantRecordId")
69+
reviewedApplicantRecords?: NonAttribute<ReviewedApplicantRecord[]>;
6070
}

backend/typescript/models/reviewedApplicantRecord.model.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import User from "./user.model";
1515
export default class ReviewedApplicantRecord extends Model {
1616
@ForeignKey(() => ApplicantRecord)
1717
@Column({ type: DataType.STRING, primaryKey: true })
18-
applicantRecordId!: number;
18+
applicantRecordId!: string;
1919

2020
@ForeignKey(() => User)
2121
@Column({ type: DataType.INTEGER, primaryKey: true })
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { ApplicationDTO } from "../../types";
2+
import IReviewPageService from "../interfaces/IReviewPageService";
3+
import { getErrorMessage } from "../../utilities/errorUtils";
4+
import logger from "../../utilities/logger";
5+
import ApplicantRecord from "../../models/applicantRecord.model";
6+
import ReviewedApplicantRecord from "../../models/reviewedApplicantRecord.model";
7+
import Applicant from "../../models/applicant.model";
8+
9+
const Logger = logger(__filename);
10+
11+
function toDTO(model: Applicant): ApplicationDTO {
12+
const firstChoice = model.applicantRecords!.find((ar) => ar.choice === 1);
13+
const secondChoice = model.applicantRecords!.find((ar) => ar.choice === 2);
14+
15+
return {
16+
id: model.id,
17+
academicOrCoop: model.academicOrCoop,
18+
academicYear: model.academicYear,
19+
email: model.email,
20+
firstChoiceRole: firstChoice!.position,
21+
firstName: model.firstName,
22+
lastName: model.lastName,
23+
heardFrom: model.heardFrom,
24+
locationPreference: model.locationPreference,
25+
program: model.program,
26+
timesApplied: model.timesApplied.toString(),
27+
pronouns: model.pronouns,
28+
pronounsSpecified: "",
29+
resumeUrl: model.resumeUrl,
30+
roleSpecificQuestions: firstChoice!.roleSpecificQuestions,
31+
secondChoiceRole: secondChoice ? secondChoice.position : "",
32+
shortAnswerQuestions: model.shortAnswerQuestions,
33+
status: firstChoice!.status,
34+
term: model.term,
35+
secondChoiceStatus: secondChoice ? secondChoice.status : "",
36+
timestamp: BigInt(1728673405),
37+
};
38+
}
39+
40+
class ReviewPageService implements IReviewPageService {
41+
/* eslint-disable class-methods-use-this */
42+
async getReviewPage(
43+
reviewedApplicantRecordId: number,
44+
): Promise<ApplicationDTO[]> {
45+
try {
46+
// get applicant ids first
47+
const rARs: Array<ReviewedApplicantRecord> =
48+
await ReviewedApplicantRecord.findAll({
49+
where: { applicantRecordId: reviewedApplicantRecordId },
50+
attributes: { exclude: ["createdAt", "updatedAt"] },
51+
});
52+
if (rARs.length === 0)
53+
throw new Error(
54+
`ReviewedApplicantRecords with ID ${reviewedApplicantRecordId} not found.`,
55+
);
56+
57+
const aRIds: Array<number> = [
58+
...new Set(rARs.map((r) => r.applicantRecordId)),
59+
];
60+
const aRs: Array<ApplicantRecord> = await ApplicantRecord.findAll({
61+
where: { id: aRIds },
62+
attributes: { exclude: ["createdAt", "updatedAt"] },
63+
});
64+
if (aRs.length === 0)
65+
throw new Error(`Database integrity has been violated`);
66+
67+
const aIds: Array<number> = [...new Set(aRs.map((a) => a.applicantId))];
68+
const as: Array<Applicant> = await Applicant.findAll({
69+
where: { id: aIds },
70+
attributes: { exclude: ["createdAt", "updatedAt"] },
71+
include: [
72+
{
73+
attributes: { exclude: ["createdAt", "updatedAt"] },
74+
association: "applicantRecords",
75+
},
76+
],
77+
});
78+
if (as.length === 0)
79+
throw new Error(`Database integrity has been violated`);
80+
81+
return as.map(toDTO);
82+
} catch (error: unknown) {
83+
Logger.error(`Failed to fetch. Reason = ${getErrorMessage(error)}`);
84+
throw error;
85+
}
86+
}
87+
}
88+
89+
export default ReviewPageService;
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { ApplicationDTO } from "../../types";
2+
3+
interface IReviewPageService {
4+
/**
5+
* Fetch data from the Applicant and ApplicantRecord table to return an ApplicationDTO object
6+
* @Param reviewedApplicantRecordId the id of the applicant record that the viewer is interested in
7+
*/
8+
getReviewPage(reviewedApplicantRecordId: number): Promise<ApplicationDTO[]>;
9+
}
10+
11+
export default IReviewPageService;

backend/typescript/types.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ export type ApplicationDashboardInput = Omit<
5252
"applicationId"
5353
>;
5454

55-
// DEPRECATED - TO BE REMOVED AT THE END OF S25
5655
export type ApplicationDTO = {
5756
id: number;
5857
academicOrCoop: string;

0 commit comments

Comments
 (0)