Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion backend/typescript/graphql/types/reviewDashboardType.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { gql } from "apollo-server-express";
import { ApplicationStatus, PositionTitle, ReviewerDTO } from "../../types";

const reviewDashboardType = gql`
type ReviewerDTO {
Expand Down
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I had to jump through a lot of hoops to get this migration working... because FK restraints in the database and enum restraints in the database prevented some of the changes. But, the general logic for the migration is as follows:

Drop foreign keys on the columns with the position enums we are trying to remove.
Delete the positions table and recreated it with columns of type string instead of enums.
Convert columns previously of type position enums to string enums.
Re-add the foreign keys to those columns.
At this point, no columns are using the position enums, drop enums.

For the down migration, the same logic applies but it changes the columns from type string back to type position enums.

Copy link
Member

Choose a reason for hiding this comment

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

Foreign keys do be a pain in the ass...

Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { DataType } from "sequelize-typescript";
import { Migration } from "../umzug";

const USER_TABLE = "users";
const APPLICANT_RECORDS_TABLE = "applicant_records";

const POSITION_TABLE = "positions";

const POSITION_DATA = [
{ title: "Project Lead", department: "Engineering" },
{ title: "Developer", department: "Engineering" },
{ title: "VP Engineering", department: "Engineering" },
{ title: "Designer", department: "Design" },
{ title: "VP Design", department: "Design" },
{ title: "Product Manager", department: "Product" },
{ title: "VP Product", department: "Product" },
{ title: "President", department: "Community" },
{ title: "VP Scoping", department: "Community" },
{ title: "VP Talent", department: "Community" },
{ title: "VP Finance", department: "Community" },
{ title: "Director Lead", department: "Community" },
{ title: "Internal Director", department: "Community" },
{ title: "External Director", department: "Community" },
{ title: "Content Strategist", department: "Community" },
{ title: "Graphic Designer", department: "Community" },
];

const POSITION_TITLES = [
"Project Lead",
"Developer",
"VP Engineering",
"Designer",
"VP Design",
"Product Manager",
"VP Product",
"President",
"VP Scoping",
"VP Talent",
"VP Finance",
"Director Lead",
"Internal Director",
"External Director",
"Content Strategist",
"Graphic Designer",
];

const DEPARTMENT_TITLES = ["Engineering", "Design", "Product", "Community"];

const position = "position";
const enum_positions_title = "enum_positions_title";
const enum_applicant_records_position = "enum_applicant_records_position";
const enum_positions_department = "enum_positions_department";

export const up: Migration = async ({ context: sequelize }) => {
// Drop FK constraints referencing positions
await sequelize.getQueryInterface().sequelize.query(`
ALTER TABLE "${USER_TABLE}" DROP CONSTRAINT IF EXISTS "${USER_TABLE}_${position}_fkey";
ALTER TABLE "${APPLICANT_RECORDS_TABLE}" DROP CONSTRAINT IF EXISTS "${APPLICANT_RECORDS_TABLE}_${position}_fkey";
`);

// Drop the enum-based positions table
await sequelize.getQueryInterface().dropTable(POSITION_TABLE);

// Recreate positions table with string columns
await sequelize.getQueryInterface().createTable(POSITION_TABLE, {
title: {
type: DataType.STRING,
allowNull: false,
primaryKey: true,
},
department: {
type: DataType.STRING,
allowNull: false,
},
});

// Re seed positions data
await sequelize.getQueryInterface().bulkInsert(POSITION_TABLE, POSITION_DATA);

// Convert enum columns in users and applicant_records to type string
await sequelize.getQueryInterface().sequelize.query(`
ALTER TABLE "${USER_TABLE}"
ALTER COLUMN "${position}" TYPE VARCHAR(255)
USING ("${position}"::text);
`);
await sequelize.getQueryInterface().sequelize.query(`
ALTER TABLE "${APPLICANT_RECORDS_TABLE}"
ALTER COLUMN "${position}" TYPE VARCHAR(255)
USING ("${position}"::text);
`);

// Re add FKs referencing string positions.title
await sequelize.getQueryInterface().sequelize.query(`
ALTER TABLE "${USER_TABLE}"
ADD CONSTRAINT "${USER_TABLE}_${position}_fkey"
FOREIGN KEY ("${position}") REFERENCES "${POSITION_TABLE}" ("title")
ON DELETE SET NULL ON UPDATE CASCADE;
`);
await sequelize.getQueryInterface().sequelize.query(`
ALTER TABLE "${APPLICANT_RECORDS_TABLE}"
ADD CONSTRAINT "${APPLICANT_RECORDS_TABLE}_${position}_fkey"
FOREIGN KEY ("${position}") REFERENCES "${POSITION_TABLE}" ("title")
ON DELETE SET NULL ON UPDATE CASCADE;
`);

// Drop old enum types
await sequelize
.getQueryInterface()
.sequelize.query(`DROP TYPE IF EXISTS "${enum_positions_title}" CASCADE;`);
await sequelize
.getQueryInterface()
.sequelize.query(
`DROP TYPE IF EXISTS "${enum_positions_department}}" CASCADE;`,
);
await sequelize
.getQueryInterface()
.sequelize.query(
`DROP TYPE IF EXISTS "${enum_applicant_records_position}" CASCADE;`,
);
};

export const down: Migration = async ({ context: sequelize }) => {
// Drop foreign key constraints referencing positions table
await sequelize.getQueryInterface().sequelize.query(`
ALTER TABLE "${USER_TABLE}" DROP CONSTRAINT IF EXISTS "${USER_TABLE}_${position}_fkey";
ALTER TABLE "${APPLICANT_RECORDS_TABLE}" DROP CONSTRAINT IF EXISTS "${APPLICANT_RECORDS_TABLE}_${position}_fkey";
`);

// Drop the string based positions table
await sequelize.getQueryInterface().dropTable(POSITION_TABLE);

// Recreate table with enums
await sequelize.getQueryInterface().createTable(POSITION_TABLE, {
title: {
type: DataType.ENUM(...POSITION_TITLES),
allowNull: false,
primaryKey: true,
},
department: {
type: DataType.ENUM(...DEPARTMENT_TITLES),
allowNull: false,
},
});

// Seed ENUM based positions table
await sequelize.getQueryInterface().bulkInsert(POSITION_TABLE, POSITION_DATA);

// change column type from string to enum
await sequelize.getQueryInterface().sequelize.query(`
ALTER TABLE "users"
ALTER COLUMN "position" TYPE "${enum_positions_title}"
USING ("position"::text::"${enum_positions_title}");
`);

// change column type from string to enum
await sequelize.getQueryInterface().sequelize.query(`
ALTER TABLE "applicant_records"
ALTER COLUMN "position" TYPE "${enum_positions_title}"
USING ("position"::text::"${enum_positions_title}");
`);

// add back foreign keys
await sequelize.getQueryInterface().sequelize.query(`
ALTER TABLE "${USER_TABLE}"
ADD CONSTRAINT "${USER_TABLE}_${position}_fkey"
FOREIGN KEY ("${position}") REFERENCES "${POSITION_TABLE}" ("title")
ON DELETE SET NULL ON UPDATE CASCADE;
`);
await sequelize.getQueryInterface().sequelize.query(`
ALTER TABLE "${APPLICANT_RECORDS_TABLE}"
ADD CONSTRAINT "${APPLICANT_RECORDS_TABLE}_${position}_fkey"
FOREIGN KEY ("${position}") REFERENCES "${POSITION_TABLE}" ("title")
ON DELETE SET NULL ON UPDATE CASCADE;
`);
};
9 changes: 4 additions & 5 deletions backend/typescript/models/position.model.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { Column, DataType, Model, Table } from "sequelize-typescript";
import { Department, PositionTitle, PositionTitles } from "../types";

@Table({ tableName: "positions" })
export default class Position extends Model {
@Column({ type: DataType.ENUM(...PositionTitles), primaryKey: true })
title!: PositionTitle;
@Column({ type: DataType.STRING, primaryKey: true })
title!: string;

@Column({ type: DataType.ENUM(...Object.values(Department)) })
department!: Department;
@Column({ type: DataType.STRING })
department!: string;
}
6 changes: 3 additions & 3 deletions backend/typescript/models/user.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
Table,
} from "sequelize-typescript";
import { NonAttribute } from "sequelize";
import { PositionTitle, PositionTitles, Role } from "../types";
import { Role } from "../types";
import Position from "./position.model";
import ReviewedApplicantRecord from "./reviewedApplicantRecord.model";

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

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

@HasMany(() => ReviewedApplicantRecord, {
foreignKey: "reviewerId",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { PositionTitle, ReviewDashboardRowDTO } from "../../types";
import { ReviewDashboardRowDTO } from "../../types";
import IReviewDashboardService from "../interfaces/IReviewDashboardService";
import { getErrorMessage } from "../../utilities/errorUtils";
import logger from "../../utilities/logger";
Expand All @@ -10,7 +10,7 @@ function toDTO(model: ApplicantRecord): ReviewDashboardRowDTO {
return {
firstName: model.applicant!.firstName,
lastName: model.applicant!.lastName,
position: model.position as PositionTitle,
position: model.position,
timesApplied: model.applicant!.timesApplied.toString(),
applicationStatus: model.status,
choice: model.choice,
Expand Down
6 changes: 3 additions & 3 deletions backend/typescript/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export type UserDTO = {
firstName: string;
lastName: string;
email: string;
position?: PositionTitle;
position?: string;
role: Role;
};

Expand Down Expand Up @@ -80,7 +80,7 @@ export type ApplicantDTO = {
export type ApplicantRecordDTO = {
id: number;
applicantId: string;
position: PositionTitle; // EDIT LATER
position: string;
roleSpecificQuestions: string[];
choice: number;
status: ApplicationStatus;
Expand Down Expand Up @@ -111,7 +111,7 @@ export type ReviewerDTO = {
export type ReviewDashboardRowDTO = {
firstName: string;
lastName: string;
position: PositionTitle;
position: string;
timesApplied: string;
applicationStatus: ApplicationStatus;
choice: number;
Expand Down
Loading