diff --git a/backend/typescript/migrations/2025.06.18T05.35.22.create-admin-table-create-position-table-update-user-table.ts b/backend/typescript/migrations/2025.06.18T05.35.22.create-admin-table-create-position-table-update-user-table.ts new file mode 100644 index 00000000..cfa830d9 --- /dev/null +++ b/backend/typescript/migrations/2025.06.18T05.35.22.create-admin-table-create-position-table-update-user-table.ts @@ -0,0 +1,96 @@ +import { DataType } from "sequelize-typescript"; + +import { Migration } from "../umzug"; +import { + CommunityPositionTitles, + Department, + DesignPositionTitles, + EngineeringPositionTitles, + PositionTitles, + ProductPositionTitles, +} from "../types"; + +const ADMIN_TABLE_NAME = "admins"; +const POSITION_TABLE_NAME = "positions"; +const USER_TABLE_NAME = "users"; + +const ADMIN_SEEDED_DATA = [ + { + userId: "1", + authorizedDepartments: [Department.Engineering, Department.Product], + }, + { + userId: "2", + authorizedDepartments: [Department.Design, Department.Community], + }, +]; +const POSITION_SEEDED_DATA = [ + ...EngineeringPositionTitles.map((title) => ({ + title, + department: Department.Engineering, + })), + ...DesignPositionTitles.map((title) => ({ + title, + department: Department.Design, + })), + ...ProductPositionTitles.map((title) => ({ + title, + department: Department.Product, + })), + ...CommunityPositionTitles.map((title) => ({ + title, + department: Department.Community, + })), +]; + +export const up: Migration = async ({ context: sequelize }) => { + await sequelize.getQueryInterface().createTable(POSITION_TABLE_NAME, { + title: { + type: DataType.ENUM(...PositionTitles), + allowNull: false, + primaryKey: true, + }, + department: { + type: DataType.ENUM(...Object.values(Department)), + allowNull: false, + }, + }); + await sequelize + .getQueryInterface() + .bulkInsert(POSITION_TABLE_NAME, POSITION_SEEDED_DATA); + + await sequelize.getQueryInterface().addColumn(USER_TABLE_NAME, "position", { + type: "enum_positions_title", + references: { + model: POSITION_TABLE_NAME, + key: "title", + }, + }); + + await sequelize.getQueryInterface().createTable(ADMIN_TABLE_NAME, { + userId: { + type: DataType.INTEGER, + allowNull: false, + primaryKey: true, + references: { + model: USER_TABLE_NAME, + key: "id", + }, + }, + authorizedDepartments: { + type: DataType.ARRAY(DataType.STRING), + allowNull: false, + }, + }); + await sequelize + .getQueryInterface() + .bulkInsert(ADMIN_TABLE_NAME, ADMIN_SEEDED_DATA); +}; + +export const down: Migration = async ({ context: sequelize }) => { + await sequelize.getQueryInterface().removeColumn(USER_TABLE_NAME, "position"); + await sequelize.getQueryInterface().dropTable(ADMIN_TABLE_NAME); + await sequelize.getQueryInterface().dropTable(POSITION_TABLE_NAME); + await sequelize.query('DROP TYPE IF EXISTS "enum_positions_title";'); + await sequelize.query('DROP TYPE IF EXISTS "enum_positions_department";'); +}; diff --git a/backend/typescript/models/admin.model.ts b/backend/typescript/models/admin.model.ts new file mode 100644 index 00000000..b673032e --- /dev/null +++ b/backend/typescript/models/admin.model.ts @@ -0,0 +1,19 @@ +import { + Column, + DataType, + ForeignKey, + Model, + Table, +} from "sequelize-typescript"; +import User from "./user.model"; +import { Department } from "../types"; + +@Table({ tableName: "admins" }) +export default class Admin extends Model { + @ForeignKey(() => User) + @Column({ type: DataType.INTEGER, primaryKey: true }) + userId!: number; + + @Column({ type: DataType.ARRAY(DataType.ENUM(...Object.values(Department))) }) + authorizedDepartments!: Department[]; +} diff --git a/backend/typescript/models/position.model.ts b/backend/typescript/models/position.model.ts new file mode 100644 index 00000000..41467246 --- /dev/null +++ b/backend/typescript/models/position.model.ts @@ -0,0 +1,11 @@ +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.ENUM(...Object.values(Department)) }) + department!: Department; +} diff --git a/backend/typescript/models/user.model.ts b/backend/typescript/models/user.model.ts index 92fd943f..14a7e987 100644 --- a/backend/typescript/models/user.model.ts +++ b/backend/typescript/models/user.model.ts @@ -1,8 +1,16 @@ /* eslint import/no-cycle: 0 */ -import { Column, DataType, HasMany, Model, Table } from "sequelize-typescript"; -import { Role } from "../types"; +import { + Column, + DataType, + ForeignKey, + HasMany, + Model, + Table, +} from "sequelize-typescript"; +import { PositionTitle, PositionTitles, Role } from "../types"; import ApplicationDashboardTable from "./applicationDashboard.model"; +import Position from "./position.model"; @Table({ tableName: "users" }) export default class User extends Model { @@ -24,6 +32,10 @@ export default class User extends Model { @Column({ type: DataType.ENUM("User", "Admin") }) role!: Role; + @ForeignKey(() => Position) + @Column({ type: DataType.ENUM(...Object.values(PositionTitles)) }) + position?: PositionTitle; + @HasMany(() => ApplicationDashboardTable) applicationDashboards?: ApplicationDashboardTable[]; } diff --git a/backend/typescript/types.ts b/backend/typescript/types.ts index d1299dda..96727273 100644 --- a/backend/typescript/types.ts +++ b/backend/typescript/types.ts @@ -125,3 +125,44 @@ export enum ApplicantRole { uxr = "user researcher", // design tab dev = "project developer", // eng tab } + +export enum Department { + Engineering = "Engineering", + Design = "Design", + Product = "Product", + Community = "Community", +} + +export const EngineeringPositionTitles = [ + "Project Lead", + "Developer", + "VP Engineering", +] as const; +export const DesignPositionTitles = ["Designer", "VP Design"] as const; +export const ProductPositionTitles = ["Product Manager", "VP Product"] as const; +export const CommunityPositionTitles = [ + "President", + "VP Scoping", + "VP Talent", + "VP Finance", + "Director Lead", + "Internal Director", + "External Director", + "Content Strategist", + "Graphic Designer", +] as const; + +export const PositionTitles = [ + ...EngineeringPositionTitles, + ...DesignPositionTitles, + ...ProductPositionTitles, + ...CommunityPositionTitles, +] as const; + +// Union types +export type EngineeringPositionTitle = + (typeof EngineeringPositionTitles)[number]; +export type DesignPositionTitle = (typeof DesignPositionTitles)[number]; +export type ProductPositionTitle = (typeof ProductPositionTitles)[number]; +export type CommunityPositionTitle = (typeof CommunityPositionTitles)[number]; +export type PositionTitle = (typeof PositionTitles)[number];