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
106 changes: 106 additions & 0 deletions db/migrations/20251019033849_add_organizer_applications_table.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import type { Knex } from "knex";


export async function up(knex: Knex): Promise<void> {
await knex.schema.createTable("organizer_applications", (table) => {
table.increments("id").unsigned().primary().notNullable();

// Basic Information
table.string("name").notNullable();
table.string("email").notNullable();

// Year and Major
table
.enum("year_standing", [
"Freshman",
"Sophomore",
"Junior",
"Senior",
"Other",
])
.notNullable();
table.string("major").notNullable();

// Team Preferences
table
.enum("first_choice_team", [
"Communications",
"Design",
"Education",
"Entertainment",
"Finance",
"Logistics",
"Marketing",
"Sponsorship",
"Technology",
])
.notNullable();
table
.enum("second_choice_team", [
"Communications",
"Design",
"Education",
"Entertainment",
"Finance",
"Logistics",
"Marketing",
"Sponsorship",
"Technology",
])
.notNullable();

// Resume (stored in Firebase, this stores the path/URL)
table.string("resume_url").notNullable();

// Application Questions
table.text("why_hackpsu", "longtext").notNullable();
table.text("new_idea", "longtext").notNullable();
table.text("what_excites_you", "longtext").notNullable();

// Application Status for each team preference
table
.enum("first_choice_status", ["pending", "accepted", "rejected"])
.notNullable()
.defaultTo("pending");
table
.enum("second_choice_status", ["pending", "accepted", "rejected"])
.notNullable()
.defaultTo("pending");

// Final assigned team (set when either first or second choice accepts)
table
.enum("assigned_team", [
"Communications",
"Design",
"Education",
"Entertainment",
"Finance",
"Logistics",
"Marketing",
"Sponsorship",
"Technology",
])
.nullable();

// Timestamps
table.timestamp("created_at").notNullable().defaultTo(knex.fn.now());
table
.timestamp("updated_at")
.notNullable()
.defaultTo(knex.raw("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"));

// Indexes for efficient querying
table.index("email");
table.index("first_choice_status");
table.index("second_choice_status");
table.index("first_choice_team");
table.index("second_choice_team");
table.index("assigned_team");
});
}


export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTableIfExists("organizer_applications");
}

2 changes: 2 additions & 0 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import { DriveModule } from "modules/drive/drive.module";
import { NotificationSchedulerModule } from "modules/notification-scheduler/notification-scheduler.module";
import { GotifyModule } from "common/gotify/gotify.module";
import gotifyConfig from "common/gotify/gotify.config";
import { OrganizerApplicationModule } from "modules/organizer-application/organizer-application.module";

@Module({
imports: [
Expand Down Expand Up @@ -110,6 +111,7 @@ import gotifyConfig from "common/gotify/gotify.config";
PhotoModule,
ReservationModule,
DriveModule,
OrganizerApplicationModule,

// WebSocket
SocketModule,
Expand Down
155 changes: 155 additions & 0 deletions src/entities/organizer-application.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import { ApiProperty, PickType } from "@nestjs/swagger";
import { Column, ID, Table } from "common/objection";
import { Entity } from "entities/base.entity";
import {
IsEmail,
IsEnum,
IsNumber,
IsOptional,
IsString,
} from "class-validator";
import { Expose } from "class-transformer";
import { ControllerMethod } from "common/validation";

export enum YearStanding {
FRESHMAN = "Freshman",
SOPHOMORE = "Sophomore",
JUNIOR = "Junior",
SENIOR = "Senior",
OTHER = "Other",
}

export enum OrganizerTeam {
COMMUNICATIONS = "Communications",
DESIGN = "Design",
EDUCATION = "Education",
ENTERTAINMENT = "Entertainment",
FINANCE = "Finance",
LOGISTICS = "Logistics",
MARKETING = "Marketing",
SPONSORSHIP = "Sponsorship",
TECHNOLOGY = "Technology",
}

export enum ApplicationStatus {
PENDING = "pending",
ACCEPTED = "accepted",
REJECTED = "rejected",
}

@Table({
name: "organizer_applications",
disableByHackathon: true,
})
export class OrganizerApplication extends Entity {
@ApiProperty()
@Expose({ groups: [ControllerMethod.POST] })
@IsNumber()
@ID({ type: "number" })
id: number;

@ApiProperty()
@IsString()
@Column({ type: "string" })
name: string;

@ApiProperty()
@IsEmail()
@Column({ type: "string" })
email: string;

@ApiProperty({ enum: YearStanding })
@IsEnum(YearStanding)
@Column({ type: "string" })
yearStanding: YearStanding;

@ApiProperty()
@IsString()
@Column({ type: "string" })
major: string;

@ApiProperty({ enum: OrganizerTeam })
@IsEnum(OrganizerTeam)
@Column({ type: "string" })
firstChoiceTeam: OrganizerTeam;

@ApiProperty({ enum: OrganizerTeam })
@IsEnum(OrganizerTeam)
@Column({ type: "string" })
secondChoiceTeam: OrganizerTeam;

@ApiProperty()
@IsString()
@Column({ type: "string" })
resumeUrl: string;

@ApiProperty()
@IsString()
@Column({ type: "string" })
whyHackpsu: string;

@ApiProperty()
@IsString()
@Column({ type: "string" })
newIdea: string;

@ApiProperty()
@IsString()
@Column({ type: "string" })
whatExcitesYou: string;

@ApiProperty({
enum: ApplicationStatus,
default: ApplicationStatus.PENDING,
required: false,
})
@IsOptional()
@IsEnum(ApplicationStatus)
@Column({ type: "string", required: false })
firstChoiceStatus?: ApplicationStatus;

@ApiProperty({
enum: ApplicationStatus,
default: ApplicationStatus.PENDING,
required: false,
})
@IsOptional()
@IsEnum(ApplicationStatus)
@Column({ type: "string", required: false })
secondChoiceStatus?: ApplicationStatus;

@ApiProperty({ enum: OrganizerTeam, required: false, nullable: true })
@IsOptional()
@IsEnum(OrganizerTeam)
@Column({ type: "string", required: false, nullable: true })
assignedTeam?: OrganizerTeam;

@ApiProperty({ required: false })
@IsOptional()
@Column({ type: "string", required: false })
createdAt?: Date;

@ApiProperty({ required: false })
@IsOptional()
@Column({ type: "string", required: false })
updatedAt?: Date;
}

export class OrganizerApplicationEntity extends PickType(OrganizerApplication, [
"id",
"name",
"email",
"yearStanding",
"major",
"firstChoiceTeam",
"secondChoiceTeam",
"resumeUrl",
"whyHackpsu",
"newIdea",
"whatExcitesYou",
"firstChoiceStatus",
"secondChoiceStatus",
"assignedTeam",
"createdAt",
"updatedAt",
] as const) {}
Loading