Skip to content

Commit 20804ff

Browse files
committed
Merge branch 'main' of https://github.com/uwblueprint/website-bp-be into INTF25-mutator-update-reviewed-applicant-record
2 parents 138f52d + b1b6fa8 commit 20804ff

16 files changed

+409
-15
lines changed

backend/typescript/graphql/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import reviewedApplicantRecordTypes from "./types/reviewedApplicantRecordTypes";
2121
import reviewedApplicantRecordResolvers from "./resolvers/reviewedApplicantRecordResolver";
2222
import adminCommentResolvers from "./resolvers/adminCommentsResolvers";
2323
import adminCommentType from "./types/adminCommentsType";
24+
import applicantRecordResolvers from "./resolvers/applicantRecordResolvers";
25+
import applicantRecordType from "./types/applicantRecordType";
2426
import reviewPageType from "./types/reviewPageType";
2527
import reviewPageResolvers from "./resolvers/reviewPageResolvers";
2628

@@ -47,6 +49,7 @@ const executableSchema = makeExecutableSchema({
4749
reviewDashboardType,
4850
reviewedApplicantRecordTypes,
4951
adminCommentType,
52+
applicantRecordType,
5053
reviewPageType,
5154
],
5255
resolvers: merge(
@@ -57,6 +60,7 @@ const executableSchema = makeExecutableSchema({
5760
reviewDashboardResolvers,
5861
reviewedApplicantRecordResolvers,
5962
adminCommentResolvers,
63+
applicantRecordResolvers,
6064
reviewPageResolvers,
6165
),
6266
});
@@ -75,6 +79,8 @@ const graphQLMiddlewares = {
7579
userByEmail: authorizedByAdmin(),
7680
login: authorizedByAdmin(),
7781
users: authorizedByAdmin(),
82+
adminCommentsByApplicantRecordId: authorizedByAdmin(),
83+
adminCommentById: authorizedByAdmin(),
7884
},
7985
Mutation: {
8086
createEntity: authorizedByAllRoles(),

backend/typescript/graphql/resolvers/adminCommentsResolvers.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,25 @@ import { CreateAdminCommentDTO, AdminCommentDTO } from "../../types";
55
const adminCommentService: IAdminCommentService = new AdminCommentService();
66

77
const adminCommentResolvers = {
8+
Query: {
9+
adminCommentsByApplicantRecordId: async (
10+
_parent: undefined,
11+
{ applicantRecordId }: { applicantRecordId: string },
12+
): Promise<AdminCommentDTO[]> => {
13+
const adminComments =
14+
await adminCommentService.getAdminCommentsByApplicantRecordId(
15+
applicantRecordId,
16+
);
17+
return adminComments;
18+
},
19+
adminCommentById: async (
20+
_parent: undefined,
21+
{ id }: { id: string },
22+
): Promise<AdminCommentDTO> => {
23+
const adminComment = await adminCommentService.getAdminCommentById(id);
24+
return adminComment;
25+
},
26+
},
827
Mutation: {
928
createAdminComment: async (
1029
_parent: undefined,
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import ApplicantRecordService from "../../services/implementations/applicantRecordService";
2+
import IApplicantRecordService from "../../services/interfaces/applicantRecordService";
3+
import { ApplicantRecordDTO } from "../../types";
4+
import { getErrorMessage } from "../../utilities/errorUtils";
5+
6+
const applicantRecordService: IApplicantRecordService =
7+
new ApplicantRecordService();
8+
9+
const applicantRecordResolvers = {
10+
Mutation: {
11+
setApplicantRecordFlag: async (
12+
_parent: undefined,
13+
{
14+
applicantRecordId,
15+
flagValue,
16+
}: { applicantRecordId: string; flagValue: boolean },
17+
): Promise<ApplicantRecordDTO> => {
18+
try {
19+
const applicantRecord =
20+
await applicantRecordService.setApplicantRecordFlag(
21+
applicantRecordId,
22+
flagValue,
23+
);
24+
return applicantRecord;
25+
} catch (error: unknown) {
26+
throw new Error(getErrorMessage(error));
27+
}
28+
},
29+
},
30+
};
31+
32+
export default applicantRecordResolvers;

backend/typescript/graphql/types/adminCommentsType.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@ const adminCommentsType = gql`
1616
comment: String!
1717
}
1818
19+
extend type Query {
20+
adminCommentsByApplicantRecordId(
21+
applicantRecordId: String!
22+
): [AdminCommentDTO!]!
23+
adminCommentById(id: String!): AdminCommentDTO!
24+
}
25+
1926
extend type Mutation {
2027
createAdminComment(adminComment: CreateAdminCommentDTO!): AdminCommentDTO!
2128
updateAdminComment(
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { gql } from "apollo-server-express";
2+
3+
const applicantRecordType = gql`
4+
type ApplicantRecordDTO {
5+
id: String!
6+
applicantId: String!
7+
position: String!
8+
roleSpecificQuestions: [String!]!
9+
choice: Int!
10+
status: String!
11+
skillCategory: String
12+
combined_score: Int
13+
isApplicantFlagged: Boolean!
14+
}
15+
16+
extend type Mutation {
17+
setApplicantRecordFlag(
18+
applicantRecordId: String!
19+
flagValue: Boolean!
20+
): ApplicantRecordDTO!
21+
}
22+
`;
23+
24+
export default applicantRecordType;

backend/typescript/graphql/types/reviewDashboardType.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { gql } from "apollo-server-express";
2-
import { ApplicationStatus, PositionTitle, ReviewerDTO } from "../../types";
32

43
const reviewDashboardType = gql`
54
type ReviewerDTO {
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
/* eslint-disable @typescript-eslint/naming-convention */
2+
import { DataType } from "sequelize-typescript";
3+
import { Migration } from "../umzug";
4+
5+
const USER_TABLE = "users";
6+
const APPLICANT_RECORDS_TABLE = "applicant_records";
7+
8+
const POSITION_TABLE = "positions";
9+
10+
const POSITION_DATA = [
11+
{ title: "Project Lead", department: "Engineering" },
12+
{ title: "Developer", department: "Engineering" },
13+
{ title: "VP Engineering", department: "Engineering" },
14+
{ title: "Designer", department: "Design" },
15+
{ title: "VP Design", department: "Design" },
16+
{ title: "Product Manager", department: "Product" },
17+
{ title: "VP Product", department: "Product" },
18+
{ title: "President", department: "Community" },
19+
{ title: "VP Scoping", department: "Community" },
20+
{ title: "VP Talent", department: "Community" },
21+
{ title: "VP Finance", department: "Community" },
22+
{ title: "Director Lead", department: "Community" },
23+
{ title: "Internal Director", department: "Community" },
24+
{ title: "External Director", department: "Community" },
25+
{ title: "Content Strategist", department: "Community" },
26+
{ title: "Graphic Designer", department: "Community" },
27+
];
28+
29+
const POSITION_TITLES = [
30+
"Project Lead",
31+
"Developer",
32+
"VP Engineering",
33+
"Designer",
34+
"VP Design",
35+
"Product Manager",
36+
"VP Product",
37+
"President",
38+
"VP Scoping",
39+
"VP Talent",
40+
"VP Finance",
41+
"Director Lead",
42+
"Internal Director",
43+
"External Director",
44+
"Content Strategist",
45+
"Graphic Designer",
46+
];
47+
48+
const DEPARTMENT_TITLES = ["Engineering", "Design", "Product", "Community"];
49+
50+
const position = "position";
51+
const enum_positions_title = "enum_positions_title";
52+
const enum_applicant_records_position = "enum_applicant_records_position";
53+
const enum_positions_department = "enum_positions_department";
54+
55+
export const up: Migration = async ({ context: sequelize }) => {
56+
// Drop FK constraints referencing positions
57+
await sequelize.getQueryInterface().sequelize.query(`
58+
ALTER TABLE "${USER_TABLE}" DROP CONSTRAINT IF EXISTS "${USER_TABLE}_${position}_fkey";
59+
ALTER TABLE "${APPLICANT_RECORDS_TABLE}" DROP CONSTRAINT IF EXISTS "${APPLICANT_RECORDS_TABLE}_${position}_fkey";
60+
`);
61+
62+
// Drop the enum-based positions table
63+
await sequelize.getQueryInterface().dropTable(POSITION_TABLE);
64+
65+
// Recreate positions table with string columns
66+
await sequelize.getQueryInterface().createTable(POSITION_TABLE, {
67+
title: {
68+
type: DataType.STRING,
69+
allowNull: false,
70+
primaryKey: true,
71+
},
72+
department: {
73+
type: DataType.STRING,
74+
allowNull: false,
75+
},
76+
});
77+
78+
// Re seed positions data
79+
await sequelize.getQueryInterface().bulkInsert(POSITION_TABLE, POSITION_DATA);
80+
81+
// Convert enum columns in users and applicant_records to type string
82+
await sequelize.getQueryInterface().sequelize.query(`
83+
ALTER TABLE "${USER_TABLE}"
84+
ALTER COLUMN "${position}" TYPE VARCHAR(255)
85+
USING ("${position}"::text);
86+
`);
87+
await sequelize.getQueryInterface().sequelize.query(`
88+
ALTER TABLE "${APPLICANT_RECORDS_TABLE}"
89+
ALTER COLUMN "${position}" TYPE VARCHAR(255)
90+
USING ("${position}"::text);
91+
`);
92+
93+
// Re add FKs referencing string positions.title
94+
await sequelize.getQueryInterface().sequelize.query(`
95+
ALTER TABLE "${USER_TABLE}"
96+
ADD CONSTRAINT "${USER_TABLE}_${position}_fkey"
97+
FOREIGN KEY ("${position}") REFERENCES "${POSITION_TABLE}" ("title")
98+
ON DELETE SET NULL ON UPDATE CASCADE;
99+
`);
100+
await sequelize.getQueryInterface().sequelize.query(`
101+
ALTER TABLE "${APPLICANT_RECORDS_TABLE}"
102+
ADD CONSTRAINT "${APPLICANT_RECORDS_TABLE}_${position}_fkey"
103+
FOREIGN KEY ("${position}") REFERENCES "${POSITION_TABLE}" ("title")
104+
ON DELETE SET NULL ON UPDATE CASCADE;
105+
`);
106+
107+
// Drop old enum types
108+
await sequelize
109+
.getQueryInterface()
110+
.sequelize.query(`DROP TYPE IF EXISTS "${enum_positions_title}" CASCADE;`);
111+
await sequelize
112+
.getQueryInterface()
113+
.sequelize.query(
114+
`DROP TYPE IF EXISTS "${enum_positions_department}}" CASCADE;`,
115+
);
116+
await sequelize
117+
.getQueryInterface()
118+
.sequelize.query(
119+
`DROP TYPE IF EXISTS "${enum_applicant_records_position}" CASCADE;`,
120+
);
121+
};
122+
123+
export const down: Migration = async ({ context: sequelize }) => {
124+
// Drop foreign key constraints referencing positions table
125+
await sequelize.getQueryInterface().sequelize.query(`
126+
ALTER TABLE "${USER_TABLE}" DROP CONSTRAINT IF EXISTS "${USER_TABLE}_${position}_fkey";
127+
ALTER TABLE "${APPLICANT_RECORDS_TABLE}" DROP CONSTRAINT IF EXISTS "${APPLICANT_RECORDS_TABLE}_${position}_fkey";
128+
`);
129+
130+
// Drop the string based positions table
131+
await sequelize.getQueryInterface().dropTable(POSITION_TABLE);
132+
133+
// Recreate table with enums
134+
await sequelize.getQueryInterface().createTable(POSITION_TABLE, {
135+
title: {
136+
type: DataType.ENUM(...POSITION_TITLES),
137+
allowNull: false,
138+
primaryKey: true,
139+
},
140+
department: {
141+
type: DataType.ENUM(...DEPARTMENT_TITLES),
142+
allowNull: false,
143+
},
144+
});
145+
146+
// Seed ENUM based positions table
147+
await sequelize.getQueryInterface().bulkInsert(POSITION_TABLE, POSITION_DATA);
148+
149+
// change column type from string to enum
150+
await sequelize.getQueryInterface().sequelize.query(`
151+
ALTER TABLE "users"
152+
ALTER COLUMN "position" TYPE "${enum_positions_title}"
153+
USING ("position"::text::"${enum_positions_title}");
154+
`);
155+
156+
// change column type from string to enum
157+
await sequelize.getQueryInterface().sequelize.query(`
158+
ALTER TABLE "applicant_records"
159+
ALTER COLUMN "position" TYPE "${enum_positions_title}"
160+
USING ("position"::text::"${enum_positions_title}");
161+
`);
162+
163+
// add back foreign keys
164+
await sequelize.getQueryInterface().sequelize.query(`
165+
ALTER TABLE "${USER_TABLE}"
166+
ADD CONSTRAINT "${USER_TABLE}_${position}_fkey"
167+
FOREIGN KEY ("${position}") REFERENCES "${POSITION_TABLE}" ("title")
168+
ON DELETE SET NULL ON UPDATE CASCADE;
169+
`);
170+
await sequelize.getQueryInterface().sequelize.query(`
171+
ALTER TABLE "${APPLICANT_RECORDS_TABLE}"
172+
ADD CONSTRAINT "${APPLICANT_RECORDS_TABLE}_${position}_fkey"
173+
FOREIGN KEY ("${position}") REFERENCES "${POSITION_TABLE}" ("title")
174+
ON DELETE SET NULL ON UPDATE CASCADE;
175+
`);
176+
};
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { DataType } from "sequelize-typescript";
2+
import { Migration } from "../umzug";
3+
4+
const TABLE_NAME = "applicant_records";
5+
6+
export const up: Migration = async ({ context: sequelize }) => {
7+
await sequelize.getQueryInterface().addColumn(TABLE_NAME, "createdAt", {
8+
type: DataType.DATE,
9+
allowNull: false,
10+
defaultValue: new Date(),
11+
});
12+
await sequelize.getQueryInterface().addColumn(TABLE_NAME, "updatedAt", {
13+
type: DataType.DATE,
14+
allowNull: false,
15+
defaultValue: new Date(),
16+
});
17+
};
18+
19+
export const down: Migration = async ({ context: sequelize }) => {
20+
await sequelize.getQueryInterface().removeColumn(TABLE_NAME, "createdAt");
21+
await sequelize.getQueryInterface().removeColumn(TABLE_NAME, "updatedAt");
22+
};
Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import { Column, DataType, Model, Table } from "sequelize-typescript";
2-
import { Department, PositionTitle, PositionTitles } from "../types";
32

43
@Table({ tableName: "positions" })
54
export default class Position extends Model {
6-
@Column({ type: DataType.ENUM(...PositionTitles), primaryKey: true })
7-
title!: PositionTitle;
5+
@Column({ type: DataType.STRING, primaryKey: true })
6+
title!: string;
87

9-
@Column({ type: DataType.ENUM(...Object.values(Department)) })
10-
department!: Department;
8+
@Column({ type: DataType.STRING })
9+
department!: string;
1110
}

backend/typescript/models/user.model.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
Table,
1010
} from "sequelize-typescript";
1111
import { NonAttribute } from "sequelize";
12-
import { PositionTitle, PositionTitles, Role } from "../types";
12+
import { Role } from "../types";
1313
import Position from "./position.model";
1414
import ReviewedApplicantRecord from "./reviewedApplicantRecord.model";
1515

@@ -34,8 +34,8 @@ export default class User extends Model {
3434
role!: Role;
3535

3636
@ForeignKey(() => Position)
37-
@Column({ type: DataType.ENUM(...Object.values(PositionTitles)) })
38-
position?: PositionTitle;
37+
@Column({ type: DataType.STRING })
38+
position?: string;
3939

4040
@HasMany(() => ReviewedApplicantRecord, {
4141
foreignKey: "reviewerId",

0 commit comments

Comments
 (0)