From 053e1601ecc7ec51af270c706be1a96923f2b8c6 Mon Sep 17 00:00:00 2001 From: Dylan Vidal Date: Mon, 13 Apr 2026 14:20:10 -0400 Subject: [PATCH 01/21] update cron --- apps/cron/src/crons/issue-reminders.ts | 67 +- packages/consts/src/index.ts | 1 + packages/consts/src/issue.ts | 3 + packages/db/drizzle/0002_dapper_forge.sql | 4 + packages/db/drizzle/meta/0002_snapshot.json | 2736 +++++++++++++++++++ packages/db/drizzle/meta/_journal.json | 7 + packages/db/src/schemas/auth.ts | 15 +- 7 files changed, 2784 insertions(+), 49 deletions(-) create mode 100644 packages/db/drizzle/0002_dapper_forge.sql create mode 100644 packages/db/drizzle/meta/0002_snapshot.json diff --git a/apps/cron/src/crons/issue-reminders.ts b/apps/cron/src/crons/issue-reminders.ts index 768189173..7f10c2347 100644 --- a/apps/cron/src/crons/issue-reminders.ts +++ b/apps/cron/src/crons/issue-reminders.ts @@ -1,6 +1,7 @@ import { Routes } from "discord-api-types/v10"; import { and, inArray, isNotNull, ne } from "drizzle-orm"; +import { IS_PROD, ISSUE } from "@forge/consts"; import { db } from "@forge/db/client"; import { Roles } from "@forge/db/schemas/auth"; import { Issue } from "@forge/db/schemas/knight-hacks"; @@ -10,20 +11,6 @@ import { api } from "@forge/utils/discord"; import { env } from "../env"; import { CronBuilder } from "../structs/CronBuilder"; -const ISSUE_REMINDER_CHANNELS = { - Teams: "Teams", - Directors: "Directors", - Design: "Design", - HackOrg: "HackOrg", -} as const; - -const ISSUE_REMINDER_DESTINATION_CHANNEL_IDS = { - Teams: "1459204271655489567", - Directors: "1463407041191088188", - HackOrg: "1461565747649187874", - Design: "1483901622558920945", -}; - const ISSUE_REMINDER_DAYS = { Fourteen: "Fourteen", Seven: "Seven", @@ -57,21 +44,23 @@ interface IssueReminderTarget { teamId: string; teamDiscordRoleId: string; assigneeDiscordUserIds: string[]; - channel: keyof typeof ISSUE_REMINDER_CHANNELS; + discordChannelId: string; day: IssueReminderDay; overdueDays: number | null; } type GroupedIssueReminders = Partial< - Record< - keyof typeof ISSUE_REMINDER_CHANNELS, - Partial> - > + Record>> >; const MAX_DISCORD_MESSAGE_LENGTH = 2000; const ISSUE_REMINDER_TIMEZONE = "America/New_York"; +const resolveDestinationChannelId = (roleChannelId: string): string => { + if (!IS_PROD) return ISSUE.DEV_ISSUE_REMINDER_CHANNEL_ID; + return roleChannelId || ISSUE.DEFAULT_ISSUE_REMINDER_CHANNEL_ID; +}; + const getTimezoneMidnightTimestamp = (date: Date, timeZone: string): number => { const parts = new Intl.DateTimeFormat("en-US", { timeZone, @@ -115,10 +104,9 @@ const buildIssueReminderTarget = (issue: { date: Date | null; teamDiscordRoleId: string; assigneeDiscordUserIds: string[]; - channel: keyof typeof ISSUE_REMINDER_CHANNELS | null; + discordChannelId: string; }): IssueReminderTarget | null => { if (!issue.date) return null; - if (!issue.channel) return null; const day = getIssueReminderDay(issue.date); if (!day) return null; if (!issue.teamDiscordRoleId) return null; @@ -128,7 +116,7 @@ const buildIssueReminderTarget = (issue: { teamId: issue.team, teamDiscordRoleId: issue.teamDiscordRoleId, assigneeDiscordUserIds: issue.assigneeDiscordUserIds, - channel: issue.channel, + discordChannelId: issue.discordChannelId, day, overdueDays: day === ISSUE_REMINDER_DAYS.Overdue @@ -148,8 +136,8 @@ const groupIssueReminderTargets = ( ): GroupedIssueReminders => { const grouped: GroupedIssueReminders = {}; for (const t of targets) { - grouped[t.channel] ??= {}; - const channelGroup = grouped[t.channel]; + grouped[t.discordChannelId] ??= {}; + const channelGroup = grouped[t.discordChannelId]; if (!channelGroup) continue; channelGroup[t.day] ??= []; const dayGroup = channelGroup[t.day]; @@ -291,7 +279,6 @@ const splitChannelReminderMessages = ( }; const sendIssueReminderChunk = async ( - channel: keyof typeof ISSUE_REMINDER_CHANNELS, channelId: string, chunk: { content: string; targets: IssueReminderTarget[] }, ) => { @@ -309,7 +296,10 @@ const sendIssueReminderChunk = async ( }, }); } catch (error) { - logger.error(`Failed sending issue reminder chunk for ${channel}.`, error); + logger.error( + `Failed sending issue reminder chunk for channel ${channelId}.`, + error, + ); } }; @@ -343,7 +333,7 @@ export const issueReminders = new CronBuilder({ string, { discordRoleId: string; - issueReminderChannel: keyof typeof ISSUE_REMINDER_CHANNELS | null; + issueReminderChannel: string; } > = {}; for (const r of roles) { @@ -355,41 +345,38 @@ export const issueReminders = new CronBuilder({ const reminderTargets = issues .map((issue) => { const role = roleDataByTeamId[issue.team]; - if (!role?.issueReminderChannel) { + if (!role) { logger.warn( - `Skipping issue reminder: no issue reminder channel configured for team ${issue.team}.`, + `Skipping issue reminder: no role row for team ${issue.team}.`, ); + return null; } + const discordChannelId = resolveDestinationChannelId( + role.issueReminderChannel, + ); return buildIssueReminderTarget({ id: issue.id, name: issue.name, team: issue.team, date: issue.date, - teamDiscordRoleId: role?.discordRoleId ?? "", + teamDiscordRoleId: role.discordRoleId, assigneeDiscordUserIds: issue.userAssignments.map( (assignment) => assignment.user.discordUserId, ), - channel: role?.issueReminderChannel ?? null, + discordChannelId, }); }) .filter(isIssueReminderTarget); const groupedReminders = groupIssueReminderTargets(reminderTargets); - for (const channel of Object.keys( - ISSUE_REMINDER_CHANNELS, - ) as (keyof typeof ISSUE_REMINDER_CHANNELS)[]) { - const groupedChannel = groupedReminders[channel]; + for (const [channelId, groupedChannel] of Object.entries(groupedReminders)) { if (!groupedChannel) continue; const chunks = splitChannelReminderMessages(groupedChannel); if (chunks.length === 0) continue; for (const chunk of chunks) { - await sendIssueReminderChunk( - channel, - ISSUE_REMINDER_DESTINATION_CHANNEL_IDS[channel], - chunk, - ); + await sendIssueReminderChunk(channelId, chunk); } } }); diff --git a/packages/consts/src/index.ts b/packages/consts/src/index.ts index 623f4b48b..b71ea4b36 100644 --- a/packages/consts/src/index.ts +++ b/packages/consts/src/index.ts @@ -1,3 +1,4 @@ +export { IS_PROD } from "./util"; export * as FORMS from "./forms"; export * as HACKATHONS from "./hackathons"; export * as PROJECT_LAUNCH from "./project-launch"; diff --git a/packages/consts/src/issue.ts b/packages/consts/src/issue.ts index 9241a24b1..db934de19 100644 --- a/packages/consts/src/issue.ts +++ b/packages/consts/src/issue.ts @@ -1,3 +1,6 @@ +export const DEFAULT_ISSUE_REMINDER_CHANNEL_ID = "1459204271655489567"; +export const DEV_ISSUE_REMINDER_CHANNEL_ID = "1263902679457992845"; + export const ISSUE_STATUS = [ "BACKLOG", "PLANNING", diff --git a/packages/db/drizzle/0002_dapper_forge.sql b/packages/db/drizzle/0002_dapper_forge.sql new file mode 100644 index 000000000..a2cc32be6 --- /dev/null +++ b/packages/db/drizzle/0002_dapper_forge.sql @@ -0,0 +1,4 @@ +ALTER TABLE "auth_roles" ALTER COLUMN "issue_reminder_channel" SET DATA TYPE varchar(32);--> statement-breakpoint +ALTER TABLE "auth_roles" ALTER COLUMN "issue_reminder_channel" SET DEFAULT '1459204271655489567';--> statement-breakpoint +ALTER TABLE "auth_roles" ALTER COLUMN "issue_reminder_channel" SET NOT NULL;--> statement-breakpoint +DROP TYPE "public"."issue_reminder_channel"; \ No newline at end of file diff --git a/packages/db/drizzle/meta/0002_snapshot.json b/packages/db/drizzle/meta/0002_snapshot.json new file mode 100644 index 000000000..388a28d86 --- /dev/null +++ b/packages/db/drizzle/meta/0002_snapshot.json @@ -0,0 +1,2736 @@ +{ + "id": "d293fee4-01a5-4e99-a06d-5b39520742ce", + "prevId": "41e22b5f-3477-4640-a21c-469f1df3e3d3", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.auth_account": { + "name": "auth_account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "provider_account_id": { + "name": "provider_account_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "refresh_token": { + "name": "refresh_token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "auth_account_user_id_auth_user_id_fk": { + "name": "auth_account_user_id_auth_user_id_fk", + "tableFrom": "auth_account", + "tableTo": "auth_user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "auth_account_provider_provider_account_id_pk": { + "name": "auth_account_provider_provider_account_id_pk", + "columns": ["provider", "provider_account_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_judge_session": { + "name": "auth_judge_session", + "schema": "", + "columns": { + "session_token": { + "name": "session_token", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "room_name": { + "name": "room_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires": { + "name": "expires", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_permissions": { + "name": "auth_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "role_id": { + "name": "role_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "auth_permissions_role_id_auth_roles_id_fk": { + "name": "auth_permissions_role_id_auth_roles_id_fk", + "tableFrom": "auth_permissions", + "tableTo": "auth_roles", + "columnsFrom": ["role_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "auth_permissions_user_id_auth_user_id_fk": { + "name": "auth_permissions_user_id_auth_user_id_fk", + "tableFrom": "auth_permissions", + "tableTo": "auth_user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_roles": { + "name": "auth_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "discord_role_id": { + "name": "discord_role_id", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "permissions": { + "name": "permissions", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "issue_reminder_channel": { + "name": "issue_reminder_channel", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'1459204271655489567'" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "auth_roles_discordRoleId_unique": { + "name": "auth_roles_discordRoleId_unique", + "nullsNotDistinct": false, + "columns": ["discord_role_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_session": { + "name": "auth_session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "session_token": { + "name": "session_token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "expires": { + "name": "expires", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "varchar(1024)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "auth_session_user_id_auth_user_id_fk": { + "name": "auth_session_user_id_auth_user_id_fk", + "tableFrom": "auth_session", + "tableTo": "auth_user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_user": { + "name": "auth_user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "discord_user_id": { + "name": "discord_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_verification": { + "name": "auth_verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_challenges": { + "name": "knight_hacks_challenges", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hackathon_id": { + "name": "hackathon_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sponsor": { + "name": "sponsor", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_challenges_hackathon_id_knight_hacks_hackathon_id_fk": { + "name": "knight_hacks_challenges_hackathon_id_knight_hacks_hackathon_id_fk", + "tableFrom": "knight_hacks_challenges", + "tableTo": "knight_hacks_hackathon", + "columnsFrom": ["hackathon_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "knight_hacks_challenges_title_hackathonId_unique": { + "name": "knight_hacks_challenges_title_hackathonId_unique", + "nullsNotDistinct": false, + "columns": ["title", "hackathon_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_dues_payment": { + "name": "knight_hacks_dues_payment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "member_id": { + "name": "member_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "payment_date": { + "name": "payment_date", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "year": { + "name": "year", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_dues_payment_member_id_knight_hacks_member_id_fk": { + "name": "knight_hacks_dues_payment_member_id_knight_hacks_member_id_fk", + "tableFrom": "knight_hacks_dues_payment", + "tableTo": "knight_hacks_member", + "columnsFrom": ["member_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "knight_hacks_dues_payment_memberId_year_unique": { + "name": "knight_hacks_dues_payment_memberId_year_unique", + "nullsNotDistinct": false, + "columns": ["member_id", "year"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_event": { + "name": "knight_hacks_event", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "discord_id": { + "name": "discord_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "google_id": { + "name": "google_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "tag": { + "name": "tag", + "type": "event_tag", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "start_datetime": { + "name": "start_datetime", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "end_datetime": { + "name": "end_datetime", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "location": { + "name": "location", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "dues_paying": { + "name": "dues_paying", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_operations_calendar": { + "name": "is_operations_calendar", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "roles": { + "name": "roles", + "type": "varchar(255)[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "points": { + "name": "points", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "hackathon_id": { + "name": "hackathon_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "discord_channel_id": { + "name": "discord_channel_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_event_hackathon_id_knight_hacks_hackathon_id_fk": { + "name": "knight_hacks_event_hackathon_id_knight_hacks_hackathon_id_fk", + "tableFrom": "knight_hacks_event", + "tableTo": "knight_hacks_hackathon", + "columnsFrom": ["hackathon_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_event_attendee": { + "name": "knight_hacks_event_attendee", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "member_id": { + "name": "member_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_event_attendee_member_id_knight_hacks_member_id_fk": { + "name": "knight_hacks_event_attendee_member_id_knight_hacks_member_id_fk", + "tableFrom": "knight_hacks_event_attendee", + "tableTo": "knight_hacks_member", + "columnsFrom": ["member_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knight_hacks_event_attendee_event_id_knight_hacks_event_id_fk": { + "name": "knight_hacks_event_attendee_event_id_knight_hacks_event_id_fk", + "tableFrom": "knight_hacks_event_attendee", + "tableTo": "knight_hacks_event", + "columnsFrom": ["event_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_event_feedback": { + "name": "knight_hacks_event_feedback", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "member_id": { + "name": "member_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "overall_event_rating": { + "name": "overall_event_rating", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "fun_rating": { + "name": "fun_rating", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "learned_rating": { + "name": "learned_rating", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "heard_about_us": { + "name": "heard_about_us", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "additional_feedback": { + "name": "additional_feedback", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "similar_event": { + "name": "similar_event", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_event_feedback_member_id_knight_hacks_member_id_fk": { + "name": "knight_hacks_event_feedback_member_id_knight_hacks_member_id_fk", + "tableFrom": "knight_hacks_event_feedback", + "tableTo": "knight_hacks_member", + "columnsFrom": ["member_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knight_hacks_event_feedback_event_id_knight_hacks_event_id_fk": { + "name": "knight_hacks_event_feedback_event_id_knight_hacks_event_id_fk", + "tableFrom": "knight_hacks_event_feedback", + "tableTo": "knight_hacks_event", + "columnsFrom": ["event_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_form_response": { + "name": "knight_hacks_form_response", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "form": { + "name": "form", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "response_data": { + "name": "response_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "edited_at": { + "name": "edited_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_form_response_form_knight_hacks_form_schemas_id_fk": { + "name": "knight_hacks_form_response_form_knight_hacks_form_schemas_id_fk", + "tableFrom": "knight_hacks_form_response", + "tableTo": "knight_hacks_form_schemas", + "columnsFrom": ["form"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "knight_hacks_form_response_user_id_auth_user_id_fk": { + "name": "knight_hacks_form_response_user_id_auth_user_id_fk", + "tableFrom": "knight_hacks_form_response", + "tableTo": "auth_user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_form_response_roles": { + "name": "knight_hacks_form_response_roles", + "schema": "", + "columns": { + "form_id": { + "name": "form_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_form_response_roles_form_id_knight_hacks_form_schemas_id_fk": { + "name": "knight_hacks_form_response_roles_form_id_knight_hacks_form_schemas_id_fk", + "tableFrom": "knight_hacks_form_response_roles", + "tableTo": "knight_hacks_form_schemas", + "columnsFrom": ["form_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knight_hacks_form_response_roles_role_id_auth_roles_id_fk": { + "name": "knight_hacks_form_response_roles_role_id_auth_roles_id_fk", + "tableFrom": "knight_hacks_form_response_roles", + "tableTo": "auth_roles", + "columnsFrom": ["role_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "knight_hacks_form_response_roles_form_id_role_id_pk": { + "name": "knight_hacks_form_response_roles_form_id_role_id_pk", + "columns": ["form_id", "role_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_form_section_roles": { + "name": "knight_hacks_form_section_roles", + "schema": "", + "columns": { + "section_id": { + "name": "section_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_form_section_roles_section_id_knight_hacks_form_sections_id_fk": { + "name": "knight_hacks_form_section_roles_section_id_knight_hacks_form_sections_id_fk", + "tableFrom": "knight_hacks_form_section_roles", + "tableTo": "knight_hacks_form_sections", + "columnsFrom": ["section_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knight_hacks_form_section_roles_role_id_auth_roles_id_fk": { + "name": "knight_hacks_form_section_roles_role_id_auth_roles_id_fk", + "tableFrom": "knight_hacks_form_section_roles", + "tableTo": "auth_roles", + "columnsFrom": ["role_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "knight_hacks_form_section_roles_section_id_role_id_pk": { + "name": "knight_hacks_form_section_roles_section_id_role_id_pk", + "columns": ["section_id", "role_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_form_sections": { + "name": "knight_hacks_form_sections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "order": { + "name": "order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "knight_hacks_form_sections_name_unique": { + "name": "knight_hacks_form_sections_name_unique", + "nullsNotDistinct": false, + "columns": ["name"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_form_schemas": { + "name": "knight_hacks_form_schemas", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "slug_name": { + "name": "slug_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "dues_only": { + "name": "dues_only", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "allow_resubmission": { + "name": "allow_resubmission", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "allow_edit": { + "name": "allow_edit", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "form_data": { + "name": "form_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "form_validator_json": { + "name": "form_validator_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "section": { + "name": "section", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'General'" + }, + "section_id": { + "name": "section_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "is_closed": { + "name": "is_closed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_form_schemas_section_id_knight_hacks_form_sections_id_fk": { + "name": "knight_hacks_form_schemas_section_id_knight_hacks_form_sections_id_fk", + "tableFrom": "knight_hacks_form_schemas", + "tableTo": "knight_hacks_form_sections", + "columnsFrom": ["section_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "knight_hacks_form_schemas_slugName_unique": { + "name": "knight_hacks_form_schemas_slugName_unique", + "nullsNotDistinct": false, + "columns": ["slug_name"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_hackathon": { + "name": "knight_hacks_hackathon", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "theme": { + "name": "theme", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "application_open": { + "name": "application_open", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "application_deadline": { + "name": "application_deadline", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "confirmation_deadline": { + "name": "confirmation_deadline", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "start_date": { + "name": "start_date", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "end_date": { + "name": "end_date", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_hackathon_sponsor": { + "name": "knight_hacks_hackathon_sponsor", + "schema": "", + "columns": { + "hackathon_id": { + "name": "hackathon_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sponsor_id": { + "name": "sponsor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "tier": { + "name": "tier", + "type": "sponsor_tier", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_hackathon_sponsor_hackathon_id_knight_hacks_hackathon_id_fk": { + "name": "knight_hacks_hackathon_sponsor_hackathon_id_knight_hacks_hackathon_id_fk", + "tableFrom": "knight_hacks_hackathon_sponsor", + "tableTo": "knight_hacks_hackathon", + "columnsFrom": ["hackathon_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knight_hacks_hackathon_sponsor_sponsor_id_knight_hacks_sponsor_id_fk": { + "name": "knight_hacks_hackathon_sponsor_sponsor_id_knight_hacks_sponsor_id_fk", + "tableFrom": "knight_hacks_hackathon_sponsor", + "tableTo": "knight_hacks_sponsor", + "columnsFrom": ["sponsor_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_hacker": { + "name": "knight_hacks_hacker", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "first_name": { + "name": "first_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "last_name": { + "name": "last_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "gender": { + "name": "gender", + "type": "gender", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'Prefer not to answer'" + }, + "discord_user": { + "name": "discord_user", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "age": { + "name": "age", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "country": { + "name": "country", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'United States of America'" + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "phone_number": { + "name": "phone_number", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "school": { + "name": "school", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "level_of_study": { + "name": "level_of_study", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "major": { + "name": "major", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'Computer Science'" + }, + "race_or_ethnicity": { + "name": "race_or_ethnicity", + "type": "race_or_ethnicity", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'Prefer not to answer'" + }, + "shirt_size": { + "name": "shirt_size", + "type": "shirt_size", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "github_profile_url": { + "name": "github_profile_url", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "linkedin_profile_url": { + "name": "linkedin_profile_url", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "website_url": { + "name": "website_url", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "resume_url": { + "name": "resume_url", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "dob": { + "name": "dob", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "grad_date": { + "name": "grad_date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "survey_1": { + "name": "survey_1", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "survey_2": { + "name": "survey_2", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_first_time": { + "name": "is_first_time", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "food_allergies": { + "name": "food_allergies", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agrees_to_receive_emails_from_mlh": { + "name": "agrees_to_receive_emails_from_mlh", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "agrees_to_mlh_code_of_conduct": { + "name": "agrees_to_mlh_code_of_conduct", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "agrees_to_mlh_data_sharing": { + "name": "agrees_to_mlh_data_sharing", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "date_created": { + "name": "date_created", + "type": "date", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_created": { + "name": "time_created", + "type": "time", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_hacker_user_id_auth_user_id_fk": { + "name": "knight_hacks_hacker_user_id_auth_user_id_fk", + "tableFrom": "knight_hacks_hacker", + "tableTo": "auth_user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_hacker_attendee": { + "name": "knight_hacks_hacker_attendee", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "hacker_id": { + "name": "hacker_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "hackathon_id": { + "name": "hackathon_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "time_applied": { + "name": "time_applied", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_confirmed": { + "name": "time_confirmed", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "points": { + "name": "points", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "class": { + "name": "class", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false, + "default": null + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_hacker_attendee_hacker_id_knight_hacks_hacker_id_fk": { + "name": "knight_hacks_hacker_attendee_hacker_id_knight_hacks_hacker_id_fk", + "tableFrom": "knight_hacks_hacker_attendee", + "tableTo": "knight_hacks_hacker", + "columnsFrom": ["hacker_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knight_hacks_hacker_attendee_hackathon_id_knight_hacks_hackathon_id_fk": { + "name": "knight_hacks_hacker_attendee_hackathon_id_knight_hacks_hackathon_id_fk", + "tableFrom": "knight_hacks_hacker_attendee", + "tableTo": "knight_hacks_hackathon", + "columnsFrom": ["hackathon_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_hacker_event_attendee": { + "name": "knight_hacks_hacker_event_attendee", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "hacker_att_id": { + "name": "hacker_att_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "hackathon_id": { + "name": "hackathon_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_hacker_event_attendee_hacker_att_id_knight_hacks_hacker_attendee_id_fk": { + "name": "knight_hacks_hacker_event_attendee_hacker_att_id_knight_hacks_hacker_attendee_id_fk", + "tableFrom": "knight_hacks_hacker_event_attendee", + "tableTo": "knight_hacks_hacker_attendee", + "columnsFrom": ["hacker_att_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knight_hacks_hacker_event_attendee_hackathon_id_knight_hacks_hackathon_id_fk": { + "name": "knight_hacks_hacker_event_attendee_hackathon_id_knight_hacks_hackathon_id_fk", + "tableFrom": "knight_hacks_hacker_event_attendee", + "tableTo": "knight_hacks_hackathon", + "columnsFrom": ["hackathon_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knight_hacks_hacker_event_attendee_event_id_knight_hacks_event_id_fk": { + "name": "knight_hacks_hacker_event_attendee_event_id_knight_hacks_event_id_fk", + "tableFrom": "knight_hacks_hacker_event_attendee", + "tableTo": "knight_hacks_event", + "columnsFrom": ["event_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_issue": { + "name": "knight_hacks_issue", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "status": { + "name": "status", + "type": "issue_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "links": { + "name": "links", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "event": { + "name": "event", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "issue_priority", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "team": { + "name": "team", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "creator": { + "name": "creator", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "parent": { + "name": "parent", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_team_idx": { + "name": "issue_team_idx", + "columns": [ + { + "expression": "team", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_creator_idx": { + "name": "issue_creator_idx", + "columns": [ + { + "expression": "creator", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_status_idx": { + "name": "issue_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_date_idx": { + "name": "issue_date_idx", + "columns": [ + { + "expression": "date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_parent_idx": { + "name": "issue_parent_idx", + "columns": [ + { + "expression": "parent", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_priority_idx": { + "name": "issue_priority_idx", + "columns": [ + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knight_hacks_issue_event_knight_hacks_event_id_fk": { + "name": "knight_hacks_issue_event_knight_hacks_event_id_fk", + "tableFrom": "knight_hacks_issue", + "tableTo": "knight_hacks_event", + "columnsFrom": ["event"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "knight_hacks_issue_team_auth_roles_id_fk": { + "name": "knight_hacks_issue_team_auth_roles_id_fk", + "tableFrom": "knight_hacks_issue", + "tableTo": "auth_roles", + "columnsFrom": ["team"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "knight_hacks_issue_creator_auth_user_id_fk": { + "name": "knight_hacks_issue_creator_auth_user_id_fk", + "tableFrom": "knight_hacks_issue", + "tableTo": "auth_user", + "columnsFrom": ["creator"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "issue_parent_fk": { + "name": "issue_parent_fk", + "tableFrom": "knight_hacks_issue", + "tableTo": "knight_hacks_issue", + "columnsFrom": ["parent"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_issues_to_teams_visibility": { + "name": "knight_hacks_issues_to_teams_visibility", + "schema": "", + "columns": { + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_issues_to_teams_visibility_issue_id_knight_hacks_issue_id_fk": { + "name": "knight_hacks_issues_to_teams_visibility_issue_id_knight_hacks_issue_id_fk", + "tableFrom": "knight_hacks_issues_to_teams_visibility", + "tableTo": "knight_hacks_issue", + "columnsFrom": ["issue_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knight_hacks_issues_to_teams_visibility_team_id_auth_roles_id_fk": { + "name": "knight_hacks_issues_to_teams_visibility_team_id_auth_roles_id_fk", + "tableFrom": "knight_hacks_issues_to_teams_visibility", + "tableTo": "auth_roles", + "columnsFrom": ["team_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "knight_hacks_issues_to_teams_visibility_issue_id_team_id_pk": { + "name": "knight_hacks_issues_to_teams_visibility_issue_id_team_id_pk", + "columns": ["issue_id", "team_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_issues_to_users_assignment": { + "name": "knight_hacks_issues_to_users_assignment", + "schema": "", + "columns": { + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_issues_to_users_assignment_issue_id_knight_hacks_issue_id_fk": { + "name": "knight_hacks_issues_to_users_assignment_issue_id_knight_hacks_issue_id_fk", + "tableFrom": "knight_hacks_issues_to_users_assignment", + "tableTo": "knight_hacks_issue", + "columnsFrom": ["issue_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knight_hacks_issues_to_users_assignment_user_id_auth_user_id_fk": { + "name": "knight_hacks_issues_to_users_assignment_user_id_auth_user_id_fk", + "tableFrom": "knight_hacks_issues_to_users_assignment", + "tableTo": "auth_user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "knight_hacks_issues_to_users_assignment_issue_id_user_id_pk": { + "name": "knight_hacks_issues_to_users_assignment_issue_id_user_id_pk", + "columns": ["issue_id", "user_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_judged_submission": { + "name": "knight_hacks_judged_submission", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "hackathon_id": { + "name": "hackathon_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "submission_id": { + "name": "submission_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "judge_id": { + "name": "judge_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "private_feedback": { + "name": "private_feedback", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "public_feedback": { + "name": "public_feedback", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "originality_rating": { + "name": "originality_rating", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "design_rating": { + "name": "design_rating", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "technical_understanding_rating": { + "name": "technical_understanding_rating", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "implementation_rating": { + "name": "implementation_rating", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "wow_factor_rating": { + "name": "wow_factor_rating", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_judged_submission_hackathon_id_knight_hacks_hackathon_id_fk": { + "name": "knight_hacks_judged_submission_hackathon_id_knight_hacks_hackathon_id_fk", + "tableFrom": "knight_hacks_judged_submission", + "tableTo": "knight_hacks_hackathon", + "columnsFrom": ["hackathon_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "knight_hacks_judged_submission_submission_id_knight_hacks_submissions_id_fk": { + "name": "knight_hacks_judged_submission_submission_id_knight_hacks_submissions_id_fk", + "tableFrom": "knight_hacks_judged_submission", + "tableTo": "knight_hacks_submissions", + "columnsFrom": ["submission_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "knight_hacks_judged_submission_judge_id_knight_hacks_judges_id_fk": { + "name": "knight_hacks_judged_submission_judge_id_knight_hacks_judges_id_fk", + "tableFrom": "knight_hacks_judged_submission", + "tableTo": "knight_hacks_judges", + "columnsFrom": ["judge_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_judges": { + "name": "knight_hacks_judges", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "room_name": { + "name": "room_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "challenge_id": { + "name": "challenge_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_judges_challenge_id_knight_hacks_challenges_id_fk": { + "name": "knight_hacks_judges_challenge_id_knight_hacks_challenges_id_fk", + "tableFrom": "knight_hacks_judges", + "tableTo": "knight_hacks_challenges", + "columnsFrom": ["challenge_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_member": { + "name": "knight_hacks_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "first_name": { + "name": "first_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "last_name": { + "name": "last_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "discord_user": { + "name": "discord_user", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "age": { + "name": "age", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "phone_number": { + "name": "phone_number", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "school": { + "name": "school", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "level_of_study": { + "name": "level_of_study", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "major": { + "name": "major", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'Computer Science'" + }, + "gender": { + "name": "gender", + "type": "gender", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'Prefer not to answer'" + }, + "race_or_ethnicity": { + "name": "race_or_ethnicity", + "type": "race_or_ethnicity", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'Prefer not to answer'" + }, + "guild_profile_visible": { + "name": "guild_profile_visible", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "tagline": { + "name": "tagline", + "type": "varchar(80)", + "primaryKey": false, + "notNull": false + }, + "about": { + "name": "about", + "type": "varchar(500)", + "primaryKey": false, + "notNull": false + }, + "profile_picture_url": { + "name": "profile_picture_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "shirt_size": { + "name": "shirt_size", + "type": "shirt_size", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "github_profile_url": { + "name": "github_profile_url", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "linkedin_profile_url": { + "name": "linkedin_profile_url", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "website_url": { + "name": "website_url", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "resume_url": { + "name": "resume_url", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "dob": { + "name": "dob", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "grad_date": { + "name": "grad_date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "company": { + "name": "company", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "points": { + "name": "points", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "date_created": { + "name": "date_created", + "type": "date", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_created": { + "name": "time_created", + "type": "time", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_member_user_id_auth_user_id_fk": { + "name": "knight_hacks_member_user_id_auth_user_id_fk", + "tableFrom": "knight_hacks_member", + "tableTo": "auth_user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "knight_hacks_member_email_unique": { + "name": "knight_hacks_member_email_unique", + "nullsNotDistinct": false, + "columns": ["email"] + }, + "knight_hacks_member_phoneNumber_unique": { + "name": "knight_hacks_member_phoneNumber_unique", + "nullsNotDistinct": false, + "columns": ["phone_number"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_companies": { + "name": "knight_hacks_companies", + "schema": "", + "columns": { + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_sponsor": { + "name": "knight_hacks_sponsor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "logo_url": { + "name": "logo_url", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "website_url": { + "name": "website_url", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_submissions": { + "name": "knight_hacks_submissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "challenge_id": { + "name": "challenge_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "hackathon_id": { + "name": "hackathon_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_submissions_challenge_id_knight_hacks_challenges_id_fk": { + "name": "knight_hacks_submissions_challenge_id_knight_hacks_challenges_id_fk", + "tableFrom": "knight_hacks_submissions", + "tableTo": "knight_hacks_challenges", + "columnsFrom": ["challenge_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knight_hacks_submissions_team_id_knight_hacks_teams_id_fk": { + "name": "knight_hacks_submissions_team_id_knight_hacks_teams_id_fk", + "tableFrom": "knight_hacks_submissions", + "tableTo": "knight_hacks_teams", + "columnsFrom": ["team_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knight_hacks_submissions_hackathon_id_knight_hacks_hackathon_id_fk": { + "name": "knight_hacks_submissions_hackathon_id_knight_hacks_hackathon_id_fk", + "tableFrom": "knight_hacks_submissions", + "tableTo": "knight_hacks_hackathon", + "columnsFrom": ["hackathon_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "knight_hacks_submissions_teamId_challengeId_unique": { + "name": "knight_hacks_submissions_teamId_challengeId_unique", + "nullsNotDistinct": false, + "columns": ["team_id", "challenge_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_teams": { + "name": "knight_hacks_teams", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "hackathon_id": { + "name": "hackathon_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_title": { + "name": "project_title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "submission_url": { + "name": "submission_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "project_created_at": { + "name": "project_created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "is_project_submitted": { + "name": "is_project_submitted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "devpost_url": { + "name": "devpost_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "universities": { + "name": "universities", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "emails": { + "name": "emails", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "match_key": { + "name": "match_key", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_teams_hackathon_id_knight_hacks_hackathon_id_fk": { + "name": "knight_hacks_teams_hackathon_id_knight_hacks_hackathon_id_fk", + "tableFrom": "knight_hacks_teams", + "tableTo": "knight_hacks_hackathon", + "columnsFrom": ["hackathon_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "knight_hacks_teams_matchKey_unique": { + "name": "knight_hacks_teams_matchKey_unique", + "nullsNotDistinct": false, + "columns": ["match_key"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_template": { + "name": "knight_hacks_template", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_trpc_form_connection": { + "name": "knight_hacks_trpc_form_connection", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "form": { + "name": "form", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "proc": { + "name": "proc", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "connections": { + "name": "connections", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_trpc_form_connection_form_knight_hacks_form_schemas_id_fk": { + "name": "knight_hacks_trpc_form_connection_form_knight_hacks_form_schemas_id_fk", + "tableFrom": "knight_hacks_trpc_form_connection", + "tableTo": "knight_hacks_form_schemas", + "columnsFrom": ["form"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.event_tag": { + "name": "event_tag", + "schema": "public", + "values": [ + "GBM", + "Social", + "Kickstart", + "Project Launch", + "Hello World", + "Sponsorship", + "Tech Exploration", + "Class Support", + "Workshop", + "OPS", + "Collabs", + "Check-in", + "Merch", + "Food", + "Ceremony", + "CAREER-FAIR", + "RSO-FAIR" + ] + }, + "public.gender": { + "name": "gender", + "schema": "public", + "values": [ + "Man", + "Woman", + "Non-binary", + "Prefer to self-describe", + "Prefer not to answer" + ] + }, + "public.hackathon_application_state": { + "name": "hackathon_application_state", + "schema": "public", + "values": [ + "withdrawn", + "pending", + "accepted", + "waitlisted", + "checkedin", + "confirmed", + "denied" + ] + }, + "public.issue_priority": { + "name": "issue_priority", + "schema": "public", + "values": ["LOWEST", "LOW", "MEDIUM", "HIGH", "HIGHEST"] + }, + "public.issue_status": { + "name": "issue_status", + "schema": "public", + "values": ["BACKLOG", "PLANNING", "IN_PROGRESS", "FINISHED"] + }, + "public.race_or_ethnicity": { + "name": "race_or_ethnicity", + "schema": "public", + "values": [ + "White", + "Black or African American", + "Hispanic / Latino / Spanish Origin", + "Asian", + "Native Hawaiian or Other Pacific Islander", + "Native American or Alaskan Native", + "Middle Eastern", + "Prefer not to answer", + "Other" + ] + }, + "public.shirt_size": { + "name": "shirt_size", + "schema": "public", + "values": ["XS", "S", "M", "L", "XL", "2XL", "3XL"] + }, + "public.sponsor_tier": { + "name": "sponsor_tier", + "schema": "public", + "values": ["gold", "silver", "bronze", "other"] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/packages/db/drizzle/meta/_journal.json b/packages/db/drizzle/meta/_journal.json index 928598a15..eee7605d2 100644 --- a/packages/db/drizzle/meta/_journal.json +++ b/packages/db/drizzle/meta/_journal.json @@ -15,6 +15,13 @@ "when": 1776028259336, "tag": "0001_careless_eternity", "breakpoints": true + }, + { + "idx": 2, + "version": "7", + "when": 1776104121304, + "tag": "0002_dapper_forge", + "breakpoints": true } ] } diff --git a/packages/db/src/schemas/auth.ts b/packages/db/src/schemas/auth.ts index d84af45ac..c685d5702 100644 --- a/packages/db/src/schemas/auth.ts +++ b/packages/db/src/schemas/auth.ts @@ -1,15 +1,9 @@ -import { pgEnum, pgTableCreator, primaryKey } from "drizzle-orm/pg-core"; +import { ISSUE } from "@forge/consts"; +import { pgTableCreator, primaryKey } from "drizzle-orm/pg-core"; import { createInsertSchema } from "drizzle-zod"; const createTable = pgTableCreator((name) => `auth_${name}`); -export const IssueReminderChannelEnum = pgEnum("issue_reminder_channel", [ - "Teams", - "Directors", - "Design", - "HackOrg", -]); - export const User = createTable("user", (t) => ({ id: t.uuid().notNull().primaryKey().defaultRandom(), discordUserId: t.varchar({ length: 255 }).notNull(), @@ -44,7 +38,10 @@ export const Roles = createTable("roles", (t) => ({ name: t.varchar().notNull().default(""), discordRoleId: t.varchar().unique().notNull(), permissions: t.varchar().notNull(), - issueReminderChannel: IssueReminderChannelEnum(), + issueReminderChannel: t + .varchar({ length: 32 }) + .notNull() + .default(ISSUE.DEFAULT_ISSUE_REMINDER_CHANNEL_ID), })); export const InsertRolesSchema = createInsertSchema(Roles); From 2f259d3e942c8be1f46b815c5f14eae75ebad70e Mon Sep 17 00:00:00 2001 From: Dylan Vidal Date: Mon, 13 Apr 2026 14:34:07 -0400 Subject: [PATCH 02/21] move to admin route --- .../navigation/reusable-user-dropdown.tsx | 44 +++++++++++++++++++ .../_components/navigation/user-dropdown.tsx | 8 ++++ .../src/app/admin/issues/calendar/page.tsx | 32 ++++++++++++++ .../src/app/admin/issues/kanban/page.tsx | 30 +++++++++++++ apps/blade/src/app/admin/issues/list/page.tsx | 30 +++++++++++++ apps/blade/src/app/admin/page.tsx | 29 ++++++++++++ 6 files changed, 173 insertions(+) create mode 100644 apps/blade/src/app/admin/issues/calendar/page.tsx create mode 100644 apps/blade/src/app/admin/issues/kanban/page.tsx create mode 100644 apps/blade/src/app/admin/issues/list/page.tsx diff --git a/apps/blade/src/app/_components/navigation/reusable-user-dropdown.tsx b/apps/blade/src/app/_components/navigation/reusable-user-dropdown.tsx index 77455eb80..fc26548fc 100644 --- a/apps/blade/src/app/_components/navigation/reusable-user-dropdown.tsx +++ b/apps/blade/src/app/_components/navigation/reusable-user-dropdown.tsx @@ -3,6 +3,8 @@ import { ChartPie, FormInput, Hotel, + ListTodo, + SquareKanban, Settings, ShieldCheck, Swords, @@ -90,6 +92,48 @@ export const systemItems: roleItems[] = [ }, ]; +export const issuesItems: roleItems[] = [ + { + name: "Kanban", + component: ( + + ), + route: "/admin/issues/kanban", + requiredPermissions: { + or: ["READ_ISSUES", "EDIT_ISSUES", "IS_OFFICER"], + }, + }, + { + name: "List", + component: ( + + ), + route: "/admin/issues/list", + requiredPermissions: { + or: ["READ_ISSUES", "EDIT_ISSUES", "IS_OFFICER"], + }, + }, + { + name: "Calendar", + component: ( + + ), + route: "/admin/issues/calendar", + requiredPermissions: { + or: ["READ_ISSUES", "EDIT_ISSUES", "IS_OFFICER"], + }, + }, +]; + export const clubItems: roleItems[] = [ { name: "Members", diff --git a/apps/blade/src/app/_components/navigation/user-dropdown.tsx b/apps/blade/src/app/_components/navigation/user-dropdown.tsx index dba314b02..477ef4341 100644 --- a/apps/blade/src/app/_components/navigation/user-dropdown.tsx +++ b/apps/blade/src/app/_components/navigation/user-dropdown.tsx @@ -24,6 +24,7 @@ import { adminItems, clubItems, hackathonItems, + issuesItems, systemItems, userItems, } from "./reusable-user-dropdown"; @@ -74,6 +75,7 @@ export function UserDropdown({ permissions }: UserDropdownProps) { systemItems, permissions, ); + const filteredIssuesItems = filterItemsByPermissions(issuesItems, permissions); const filteredClubItems = filterItemsByPermissions(clubItems, permissions); const filteredHackathonItems = filterItemsByPermissions( hackathonItems, @@ -107,6 +109,12 @@ export function UserDropdown({ permissions }: UserDropdownProps) { )} + {filteredIssuesItems.length > 0 && ( + <> + Issues + + + )} {filteredClubItems.length > 0 && ( <> Club diff --git a/apps/blade/src/app/admin/issues/calendar/page.tsx b/apps/blade/src/app/admin/issues/calendar/page.tsx new file mode 100644 index 000000000..676641ddd --- /dev/null +++ b/apps/blade/src/app/admin/issues/calendar/page.tsx @@ -0,0 +1,32 @@ +import { notFound, redirect } from "next/navigation"; + +import { auth } from "@forge/auth"; + +import Calendar from "~/app/_components/issue-calendar/calendar"; +import { SIGN_IN_PATH } from "~/consts"; +import { api, HydrateClient } from "~/trpc/server"; + +export default async function AdminIssuesCalendarPage() { + const session = await auth(); + if (!session) { + redirect(SIGN_IN_PATH); + } + + const hasAccess = await api.roles.hasPermission({ + and: [ + "READ_ISSUES", + "EDIT_ISSUES", + "EDIT_ISSUE_TEMPLATES", + "READ_ISSUE_TEMPLATES", + ], + }); + if (!hasAccess) notFound(); + + return ( + +
+ +
+
+ ); +} diff --git a/apps/blade/src/app/admin/issues/kanban/page.tsx b/apps/blade/src/app/admin/issues/kanban/page.tsx new file mode 100644 index 000000000..1fc90a69e --- /dev/null +++ b/apps/blade/src/app/admin/issues/kanban/page.tsx @@ -0,0 +1,30 @@ +import { notFound, redirect } from "next/navigation"; + +import { auth } from "@forge/auth"; + +import { KanbanBoard } from "~/app/_components/issue-kanban/issues-kanban"; +import { SIGN_IN_PATH } from "~/consts"; +import { api, HydrateClient } from "~/trpc/server"; + +export default async function AdminIssuesKanbanPage() { + const session = await auth(); + if (!session) redirect(SIGN_IN_PATH); + + const hasAccess = await api.roles.hasPermission({ + and: [ + "READ_ISSUES", + "EDIT_ISSUES", + "EDIT_ISSUE_TEMPLATES", + "READ_ISSUE_TEMPLATES", + ], + }); + if (!hasAccess) notFound(); + + return ( + +
+ +
+
+ ); +} diff --git a/apps/blade/src/app/admin/issues/list/page.tsx b/apps/blade/src/app/admin/issues/list/page.tsx new file mode 100644 index 000000000..ddcde3d70 --- /dev/null +++ b/apps/blade/src/app/admin/issues/list/page.tsx @@ -0,0 +1,30 @@ +import { notFound, redirect } from "next/navigation"; + +import { auth } from "@forge/auth"; + +import { IssuesList } from "~/app/_components/issue-list/issues-list"; +import { SIGN_IN_PATH } from "~/consts"; +import { api, HydrateClient } from "~/trpc/server"; + +export default async function AdminIssuesListPage() { + const session = await auth(); + if (!session) redirect(SIGN_IN_PATH); + + const hasAccess = await api.roles.hasPermission({ + and: [ + "READ_ISSUES", + "EDIT_ISSUES", + "EDIT_ISSUE_TEMPLATES", + "READ_ISSUE_TEMPLATES", + ], + }); + if (!hasAccess) notFound(); + + return ( + +
+ +
+
+ ); +} diff --git a/apps/blade/src/app/admin/page.tsx b/apps/blade/src/app/admin/page.tsx index d63f6d916..88a1ca50b 100644 --- a/apps/blade/src/app/admin/page.tsx +++ b/apps/blade/src/app/admin/page.tsx @@ -6,6 +6,8 @@ import { ChartPie, FormInput, Hotel, + ListTodo, + SquareKanban, Settings, ShieldCheck, Swords, @@ -135,6 +137,33 @@ export default async function Admin() { }, ], }, + { + title: "Issues", + description: "Manage issues across kanban, list, and calendar views", + icon: SquareKanban, + color: "text-amber-500", + bgColor: "bg-amber-500/10", + items: [ + { + href: "/admin/issues/kanban", + label: "Kanban", + icon: SquareKanban, + show: perms.READ_ISSUES || perms.EDIT_ISSUES || perms.IS_OFFICER, + }, + { + href: "/admin/issues/list", + label: "List", + icon: ListTodo, + show: perms.READ_ISSUES || perms.EDIT_ISSUES || perms.IS_OFFICER, + }, + { + href: "/admin/issues/calendar", + label: "Calendar", + icon: CalendarDays, + show: perms.READ_ISSUES || perms.EDIT_ISSUES || perms.IS_OFFICER, + }, + ], + }, { title: "System", description: "Forms, emails, and role management", From c4f17cff71127dc11be4c0fd91872b43d16ad135 Mon Sep 17 00:00:00 2001 From: Dylan Vidal Date: Mon, 13 Apr 2026 14:39:41 -0400 Subject: [PATCH 03/21] extrapolate floating bar --- .../_components/issue-calendar/calendar.tsx | 112 +++------------- .../issue-kanban/issues-kanban.tsx | 85 +++--------- .../_components/issue-list/issues-list.tsx | 76 ++--------- .../issues/issue-view-control-bar.tsx | 123 ++++++++++++++++++ 4 files changed, 169 insertions(+), 227 deletions(-) create mode 100644 apps/blade/src/app/_components/issues/issue-view-control-bar.tsx diff --git a/apps/blade/src/app/_components/issue-calendar/calendar.tsx b/apps/blade/src/app/_components/issue-calendar/calendar.tsx index b3389006b..e5a43e502 100644 --- a/apps/blade/src/app/_components/issue-calendar/calendar.tsx +++ b/apps/blade/src/app/_components/issue-calendar/calendar.tsx @@ -23,16 +23,9 @@ import { import dayGridPlugin from "@fullcalendar/daygrid"; import interactionPlugin from "@fullcalendar/interaction"; import FullCalendar from "@fullcalendar/react"; -import { - CheckCircle2, - ChevronLeft, - ChevronRight, - CircleDot, - SlidersHorizontal, -} from "lucide-react"; +import { ChevronLeft, ChevronRight } from "lucide-react"; import { ISSUE } from "@forge/consts"; -import { cn } from "@forge/ui"; import { Button } from "@forge/ui/button"; import { Tabs, TabsList, TabsTrigger } from "@forge/ui/tabs"; import { toast } from "@forge/ui/toast"; @@ -40,7 +33,10 @@ import { toast } from "@forge/ui/toast"; import { api } from "~/trpc/react"; import { CreateEditDialog } from "../issues/create-edit-dialog"; import { IssueFetcherPane } from "../issues/issue-fetcher-pane"; -import IssueTemplateDialog from "../issues/issue-template-dialog"; +import { + getActiveIssueFilterTags, + IssueViewControlBar, +} from "../issues/issue-view-control-bar"; import { IssueDayAgenda } from "./calendar-day-agenda"; import { CalendarIssueDialog } from "./calendar-issue-dialog"; import { IssueStatusDotLegend } from "./calendar-status-dot-legend"; @@ -124,13 +120,6 @@ function dismissFullCalendarMorePopovers() { }); } -function formatStatus(status: string) { - return status - .toLowerCase() - .replace(/_/g, " ") - .replace(/\b\w/g, (char) => char.toUpperCase()); -} - export default function CalendarView() { const calendarRef = useRef(null); const calendarSectionRef = useRef(null); @@ -180,21 +169,7 @@ export default function CalendarView() { const filters = paneData?.filters; const activeFilters = useMemo(() => { - if (!filters) return []; - const tags: string[] = []; - if (filters.statusFilter !== "all") - tags.push(formatStatus(filters.statusFilter)); - if (filters.teamFilter !== "all") tags.push("Team selected"); - if (filters.issueKind !== "all") - tags.push( - filters.issueKind === "task" ? "Tasks only" : "Event-linked only", - ); - if (filters.rootOnly) tags.push("Root only"); - if (filters.dateFrom) tags.push("From " + filters.dateFrom); - if (filters.dateTo) tags.push("To " + filters.dateTo); - if (filters.searchTerm.trim()) - tags.push('Search "' + filters.searchTerm.trim() + '"'); - return tags; + return getActiveIssueFilterTags(filters); }, [filters]); const issuesForCurrentView = useMemo(() => { @@ -534,6 +509,16 @@ export default function CalendarView() { ref={calendarSectionRef} className="calendar-theme mx-auto flex min-h-0 w-full min-w-0 max-w-6xl flex-1 flex-col gap-3 py-1" > + setIsFiltersOpen(true)} + /> +
@@ -578,71 +563,6 @@ export default function CalendarView() {
- -
-
0 - ? "md:flex-row md:items-start md:justify-between md:gap-6" - : "sm:flex-row sm:items-center sm:justify-between sm:gap-4 md:gap-6", - )} - > -
-
-
- - {openCount} Open -
-
- - {closedCount} Closed -
-
- {activeFilters.length > 0 ? ( -
- Active filters - {activeFilters.map((tag) => ( - - {tag} - - ))} -
- ) : null} -
- -
- - - - - -
-
-
diff --git a/apps/blade/src/app/_components/issue-kanban/issues-kanban.tsx b/apps/blade/src/app/_components/issue-kanban/issues-kanban.tsx index 90aa65af4..c1ed96e1e 100644 --- a/apps/blade/src/app/_components/issue-kanban/issues-kanban.tsx +++ b/apps/blade/src/app/_components/issue-kanban/issues-kanban.tsx @@ -2,22 +2,17 @@ import { useMemo, useState } from "react"; import Link from "next/link"; -import { - Calendar, - CheckCircle2, - CircleDot, - Pencil, - SlidersHorizontal, - Users, -} from "lucide-react"; +import { Calendar, Pencil, Users } from "lucide-react"; import { ISSUE } from "@forge/consts"; -import { Button } from "@forge/ui/button"; import { toast } from "@forge/ui/toast"; import { CreateEditDialog } from "~/app/_components/issues/create-edit-dialog"; import { IssueFetcherPane } from "~/app/_components/issues/issue-fetcher-pane"; -import IssueTemplateDialog from "~/app/_components/issues/issue-template-dialog"; +import { + getActiveIssueFilterTags, + IssueViewControlBar, +} from "~/app/_components/issues/issue-view-control-bar"; import { api } from "~/trpc/react"; const STATUS_COLORS: Record = { @@ -66,12 +61,14 @@ export function KanbanBoard() { onError: () => toast.error("Failed to update issue status"), }); - const issues = paneData?.issues - ? paneData.issues.map((issue) => ({ + const issues = useMemo( + () => + (paneData?.issues ?? []).map((issue) => ({ ...issue, status: statusOverrides[issue.id] ?? issue.status, - })) - : []; + })), + [paneData?.issues, statusOverrides], + ); const isLoading = paneData?.isLoading ?? true; @@ -85,21 +82,7 @@ export function KanbanBoard() { // --- Active Filters Logic --- const filters = paneData?.filters; const activeFilters = useMemo(() => { - if (!filters) return []; - const tags: string[] = []; - if (filters.statusFilter !== "all") - tags.push(formatStatus(filters.statusFilter)); - if (filters.teamFilter !== "all") tags.push("Team selected"); - if (filters.issueKind !== "all") - tags.push( - filters.issueKind === "task" ? "Tasks only" : "Event-linked only", - ); - if (filters.rootOnly) tags.push("Root"); - if (filters.dateFrom) tags.push("From " + filters.dateFrom); - if (filters.dateTo) tags.push("To " + filters.dateTo); - if (filters.searchTerm.trim()) - tags.push('Search "' + filters.searchTerm.trim() + '"'); - return tags; + return getActiveIssueFilterTags(filters); }, [filters]); const handleDragStart = ( @@ -147,44 +130,12 @@ export function KanbanBoard() { return (
-
-
-
- - {openCount} Open -
-
- - {closedCount} Closed -
-
- - {/* Action Buttons */} -
- - - - - -
-
- - {/* 2. Active Filter Tags */} - {activeFilters.length > 0 && ( -
- {activeFilters.map((tag) => ( - - {tag} - - ))} -
- )} + setIsFiltersOpen(true)} + /> {/* 3. The Board */} {isLoading ? ( diff --git a/apps/blade/src/app/_components/issue-list/issues-list.tsx b/apps/blade/src/app/_components/issue-list/issues-list.tsx index 8da1b3c6f..111008539 100644 --- a/apps/blade/src/app/_components/issue-list/issues-list.tsx +++ b/apps/blade/src/app/_components/issue-list/issues-list.tsx @@ -2,12 +2,7 @@ import { useMemo, useState } from "react"; import Link from "next/link"; -import { - CheckCircle2, - CircleDot, - Pencil, - SlidersHorizontal, -} from "lucide-react"; +import { Pencil } from "lucide-react"; import type { ISSUE } from "@forge/consts"; import { Button } from "@forge/ui/button"; @@ -16,16 +11,12 @@ import { toast } from "@forge/ui/toast"; import { CreateEditDialog } from "~/app/_components/issues/create-edit-dialog"; import { IssueFetcherPane } from "~/app/_components/issues/issue-fetcher-pane"; import { StatusSelect } from "~/app/_components/issues/issue-form-fields"; -import IssueTemplateDialog from "~/app/_components/issues/issue-template-dialog"; +import { + getActiveIssueFilterTags, + IssueViewControlBar, +} from "~/app/_components/issues/issue-view-control-bar"; import { api } from "~/trpc/react"; -function formatStatus(status: string) { - return status - .toLowerCase() - .replace(/_/g, " ") - .replace(/\b\w/g, (char) => char.toUpperCase()); -} - function formatDate(value: Date | null) { if (!value) return "No due date"; return new Date(value).toLocaleDateString(); @@ -104,60 +95,17 @@ export function IssuesList() { const filters = paneData?.filters; const activeFilters = useMemo(() => { - if (!filters) return []; - const tags: string[] = []; - if (filters.statusFilter !== "all") - tags.push(formatStatus(filters.statusFilter)); - if (filters.teamFilter !== "all") tags.push("Team selected"); - if (filters.issueKind !== "all") - tags.push( - filters.issueKind === "task" ? "Tasks only" : "Event-linked only", - ); - if (filters.rootOnly) tags.push("Root only"); - if (filters.dateFrom) tags.push("From " + filters.dateFrom); - if (filters.dateTo) tags.push("To " + filters.dateTo); - if (filters.searchTerm.trim()) - tags.push('Search "' + filters.searchTerm.trim() + '"'); - return tags; + return getActiveIssueFilterTags(filters); }, [filters]); return (
-
-
-
- - {openCount} Open -
-
- - {closedCount} Closed -
-
-
- - - - - -
-
- - {activeFilters.length > 0 && ( -
- {activeFilters.map((tag) => ( - - {tag} - - ))} -
- )} + setIsFiltersOpen(true)} + />
diff --git a/apps/blade/src/app/_components/issues/issue-view-control-bar.tsx b/apps/blade/src/app/_components/issues/issue-view-control-bar.tsx new file mode 100644 index 000000000..4d5403d48 --- /dev/null +++ b/apps/blade/src/app/_components/issues/issue-view-control-bar.tsx @@ -0,0 +1,123 @@ +"use client"; + +import { CheckCircle2, CircleDot, SlidersHorizontal } from "lucide-react"; + +import type { ISSUE } from "@forge/consts"; +import { Button } from "@forge/ui/button"; + +import { CreateEditDialog } from "~/app/_components/issues/create-edit-dialog"; +import IssueTemplateDialog from "~/app/_components/issues/issue-template-dialog"; + +function formatStatus(status: string) { + return status + .toLowerCase() + .replace(/_/g, " ") + .replace(/\b\w/g, (char) => char.toUpperCase()); +} + +export function getActiveIssueFilterTags( + filters: ISSUE.IssueFilters | null | undefined, +) { + if (!filters) return []; + + const tags: string[] = []; + if (filters.statusFilter !== "all") tags.push(formatStatus(filters.statusFilter)); + if (filters.teamFilter !== "all") tags.push("Team selected"); + if (filters.issueKind !== "all") { + tags.push( + filters.issueKind === "task" ? "Tasks only" : "Event-linked only", + ); + } + if (filters.rootOnly) tags.push("Root only"); + if (filters.dateFrom) tags.push("From " + filters.dateFrom); + if (filters.dateTo) tags.push("To " + filters.dateTo); + if (filters.searchTerm.trim()) { + tags.push('Search "' + filters.searchTerm.trim() + '"'); + } + + return tags; +} + +interface IssueViewControlBarProps { + openCount: number; + closedCount: number; + activeFilters: string[]; + onOpenFilters: () => void; + createInitialValues?: Partial; + onBeforeCreate?: () => void; + onBeforeOpenFilters?: () => void; +} + +export function IssueViewControlBar({ + openCount, + closedCount, + activeFilters, + onOpenFilters, + createInitialValues, + onBeforeCreate, + onBeforeOpenFilters, +}: IssueViewControlBarProps) { + return ( +
+
0 + ? "flex flex-col gap-3 md:flex-row md:items-start md:justify-between md:gap-6" + : "flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between sm:gap-4 md:gap-6" + } + > +
+
+
+ + {openCount} Open +
+
+ + {closedCount} Closed +
+
+ + {activeFilters.length > 0 ? ( +
+ Active filters + {activeFilters.map((tag) => ( + + {tag} + + ))} +
+ ) : null} +
+ +
+ + + + + +
+
+
+ ); +} From f5ccb03494fe02e288b5c4a5702d076f20c028a1 Mon Sep 17 00:00:00 2001 From: Dylan Vidal Date: Mon, 13 Apr 2026 14:49:04 -0400 Subject: [PATCH 04/21] consistency changes --- apps/blade/src/app/_components/issue-calendar/calendar.tsx | 6 +++--- .../src/app/_components/issue-kanban/issues-kanban.tsx | 4 ++-- apps/blade/src/app/_components/issue-list/issues-list.tsx | 6 +++--- .../src/app/_components/issues/issue-view-control-bar.tsx | 6 +++++- apps/blade/src/app/admin/issues/calendar/page.tsx | 2 +- apps/blade/src/app/admin/issues/kanban/page.tsx | 2 +- apps/blade/src/app/admin/issues/list/page.tsx | 2 +- packages/consts/src/issue.ts | 2 +- 8 files changed, 17 insertions(+), 13 deletions(-) diff --git a/apps/blade/src/app/_components/issue-calendar/calendar.tsx b/apps/blade/src/app/_components/issue-calendar/calendar.tsx index e5a43e502..b99e34393 100644 --- a/apps/blade/src/app/_components/issue-calendar/calendar.tsx +++ b/apps/blade/src/app/_components/issue-calendar/calendar.tsx @@ -169,8 +169,8 @@ export default function CalendarView() { const filters = paneData?.filters; const activeFilters = useMemo(() => { - return getActiveIssueFilterTags(filters); - }, [filters]); + return getActiveIssueFilterTags(filters, paneData?.roleNameById); + }, [filters, paneData?.roleNameById]); const issuesForCurrentView = useMemo(() => { if (view === "issueDayAgenda") { @@ -507,7 +507,7 @@ export default function CalendarView() { return (
{ - return getActiveIssueFilterTags(filters); - }, [filters]); + return getActiveIssueFilterTags(filters, paneData?.roleNameById); + }, [filters, paneData?.roleNameById]); const handleDragStart = ( e: React.DragEvent, diff --git a/apps/blade/src/app/_components/issue-list/issues-list.tsx b/apps/blade/src/app/_components/issue-list/issues-list.tsx index 111008539..4db2095b8 100644 --- a/apps/blade/src/app/_components/issue-list/issues-list.tsx +++ b/apps/blade/src/app/_components/issue-list/issues-list.tsx @@ -95,11 +95,11 @@ export function IssuesList() { const filters = paneData?.filters; const activeFilters = useMemo(() => { - return getActiveIssueFilterTags(filters); - }, [filters]); + return getActiveIssueFilterTags(filters, paneData?.roleNameById); + }, [filters, paneData?.roleNameById]); return ( -
+
| null, ) { if (!filters) return []; const tags: string[] = []; if (filters.statusFilter !== "all") tags.push(formatStatus(filters.statusFilter)); - if (filters.teamFilter !== "all") tags.push("Team selected"); + if (filters.teamFilter !== "all") { + const teamName = roleNameById?.get(filters.teamFilter) ?? filters.teamFilter; + tags.push(`Team Selected: ${teamName}`); + } if (filters.issueKind !== "all") { tags.push( filters.issueKind === "task" ? "Tasks only" : "Event-linked only", diff --git a/apps/blade/src/app/admin/issues/calendar/page.tsx b/apps/blade/src/app/admin/issues/calendar/page.tsx index 676641ddd..e012850ea 100644 --- a/apps/blade/src/app/admin/issues/calendar/page.tsx +++ b/apps/blade/src/app/admin/issues/calendar/page.tsx @@ -24,7 +24,7 @@ export default async function AdminIssuesCalendarPage() { return ( -
+
diff --git a/apps/blade/src/app/admin/issues/kanban/page.tsx b/apps/blade/src/app/admin/issues/kanban/page.tsx index 1fc90a69e..970578abf 100644 --- a/apps/blade/src/app/admin/issues/kanban/page.tsx +++ b/apps/blade/src/app/admin/issues/kanban/page.tsx @@ -22,7 +22,7 @@ export default async function AdminIssuesKanbanPage() { return ( -
+
diff --git a/apps/blade/src/app/admin/issues/list/page.tsx b/apps/blade/src/app/admin/issues/list/page.tsx index ddcde3d70..c5ee1dd94 100644 --- a/apps/blade/src/app/admin/issues/list/page.tsx +++ b/apps/blade/src/app/admin/issues/list/page.tsx @@ -22,7 +22,7 @@ export default async function AdminIssuesListPage() { return ( -
+
diff --git a/packages/consts/src/issue.ts b/packages/consts/src/issue.ts index db934de19..89c68c6a9 100644 --- a/packages/consts/src/issue.ts +++ b/packages/consts/src/issue.ts @@ -76,7 +76,7 @@ export const DEFAULT_ISSUE_FILTERS: IssueFilters = { searchTerm: "", dateFrom: "", dateTo: "", - rootOnly: true, + rootOnly: false, issueKind: "all", }; From 573f425d8c78f1ce6b7dd55398d34a908b6d41de Mon Sep 17 00:00:00 2001 From: Dylan Vidal Date: Mon, 13 Apr 2026 14:56:12 -0400 Subject: [PATCH 05/21] add parsing flow for {INPUT} and {PARENT} --- .../_components/issues/create-edit-dialog.tsx | 113 +++++++++++++++++- .../src/app/_components/issues/issue-node.tsx | 2 +- .../issues/issue-template-dialog.tsx | 23 +++- .../issues/issue-template-keywords.ts | 18 +++ 4 files changed, 145 insertions(+), 11 deletions(-) create mode 100644 apps/blade/src/app/_components/issues/issue-template-keywords.ts diff --git a/apps/blade/src/app/_components/issues/create-edit-dialog.tsx b/apps/blade/src/app/_components/issues/create-edit-dialog.tsx index 4586af220..8275d8297 100644 --- a/apps/blade/src/app/_components/issues/create-edit-dialog.tsx +++ b/apps/blade/src/app/_components/issues/create-edit-dialog.tsx @@ -20,6 +20,7 @@ import { Checkbox } from "@forge/ui/checkbox"; import { Dialog, DialogContent, + DialogDescription, DialogFooter, DialogHeader, DialogTitle, @@ -57,6 +58,11 @@ import { updateIssueNode, validateIssueNodes, } from "./issue-node"; +import { + hasInputKeyword, + resolveChildTemplateName, + resolveRootTemplateName, +} from "./issue-template-keywords"; const baseField = "w-full"; @@ -134,19 +140,22 @@ const defaultForm = (): ISSUE.IssueEditNode => newIssueNode(); function templateToIssueNodes( items: ISSUE.IssueTemplate[], parentDate: Date, + parentName?: string, ): ISSUE.IssueEditNode[] { return items.map((item) => { const date = item.dateMs ? new Date(parentDate.getTime() - item.dateMs) : undefined; + const resolvedName = resolveChildTemplateName(item.title, parentName); return newIssueNode({ - name: item.title, + name: resolvedName, description: item.description ?? "", team: item.team ?? "", date, children: templateToIssueNodes( item.children ?? [], date ?? normalizeTaskDueDate(), + resolvedName, ), }); }); @@ -166,6 +175,10 @@ export function CreateEditDialog(props: CreateEditDialogComponentProps) { const [internalOpen, setInternalOpen] = useState(false); const isControlled = open !== undefined; const isOpen = isControlled ? open : internalOpen; + const [templateInputPrompt, setTemplateInputPrompt] = useState<{ + template: { name: string; body: unknown }; + value: string; + } | null>(null); const utils = api.useUtils(); const rolesQuery = api.roles.getAllLinks.useQuery(); const hackathonsQuery = api.hackathon.getHackathons.useQuery(); @@ -343,13 +356,20 @@ export function CreateEditDialog(props: CreateEditDialogComponentProps) { const { data: templates = [], isLoading: isTemplatesLoading } = api.issues.getTemplates.useQuery(undefined, { enabled: isOpen }); - const applyTemplate = (template: { name: string; body: unknown }) => { + const applyTemplate = ( + template: { name: string; body: unknown }, + rootInput?: string, + ) => { const body = template.body as ISSUE.IssueTemplate[]; const root = Array.isArray(body) ? body[0] : undefined; + const resolvedRootName = resolveRootTemplateName( + root?.title ?? template.name, + rootInput?.trim() ?? "", + ); setFormValues((prev) => ({ ...prev, - name: root?.title ?? template.name, + name: resolvedRootName, description: root?.description ?? prev.description, ...(root?.team ? { team: root.team } : {}), })); @@ -358,10 +378,27 @@ export function CreateEditDialog(props: CreateEditDialogComponentProps) { templateToIssueNodes( root?.children ?? [], normalizeTaskDueDate(formValues.date), + resolvedRootName, ), ); }; + const handleTemplateSelection = (template: { name: string; body: unknown }) => { + const body = template.body as ISSUE.IssueTemplate[]; + const root = Array.isArray(body) ? body[0] : undefined; + const rootTitle = root?.title ?? template.name; + + if (hasInputKeyword(rootTitle)) { + setTemplateInputPrompt({ + template, + value: "", + }); + return; + } + + applyTemplate(template); + }; + async function buildIssueNodes( nodes: ISSUE.IssueEditNode[], ): Promise { @@ -551,6 +588,16 @@ export function CreateEditDialog(props: CreateEditDialogComponentProps) { } }; + const handleTemplateInputApply = () => { + if (!templateInputPrompt) return; + + const trimmedValue = templateInputPrompt.value.trim(); + if (!trimmedValue) return; + + applyTemplate(templateInputPrompt.template, trimmedValue); + setTemplateInputPrompt(null); + }; + return ( <> {trigger} @@ -913,7 +960,7 @@ export function CreateEditDialog(props: CreateEditDialogComponentProps) { {templates.map((t) => ( applyTemplate(t)} + onClick={() => handleTemplateSelection(t)} > {t.name} @@ -954,6 +1001,64 @@ export function CreateEditDialog(props: CreateEditDialogComponentProps) { + { + if (!open) setTemplateInputPrompt(null); + }} + > + + + Template Input + + This template uses {`{INPUT}`} in the parent issue + title. Enter the value to substitute before child issues are + generated. + + +
+ + + setTemplateInputPrompt((previous) => + previous + ? { + ...previous, + value: event.target.value, + } + : previous, + ) + } + onKeyDown={(event) => { + if (event.key === "Enter") { + event.preventDefault(); + handleTemplateInputApply(); + } + }} + /> +
+ + + + +
+
); } diff --git a/apps/blade/src/app/_components/issues/issue-node.tsx b/apps/blade/src/app/_components/issues/issue-node.tsx index fe14c43fc..13c6bedbd 100644 --- a/apps/blade/src/app/_components/issues/issue-node.tsx +++ b/apps/blade/src/app/_components/issues/issue-node.tsx @@ -435,7 +435,7 @@ export function IssueTemplateNode({ update("name", e.target.value)} onClick={(e) => e.stopPropagation()} diff --git a/apps/blade/src/app/_components/issues/issue-template-dialog.tsx b/apps/blade/src/app/_components/issues/issue-template-dialog.tsx index 7c34a62a3..6a7e7c433 100644 --- a/apps/blade/src/app/_components/issues/issue-template-dialog.tsx +++ b/apps/blade/src/app/_components/issues/issue-template-dialog.tsx @@ -327,12 +327,18 @@ export default function IssueTemplateDialog({ <>
- updateRoot({ name: e.target.value })} - /> +
+ updateRoot({ name: e.target.value })} + /> +

+ Root task names can include {`{INPUT}`} to + prompt for a value when the template is applied. +

+
@@ -373,6 +379,11 @@ export default function IssueTemplateDialog({ Add Sub-task
+

+ Hint: sub-task names can include {`{PARENT}`}{" "} + to insert the applied parent issue name. Example:{" "} + {`{PARENT} Post`}. +

{root.children.length === 0 && (

No sub-tasks yet. diff --git a/apps/blade/src/app/_components/issues/issue-template-keywords.ts b/apps/blade/src/app/_components/issues/issue-template-keywords.ts new file mode 100644 index 000000000..c22e91d6f --- /dev/null +++ b/apps/blade/src/app/_components/issues/issue-template-keywords.ts @@ -0,0 +1,18 @@ +const ROOT_INPUT_KEYWORD = "{INPUT}"; +const CHILD_PARENT_KEYWORD = "{PARENT}"; + +export function hasInputKeyword(name: string) { + return name.includes(ROOT_INPUT_KEYWORD); +} + +export function resolveRootTemplateName(name: string, input: string) { + return name.replaceAll(ROOT_INPUT_KEYWORD, input); +} + +export function resolveChildTemplateName( + name: string, + parentName: string | undefined, +) { + if (!parentName) return name; + return name.replaceAll(CHILD_PARENT_KEYWORD, parentName); +} From e6dfc03bf9cb2b7d35fc18f1297f0f2227ed61b4 Mon Sep 17 00:00:00 2001 From: Dylan Vidal Date: Mon, 13 Apr 2026 15:04:50 -0400 Subject: [PATCH 06/21] Update issue-view-control-bar.tsx --- .../issues/issue-view-control-bar.tsx | 58 ++++++++++++++++++- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/apps/blade/src/app/_components/issues/issue-view-control-bar.tsx b/apps/blade/src/app/_components/issues/issue-view-control-bar.tsx index e0e2b9eab..fc1311e66 100644 --- a/apps/blade/src/app/_components/issues/issue-view-control-bar.tsx +++ b/apps/blade/src/app/_components/issues/issue-view-control-bar.tsx @@ -1,9 +1,19 @@ "use client"; -import { CheckCircle2, CircleDot, SlidersHorizontal } from "lucide-react"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import { + CalendarDays, + CheckCircle2, + CircleDot, + ListTodo, + SlidersHorizontal, + SquareKanban, +} from "lucide-react"; +import { cn } from "@forge/ui"; import type { ISSUE } from "@forge/consts"; -import { Button } from "@forge/ui/button"; +import { Button, buttonVariants } from "@forge/ui/button"; import { CreateEditDialog } from "~/app/_components/issues/create-edit-dialog"; import IssueTemplateDialog from "~/app/_components/issues/issue-template-dialog"; @@ -52,6 +62,24 @@ interface IssueViewControlBarProps { onBeforeOpenFilters?: () => void; } +const issueViewLinks = [ + { + href: "/admin/issues/kanban", + label: "Kanban", + icon: SquareKanban, + }, + { + href: "/admin/issues/list", + label: "List", + icon: ListTodo, + }, + { + href: "/admin/issues/calendar", + label: "Calendar", + icon: CalendarDays, + }, +] as const; + export function IssueViewControlBar({ openCount, closedCount, @@ -61,6 +89,8 @@ export function IssueViewControlBar({ onBeforeCreate, onBeforeOpenFilters, }: IssueViewControlBarProps) { + const pathname = usePathname(); + return (

+
+ {issueViewLinks.map((link) => { + const Icon = link.icon; + const isActive = pathname === link.href; + + return ( + + + {link.label} + + ); + })} +
From 0357a415e7a591afccf7602bde336172a7de38bf Mon Sep 17 00:00:00 2001 From: Dylan Vidal Date: Mon, 13 Apr 2026 15:09:14 -0400 Subject: [PATCH 07/21] add shared bar controls and issue list updates --- .../_components/issue-list/issues-list.tsx | 134 ++++++++++++++++-- .../issues/issue-view-control-bar.tsx | 101 ++++++------- 2 files changed, 167 insertions(+), 68 deletions(-) diff --git a/apps/blade/src/app/_components/issue-list/issues-list.tsx b/apps/blade/src/app/_components/issue-list/issues-list.tsx index 4db2095b8..61e30718b 100644 --- a/apps/blade/src/app/_components/issue-list/issues-list.tsx +++ b/apps/blade/src/app/_components/issue-list/issues-list.tsx @@ -4,7 +4,7 @@ import { useMemo, useState } from "react"; import Link from "next/link"; import { Pencil } from "lucide-react"; -import type { ISSUE } from "@forge/consts"; +import { ISSUE } from "@forge/consts"; import { Button } from "@forge/ui/button"; import { toast } from "@forge/ui/toast"; @@ -15,6 +15,7 @@ import { getActiveIssueFilterTags, IssueViewControlBar, } from "~/app/_components/issues/issue-view-control-bar"; +import SortButton from "~/app/_components/shared/SortButton"; import { api } from "~/trpc/react"; function formatDate(value: Date | null) { @@ -45,6 +46,9 @@ function formatTeamLabel(roleName: string) { return trimmed || roleName; } +type SortField = "name" | "status" | "date" | "team" | "updatedAt"; +type SortOrder = "asc" | "desc" | null; + function isOverdueIssue(issue: ISSUE.IssueFetcherPaneIssue) { if (issue.status === "FINISHED" || !issue.date) return false; const dueDate = new Date(issue.date); @@ -55,6 +59,8 @@ function isOverdueIssue(issue: ISSUE.IssueFetcherPaneIssue) { export function IssuesList() { const [isFiltersOpen, setIsFiltersOpen] = useState(false); + const [sortField, setSortField] = useState(null); + const [sortOrder, setSortOrder] = useState(null); const [statusOverrides, setStatusOverrides] = useState< Record >({}); @@ -82,7 +88,14 @@ export function IssuesList() { }, }); - const issues = useMemo(() => paneData?.issues ?? [], [paneData?.issues]); + const issues = useMemo( + () => + (paneData?.issues ?? []).map((issue) => ({ + ...issue, + status: statusOverrides[issue.id] ?? issue.status, + })), + [paneData?.issues, statusOverrides], + ); const isLoading = paneData?.isLoading ?? true; const error = paneData?.error ?? null; @@ -98,6 +111,44 @@ export function IssuesList() { return getActiveIssueFilterTags(filters, paneData?.roleNameById); }, [filters, paneData?.roleNameById]); + const sortedIssues = useMemo(() => { + if (!sortField || !sortOrder) return issues; + + const roleNameById = paneData?.roleNameById; + const statusRank = new Map( + ISSUE.ISSUE_STATUS.map((status, index) => [status, index]), + ); + + const getTeamName = (issue: ISSUE.IssueFetcherPaneIssue) => + formatTeamLabel(roleNameById?.get(issue.team) ?? issue.team); + + const compareNullableDates = (left: Date | null, right: Date | null) => { + if (!left && !right) return 0; + if (!left) return 1; + if (!right) return -1; + return left.getTime() - right.getTime(); + }; + + const sorted = [...issues].sort((left, right) => { + switch (sortField) { + case "name": + return left.name.localeCompare(right.name); + case "status": + return ( + (statusRank.get(left.status) ?? 0) - (statusRank.get(right.status) ?? 0) + ); + case "date": + return compareNullableDates(left.date, right.date); + case "team": + return getTeamName(left).localeCompare(getTeamName(right)); + case "updatedAt": + return compareNullableDates(left.updatedAt, right.updatedAt); + } + }); + + return sortOrder === "asc" ? sorted : sorted.reverse(); + }, [issues, paneData?.roleNameById, sortField, sortOrder]); + return (
-
- Issue - Status - Due +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
Edit
@@ -135,10 +233,10 @@ export function IssuesList() { {!isLoading && !error && - issues.map((issue) => ( + sortedIssues.map((issue) => (
{issue.name} -
- {formatUpdatedAt(issue.updatedAt)} •{" "} - {formatTeamLabel( - paneData?.roleNameById.get(issue.team) ?? issue.team, - )} -
@@ -210,6 +302,20 @@ export function IssuesList() {
+
+ + Team + + {formatTeamLabel(paneData?.roleNameById.get(issue.team) ?? issue.team)} +
+ +
+ + Updated + + {formatUpdatedAt(issue.updatedAt)} +
+
Edit diff --git a/apps/blade/src/app/_components/issues/issue-view-control-bar.tsx b/apps/blade/src/app/_components/issues/issue-view-control-bar.tsx index fc1311e66..1e66f3ff2 100644 --- a/apps/blade/src/app/_components/issues/issue-view-control-bar.tsx +++ b/apps/blade/src/app/_components/issues/issue-view-control-bar.tsx @@ -93,65 +93,44 @@ export function IssueViewControlBar({ return (
-
0 - ? "flex flex-col gap-3 md:flex-row md:items-start md:justify-between md:gap-6" - : "flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between sm:gap-4 md:gap-6" - } - > -
-
- {issueViewLinks.map((link) => { - const Icon = link.icon; - const isActive = pathname === link.href; - - return ( - - - {link.label} - - ); - })} +
+
+
+ + {openCount} Open
-
-
- - {openCount} Open -
-
- - {closedCount} Closed -
+
+ + {closedCount} Closed
+
+ +
+ {issueViewLinks.map((link) => { + const Icon = link.icon; + const isActive = pathname === link.href; - {activeFilters.length > 0 ? ( -
- Active filters - {activeFilters.map((tag) => ( - - {tag} - - ))} -
- ) : null} + return ( + + + {link.label} + + ); + })}
-
+
+ + {activeFilters.length > 0 ? ( +
+ Active filters + {activeFilters.map((tag) => ( + + {tag} + + ))} +
+ ) : null}
); } From 3390a11aec1966c01166b8d7d4a57b61a58d592b Mon Sep 17 00:00:00 2001 From: Dylan Vidal Date: Mon, 13 Apr 2026 15:17:05 -0400 Subject: [PATCH 08/21] cascade delete --- packages/api/src/routers/issues.ts | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/packages/api/src/routers/issues.ts b/packages/api/src/routers/issues.ts index 76f85f7ab..6755dc102 100644 --- a/packages/api/src/routers/issues.ts +++ b/packages/api/src/routers/issues.ts @@ -95,6 +95,28 @@ async function requireIssue(id: string, label = "Issue") { return issue; } +async function collectIssueSubtreeIds( + tx: Parameters[0]>[0], + rootId: string, +) { + const allIds = [rootId]; + let frontier = [rootId]; + + while (frontier.length > 0) { + const children = await tx + .select({ id: Issue.id }) + .from(Issue) + .where(inArray(Issue.parent, frontier)); + + if (children.length === 0) break; + + frontier = children.map((child) => child.id); + allIds.push(...frontier); + } + + return allIds; +} + const baseIssueTemplateSchema = z.object({ title: z.string().min(1, "Title is required"), description: z.string().optional(), @@ -459,7 +481,10 @@ export const issuesRouter = { permissions.controlPerms.or(["EDIT_ISSUES"], ctx); await requireIssue(input.id); - await db.delete(Issue).where(eq(Issue.id, input.id)); + await db.transaction(async (tx) => { + const issueIds = await collectIssueSubtreeIds(tx, input.id); + await tx.delete(Issue).where(inArray(Issue.id, issueIds)); + }); return { success: true }; }), From adafa8a1d83a075a2f1614bb3d48558e312572cd Mon Sep 17 00:00:00 2001 From: Dylan Vidal Date: Mon, 13 Apr 2026 15:29:06 -0400 Subject: [PATCH 09/21] format --- .../src/app/_components/issue-list/issues-list.tsx | 7 +++++-- .../src/app/_components/issues/create-edit-dialog.tsx | 5 ++++- .../app/_components/issues/issue-template-dialog.tsx | 10 +++++----- .../app/_components/issues/issue-view-control-bar.tsx | 8 +++++--- .../_components/navigation/reusable-user-dropdown.tsx | 2 +- .../src/app/_components/navigation/user-dropdown.tsx | 5 ++++- apps/blade/src/app/admin/page.tsx | 2 +- packages/db/src/schemas/auth.ts | 3 ++- 8 files changed, 27 insertions(+), 15 deletions(-) diff --git a/apps/blade/src/app/_components/issue-list/issues-list.tsx b/apps/blade/src/app/_components/issue-list/issues-list.tsx index 61e30718b..6b79f2b66 100644 --- a/apps/blade/src/app/_components/issue-list/issues-list.tsx +++ b/apps/blade/src/app/_components/issue-list/issues-list.tsx @@ -135,7 +135,8 @@ export function IssuesList() { return left.name.localeCompare(right.name); case "status": return ( - (statusRank.get(left.status) ?? 0) - (statusRank.get(right.status) ?? 0) + (statusRank.get(left.status) ?? 0) - + (statusRank.get(right.status) ?? 0) ); case "date": return compareNullableDates(left.date, right.date); @@ -306,7 +307,9 @@ export function IssuesList() { Team - {formatTeamLabel(paneData?.roleNameById.get(issue.team) ?? issue.team)} + {formatTeamLabel( + paneData?.roleNameById.get(issue.team) ?? issue.team, + )}
diff --git a/apps/blade/src/app/_components/issues/create-edit-dialog.tsx b/apps/blade/src/app/_components/issues/create-edit-dialog.tsx index 8275d8297..e7eb37e6c 100644 --- a/apps/blade/src/app/_components/issues/create-edit-dialog.tsx +++ b/apps/blade/src/app/_components/issues/create-edit-dialog.tsx @@ -383,7 +383,10 @@ export function CreateEditDialog(props: CreateEditDialogComponentProps) { ); }; - const handleTemplateSelection = (template: { name: string; body: unknown }) => { + const handleTemplateSelection = (template: { + name: string; + body: unknown; + }) => { const body = template.body as ISSUE.IssueTemplate[]; const root = Array.isArray(body) ? body[0] : undefined; const rootTitle = root?.title ?? template.name; diff --git a/apps/blade/src/app/_components/issues/issue-template-dialog.tsx b/apps/blade/src/app/_components/issues/issue-template-dialog.tsx index 6a7e7c433..ae6f8be94 100644 --- a/apps/blade/src/app/_components/issues/issue-template-dialog.tsx +++ b/apps/blade/src/app/_components/issues/issue-template-dialog.tsx @@ -335,8 +335,8 @@ export default function IssueTemplateDialog({ onChange={(e) => updateRoot({ name: e.target.value })} />

- Root task names can include {`{INPUT}`} to - prompt for a value when the template is applied. + Root task names can include {`{INPUT}`}{" "} + to prompt for a value when the template is applied.

@@ -380,9 +380,9 @@ export default function IssueTemplateDialog({

- Hint: sub-task names can include {`{PARENT}`}{" "} - to insert the applied parent issue name. Example:{" "} - {`{PARENT} Post`}. + Hint: sub-task names can include{" "} + {`{PARENT}`} to insert the applied parent + issue name. Example: {`{PARENT} Post`}.

{root.children.length === 0 && (

diff --git a/apps/blade/src/app/_components/issues/issue-view-control-bar.tsx b/apps/blade/src/app/_components/issues/issue-view-control-bar.tsx index 1e66f3ff2..16efa7cec 100644 --- a/apps/blade/src/app/_components/issues/issue-view-control-bar.tsx +++ b/apps/blade/src/app/_components/issues/issue-view-control-bar.tsx @@ -11,8 +11,8 @@ import { SquareKanban, } from "lucide-react"; -import { cn } from "@forge/ui"; import type { ISSUE } from "@forge/consts"; +import { cn } from "@forge/ui"; import { Button, buttonVariants } from "@forge/ui/button"; import { CreateEditDialog } from "~/app/_components/issues/create-edit-dialog"; @@ -32,9 +32,11 @@ export function getActiveIssueFilterTags( if (!filters) return []; const tags: string[] = []; - if (filters.statusFilter !== "all") tags.push(formatStatus(filters.statusFilter)); + if (filters.statusFilter !== "all") + tags.push(formatStatus(filters.statusFilter)); if (filters.teamFilter !== "all") { - const teamName = roleNameById?.get(filters.teamFilter) ?? filters.teamFilter; + const teamName = + roleNameById?.get(filters.teamFilter) ?? filters.teamFilter; tags.push(`Team Selected: ${teamName}`); } if (filters.issueKind !== "all") { diff --git a/apps/blade/src/app/_components/navigation/reusable-user-dropdown.tsx b/apps/blade/src/app/_components/navigation/reusable-user-dropdown.tsx index fc26548fc..51b44b849 100644 --- a/apps/blade/src/app/_components/navigation/reusable-user-dropdown.tsx +++ b/apps/blade/src/app/_components/navigation/reusable-user-dropdown.tsx @@ -4,9 +4,9 @@ import { FormInput, Hotel, ListTodo, - SquareKanban, Settings, ShieldCheck, + SquareKanban, Swords, TicketCheck, User, diff --git a/apps/blade/src/app/_components/navigation/user-dropdown.tsx b/apps/blade/src/app/_components/navigation/user-dropdown.tsx index 477ef4341..9f24a511c 100644 --- a/apps/blade/src/app/_components/navigation/user-dropdown.tsx +++ b/apps/blade/src/app/_components/navigation/user-dropdown.tsx @@ -75,7 +75,10 @@ export function UserDropdown({ permissions }: UserDropdownProps) { systemItems, permissions, ); - const filteredIssuesItems = filterItemsByPermissions(issuesItems, permissions); + const filteredIssuesItems = filterItemsByPermissions( + issuesItems, + permissions, + ); const filteredClubItems = filterItemsByPermissions(clubItems, permissions); const filteredHackathonItems = filterItemsByPermissions( hackathonItems, diff --git a/apps/blade/src/app/admin/page.tsx b/apps/blade/src/app/admin/page.tsx index 88a1ca50b..73b949829 100644 --- a/apps/blade/src/app/admin/page.tsx +++ b/apps/blade/src/app/admin/page.tsx @@ -7,9 +7,9 @@ import { FormInput, Hotel, ListTodo, - SquareKanban, Settings, ShieldCheck, + SquareKanban, Swords, TicketCheck, User, diff --git a/packages/db/src/schemas/auth.ts b/packages/db/src/schemas/auth.ts index c685d5702..9d87700af 100644 --- a/packages/db/src/schemas/auth.ts +++ b/packages/db/src/schemas/auth.ts @@ -1,7 +1,8 @@ -import { ISSUE } from "@forge/consts"; import { pgTableCreator, primaryKey } from "drizzle-orm/pg-core"; import { createInsertSchema } from "drizzle-zod"; +import { ISSUE } from "@forge/consts"; + const createTable = pgTableCreator((name) => `auth_${name}`); export const User = createTable("user", (t) => ({ From a82faa00ae25891c27cc402c92522b3faeea09d1 Mon Sep 17 00:00:00 2001 From: Dylan Vidal Date: Mon, 13 Apr 2026 15:35:21 -0400 Subject: [PATCH 10/21] add color filtering --- .../issue-calendar/calendar-day-agenda.tsx | 11 + .../_components/issue-calendar/calendar.tsx | 23 +- .../_components/issues/issue-fetcher-pane.tsx | 9 + packages/api/src/routers/roles.ts | 22 + packages/consts/src/issue.ts | 1 + packages/db/drizzle/0003_hot_killraven.sql | 1 + packages/db/drizzle/meta/0003_snapshot.json | 2742 +++++++++++++++++ packages/db/drizzle/meta/_journal.json | 7 + packages/db/src/schemas/auth.ts | 1 + 9 files changed, 2816 insertions(+), 1 deletion(-) create mode 100644 packages/db/drizzle/0003_hot_killraven.sql create mode 100644 packages/db/drizzle/meta/0003_snapshot.json diff --git a/apps/blade/src/app/_components/issue-calendar/calendar-day-agenda.tsx b/apps/blade/src/app/_components/issue-calendar/calendar-day-agenda.tsx index 92098adaa..869e7a0b2 100644 --- a/apps/blade/src/app/_components/issue-calendar/calendar-day-agenda.tsx +++ b/apps/blade/src/app/_components/issue-calendar/calendar-day-agenda.tsx @@ -68,6 +68,7 @@ export function IssueDayAgenda(props: { issues: Issue[]; isLoading: boolean; roleNameById: Map | undefined; + roleColorById: Map | undefined; onIssueSelect?: (issueId: string) => void; onIssuesChanged?: () => void; }) { @@ -76,6 +77,7 @@ export function IssueDayAgenda(props: { issues, isLoading, roleNameById, + roleColorById, onIssueSelect, onIssuesChanged, } = props; @@ -171,6 +173,7 @@ export function IssueDayAgenda(props: { const teamsText = teams.join(" · "); const showTeamsBlock = teamsText.length > 0; const assigneeNames = assigneeDisplayNames(issue); + const teamColor = roleColorById?.get(issue.team) ?? null; const assigneesText = assigneeNames.length > 0 ? assigneeNames.join(" · ") @@ -180,6 +183,14 @@ export function IssueDayAgenda(props: {

  • diff --git a/apps/blade/src/app/_components/issue-calendar/calendar.tsx b/apps/blade/src/app/_components/issue-calendar/calendar.tsx index b99e34393..97989b1da 100644 --- a/apps/blade/src/app/_components/issue-calendar/calendar.tsx +++ b/apps/blade/src/app/_components/issue-calendar/calendar.tsx @@ -52,6 +52,16 @@ function issueStatusLabel(status: IssueCalendarStatus) { .join(" "); } +function calendarEventTextColor(backgroundColor: string) { + const hex = backgroundColor.replace("#", ""); + if (!/^[0-9a-fA-F]{6}$/.test(hex)) return "#ffffff"; + const r = parseInt(hex.slice(0, 2), 16); + const g = parseInt(hex.slice(2, 4), 16); + const b = parseInt(hex.slice(4, 6), 16); + const yiq = (r * 299 + g * 587 + b * 114) / 1000; + return yiq >= 160 ? "#111827" : "#ffffff"; +} + function startOfLocalDay(isoOrDate: Date): Date { const d = new Date(isoOrDate); return new Date(d.getFullYear(), d.getMonth(), d.getDate()); @@ -211,6 +221,14 @@ export default function CalendarView() { return issuesForCurrentView.flatMap((issue): EventInput[] => { if (!issue.date) return []; const d = new Date(issue.date); + const teamColor = paneData?.roleColorById.get(issue.team) ?? null; + const eventPalette = teamColor + ? { + backgroundColor: teamColor, + borderColor: teamColor, + textColor: calendarEventTextColor(teamColor), + } + : {}; const baseClassNames = [ "calendar-issue", issue.event ? "calendar-issue--linked" : "calendar-issue--task", @@ -229,6 +247,7 @@ export default function CalendarView() { display: "block" as const, extendedProps: { issueStatus: issue.status }, classNames: baseClassNames, + ...eventPalette, }, ]; } @@ -244,10 +263,11 @@ export default function CalendarView() { display: "block" as const, extendedProps: { issueStatus: issue.status }, classNames: baseClassNames, + ...eventPalette, }, ]; }); - }, [view, issuesForCurrentView]); + }, [paneData?.roleColorById, view, issuesForCurrentView]); const fullCalendarViews = useMemo( () => ({ @@ -608,6 +628,7 @@ export default function CalendarView() { issues={issuesForCurrentView} isLoading={paneData?.isLoading ?? true} roleNameById={paneData?.roleNameById} + roleColorById={paneData?.roleColorById} onIssueSelect={(issueId: string) => { setDetailIssueId(issueId); setIsDetailOpen(true); diff --git a/apps/blade/src/app/_components/issues/issue-fetcher-pane.tsx b/apps/blade/src/app/_components/issues/issue-fetcher-pane.tsx index b8775ae8d..26c4175dd 100644 --- a/apps/blade/src/app/_components/issues/issue-fetcher-pane.tsx +++ b/apps/blade/src/app/_components/issues/issue-fetcher-pane.tsx @@ -134,6 +134,11 @@ export function IssueFetcherPane(props: IssueFetcherPaneProps) { () => new Map(roles.map((role) => [role.id, role.name])), [roles], ); + const roleColorById = useMemo( + () => + new Map(roles.map((role) => [role.id, role.teamHexcodeColor ?? null])), + [roles], + ); const allIssues = useMemo( () => (issuesData ?? []) as ISSUE.IssueFetcherPaneIssue[], @@ -184,6 +189,9 @@ export function IssueFetcherPane(props: IssueFetcherPaneProps) { issues: isReady ? issues : [], blockedParentIds: isReady ? blockedParentIds : new Set(), roleNameById: isReady ? roleNameById : new Map(), + roleColorById: isReady + ? roleColorById + : new Map(), isLoading: combinedIsLoading, error: combinedErrorMessage, refresh, @@ -197,6 +205,7 @@ export function IssueFetcherPane(props: IssueFetcherPaneProps) { isReady, issues, refresh, + roleColorById, roleNameById, ], ); diff --git a/packages/api/src/routers/roles.ts b/packages/api/src/routers/roles.ts index 784b711c9..6aa6962aa 100644 --- a/packages/api/src/routers/roles.ts +++ b/packages/api/src/routers/roles.ts @@ -13,6 +13,22 @@ import * as discord from "@forge/utils/discord"; import { permProcedure, protectedProcedure } from "../trpc"; +function discordRoleColorToHex(role: APIRole | null) { + if (!role || role.color <= 0) return null; + return `#${role.color.toString(16).padStart(6, "0")}`; +} + +async function fetchDiscordRoleHexColor(roleId: string) { + try { + const role = (await discord.api.get( + Routes.guildRole(DISCORD.KNIGHTHACKS_GUILD, roleId), + )) as APIRole | null; + return discordRoleColorToHex(role); + } catch { + return null; + } +} + export const rolesRouter = { // ROLES @@ -37,6 +53,8 @@ export const rolesRouter = { code: "CONFLICT", }); + const teamHexcodeColor = await fetchDiscordRoleHexColor(input.roleId); + // Create the role link first const insertedRoles = await db .insert(Roles) @@ -44,6 +62,7 @@ export const rolesRouter = { name: input.name, discordRoleId: input.roleId, permissions: input.permissions, + teamHexcodeColor, }) .returning(); @@ -145,12 +164,15 @@ export const rolesRouter = { code: "CONFLICT", }); + const teamHexcodeColor = await fetchDiscordRoleHexColor(input.roleId); + await db .update(Roles) .set({ name: input.name, discordRoleId: input.roleId, permissions: input.permissions, + teamHexcodeColor, }) .where(eq(Roles.id, input.id)); diff --git a/packages/consts/src/issue.ts b/packages/consts/src/issue.ts index 89c68c6a9..5575b69ed 100644 --- a/packages/consts/src/issue.ts +++ b/packages/consts/src/issue.ts @@ -64,6 +64,7 @@ export interface IssueFetcherPaneData { issues: IssueFetcherPaneIssue[]; blockedParentIds: Set; roleNameById: Map; + roleColorById: Map; isLoading: boolean; error: string | null; refresh: () => void; diff --git a/packages/db/drizzle/0003_hot_killraven.sql b/packages/db/drizzle/0003_hot_killraven.sql new file mode 100644 index 000000000..91ad28eac --- /dev/null +++ b/packages/db/drizzle/0003_hot_killraven.sql @@ -0,0 +1 @@ +ALTER TABLE "auth_roles" ADD COLUMN "team_hexcode_color" varchar(7); \ No newline at end of file diff --git a/packages/db/drizzle/meta/0003_snapshot.json b/packages/db/drizzle/meta/0003_snapshot.json new file mode 100644 index 000000000..aff9ff3e9 --- /dev/null +++ b/packages/db/drizzle/meta/0003_snapshot.json @@ -0,0 +1,2742 @@ +{ + "id": "3c88a643-8696-4819-afc0-11117b8e124e", + "prevId": "d293fee4-01a5-4e99-a06d-5b39520742ce", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.auth_account": { + "name": "auth_account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "provider_account_id": { + "name": "provider_account_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "refresh_token": { + "name": "refresh_token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "auth_account_user_id_auth_user_id_fk": { + "name": "auth_account_user_id_auth_user_id_fk", + "tableFrom": "auth_account", + "tableTo": "auth_user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "auth_account_provider_provider_account_id_pk": { + "name": "auth_account_provider_provider_account_id_pk", + "columns": ["provider", "provider_account_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_judge_session": { + "name": "auth_judge_session", + "schema": "", + "columns": { + "session_token": { + "name": "session_token", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "room_name": { + "name": "room_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires": { + "name": "expires", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_permissions": { + "name": "auth_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "role_id": { + "name": "role_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "auth_permissions_role_id_auth_roles_id_fk": { + "name": "auth_permissions_role_id_auth_roles_id_fk", + "tableFrom": "auth_permissions", + "tableTo": "auth_roles", + "columnsFrom": ["role_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "auth_permissions_user_id_auth_user_id_fk": { + "name": "auth_permissions_user_id_auth_user_id_fk", + "tableFrom": "auth_permissions", + "tableTo": "auth_user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_roles": { + "name": "auth_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "discord_role_id": { + "name": "discord_role_id", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "permissions": { + "name": "permissions", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "issue_reminder_channel": { + "name": "issue_reminder_channel", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'1459204271655489567'" + }, + "team_hexcode_color": { + "name": "team_hexcode_color", + "type": "varchar(7)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "auth_roles_discordRoleId_unique": { + "name": "auth_roles_discordRoleId_unique", + "nullsNotDistinct": false, + "columns": ["discord_role_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_session": { + "name": "auth_session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "session_token": { + "name": "session_token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "expires": { + "name": "expires", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "varchar(1024)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "auth_session_user_id_auth_user_id_fk": { + "name": "auth_session_user_id_auth_user_id_fk", + "tableFrom": "auth_session", + "tableTo": "auth_user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_user": { + "name": "auth_user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "discord_user_id": { + "name": "discord_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_verification": { + "name": "auth_verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_challenges": { + "name": "knight_hacks_challenges", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hackathon_id": { + "name": "hackathon_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sponsor": { + "name": "sponsor", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_challenges_hackathon_id_knight_hacks_hackathon_id_fk": { + "name": "knight_hacks_challenges_hackathon_id_knight_hacks_hackathon_id_fk", + "tableFrom": "knight_hacks_challenges", + "tableTo": "knight_hacks_hackathon", + "columnsFrom": ["hackathon_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "knight_hacks_challenges_title_hackathonId_unique": { + "name": "knight_hacks_challenges_title_hackathonId_unique", + "nullsNotDistinct": false, + "columns": ["title", "hackathon_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_dues_payment": { + "name": "knight_hacks_dues_payment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "member_id": { + "name": "member_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "payment_date": { + "name": "payment_date", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "year": { + "name": "year", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_dues_payment_member_id_knight_hacks_member_id_fk": { + "name": "knight_hacks_dues_payment_member_id_knight_hacks_member_id_fk", + "tableFrom": "knight_hacks_dues_payment", + "tableTo": "knight_hacks_member", + "columnsFrom": ["member_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "knight_hacks_dues_payment_memberId_year_unique": { + "name": "knight_hacks_dues_payment_memberId_year_unique", + "nullsNotDistinct": false, + "columns": ["member_id", "year"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_event": { + "name": "knight_hacks_event", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "discord_id": { + "name": "discord_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "google_id": { + "name": "google_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "tag": { + "name": "tag", + "type": "event_tag", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "start_datetime": { + "name": "start_datetime", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "end_datetime": { + "name": "end_datetime", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "location": { + "name": "location", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "dues_paying": { + "name": "dues_paying", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_operations_calendar": { + "name": "is_operations_calendar", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "roles": { + "name": "roles", + "type": "varchar(255)[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "points": { + "name": "points", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "hackathon_id": { + "name": "hackathon_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "discord_channel_id": { + "name": "discord_channel_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_event_hackathon_id_knight_hacks_hackathon_id_fk": { + "name": "knight_hacks_event_hackathon_id_knight_hacks_hackathon_id_fk", + "tableFrom": "knight_hacks_event", + "tableTo": "knight_hacks_hackathon", + "columnsFrom": ["hackathon_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_event_attendee": { + "name": "knight_hacks_event_attendee", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "member_id": { + "name": "member_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_event_attendee_member_id_knight_hacks_member_id_fk": { + "name": "knight_hacks_event_attendee_member_id_knight_hacks_member_id_fk", + "tableFrom": "knight_hacks_event_attendee", + "tableTo": "knight_hacks_member", + "columnsFrom": ["member_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knight_hacks_event_attendee_event_id_knight_hacks_event_id_fk": { + "name": "knight_hacks_event_attendee_event_id_knight_hacks_event_id_fk", + "tableFrom": "knight_hacks_event_attendee", + "tableTo": "knight_hacks_event", + "columnsFrom": ["event_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_event_feedback": { + "name": "knight_hacks_event_feedback", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "member_id": { + "name": "member_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "overall_event_rating": { + "name": "overall_event_rating", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "fun_rating": { + "name": "fun_rating", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "learned_rating": { + "name": "learned_rating", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "heard_about_us": { + "name": "heard_about_us", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "additional_feedback": { + "name": "additional_feedback", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "similar_event": { + "name": "similar_event", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_event_feedback_member_id_knight_hacks_member_id_fk": { + "name": "knight_hacks_event_feedback_member_id_knight_hacks_member_id_fk", + "tableFrom": "knight_hacks_event_feedback", + "tableTo": "knight_hacks_member", + "columnsFrom": ["member_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knight_hacks_event_feedback_event_id_knight_hacks_event_id_fk": { + "name": "knight_hacks_event_feedback_event_id_knight_hacks_event_id_fk", + "tableFrom": "knight_hacks_event_feedback", + "tableTo": "knight_hacks_event", + "columnsFrom": ["event_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_form_response": { + "name": "knight_hacks_form_response", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "form": { + "name": "form", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "response_data": { + "name": "response_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "edited_at": { + "name": "edited_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_form_response_form_knight_hacks_form_schemas_id_fk": { + "name": "knight_hacks_form_response_form_knight_hacks_form_schemas_id_fk", + "tableFrom": "knight_hacks_form_response", + "tableTo": "knight_hacks_form_schemas", + "columnsFrom": ["form"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "knight_hacks_form_response_user_id_auth_user_id_fk": { + "name": "knight_hacks_form_response_user_id_auth_user_id_fk", + "tableFrom": "knight_hacks_form_response", + "tableTo": "auth_user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_form_response_roles": { + "name": "knight_hacks_form_response_roles", + "schema": "", + "columns": { + "form_id": { + "name": "form_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_form_response_roles_form_id_knight_hacks_form_schemas_id_fk": { + "name": "knight_hacks_form_response_roles_form_id_knight_hacks_form_schemas_id_fk", + "tableFrom": "knight_hacks_form_response_roles", + "tableTo": "knight_hacks_form_schemas", + "columnsFrom": ["form_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knight_hacks_form_response_roles_role_id_auth_roles_id_fk": { + "name": "knight_hacks_form_response_roles_role_id_auth_roles_id_fk", + "tableFrom": "knight_hacks_form_response_roles", + "tableTo": "auth_roles", + "columnsFrom": ["role_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "knight_hacks_form_response_roles_form_id_role_id_pk": { + "name": "knight_hacks_form_response_roles_form_id_role_id_pk", + "columns": ["form_id", "role_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_form_section_roles": { + "name": "knight_hacks_form_section_roles", + "schema": "", + "columns": { + "section_id": { + "name": "section_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_form_section_roles_section_id_knight_hacks_form_sections_id_fk": { + "name": "knight_hacks_form_section_roles_section_id_knight_hacks_form_sections_id_fk", + "tableFrom": "knight_hacks_form_section_roles", + "tableTo": "knight_hacks_form_sections", + "columnsFrom": ["section_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knight_hacks_form_section_roles_role_id_auth_roles_id_fk": { + "name": "knight_hacks_form_section_roles_role_id_auth_roles_id_fk", + "tableFrom": "knight_hacks_form_section_roles", + "tableTo": "auth_roles", + "columnsFrom": ["role_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "knight_hacks_form_section_roles_section_id_role_id_pk": { + "name": "knight_hacks_form_section_roles_section_id_role_id_pk", + "columns": ["section_id", "role_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_form_sections": { + "name": "knight_hacks_form_sections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "order": { + "name": "order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "knight_hacks_form_sections_name_unique": { + "name": "knight_hacks_form_sections_name_unique", + "nullsNotDistinct": false, + "columns": ["name"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_form_schemas": { + "name": "knight_hacks_form_schemas", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "slug_name": { + "name": "slug_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "dues_only": { + "name": "dues_only", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "allow_resubmission": { + "name": "allow_resubmission", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "allow_edit": { + "name": "allow_edit", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "form_data": { + "name": "form_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "form_validator_json": { + "name": "form_validator_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "section": { + "name": "section", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'General'" + }, + "section_id": { + "name": "section_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "is_closed": { + "name": "is_closed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_form_schemas_section_id_knight_hacks_form_sections_id_fk": { + "name": "knight_hacks_form_schemas_section_id_knight_hacks_form_sections_id_fk", + "tableFrom": "knight_hacks_form_schemas", + "tableTo": "knight_hacks_form_sections", + "columnsFrom": ["section_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "knight_hacks_form_schemas_slugName_unique": { + "name": "knight_hacks_form_schemas_slugName_unique", + "nullsNotDistinct": false, + "columns": ["slug_name"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_hackathon": { + "name": "knight_hacks_hackathon", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "theme": { + "name": "theme", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "application_open": { + "name": "application_open", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "application_deadline": { + "name": "application_deadline", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "confirmation_deadline": { + "name": "confirmation_deadline", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "start_date": { + "name": "start_date", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "end_date": { + "name": "end_date", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_hackathon_sponsor": { + "name": "knight_hacks_hackathon_sponsor", + "schema": "", + "columns": { + "hackathon_id": { + "name": "hackathon_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sponsor_id": { + "name": "sponsor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "tier": { + "name": "tier", + "type": "sponsor_tier", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_hackathon_sponsor_hackathon_id_knight_hacks_hackathon_id_fk": { + "name": "knight_hacks_hackathon_sponsor_hackathon_id_knight_hacks_hackathon_id_fk", + "tableFrom": "knight_hacks_hackathon_sponsor", + "tableTo": "knight_hacks_hackathon", + "columnsFrom": ["hackathon_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knight_hacks_hackathon_sponsor_sponsor_id_knight_hacks_sponsor_id_fk": { + "name": "knight_hacks_hackathon_sponsor_sponsor_id_knight_hacks_sponsor_id_fk", + "tableFrom": "knight_hacks_hackathon_sponsor", + "tableTo": "knight_hacks_sponsor", + "columnsFrom": ["sponsor_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_hacker": { + "name": "knight_hacks_hacker", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "first_name": { + "name": "first_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "last_name": { + "name": "last_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "gender": { + "name": "gender", + "type": "gender", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'Prefer not to answer'" + }, + "discord_user": { + "name": "discord_user", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "age": { + "name": "age", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "country": { + "name": "country", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'United States of America'" + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "phone_number": { + "name": "phone_number", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "school": { + "name": "school", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "level_of_study": { + "name": "level_of_study", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "major": { + "name": "major", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'Computer Science'" + }, + "race_or_ethnicity": { + "name": "race_or_ethnicity", + "type": "race_or_ethnicity", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'Prefer not to answer'" + }, + "shirt_size": { + "name": "shirt_size", + "type": "shirt_size", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "github_profile_url": { + "name": "github_profile_url", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "linkedin_profile_url": { + "name": "linkedin_profile_url", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "website_url": { + "name": "website_url", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "resume_url": { + "name": "resume_url", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "dob": { + "name": "dob", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "grad_date": { + "name": "grad_date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "survey_1": { + "name": "survey_1", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "survey_2": { + "name": "survey_2", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_first_time": { + "name": "is_first_time", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "food_allergies": { + "name": "food_allergies", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agrees_to_receive_emails_from_mlh": { + "name": "agrees_to_receive_emails_from_mlh", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "agrees_to_mlh_code_of_conduct": { + "name": "agrees_to_mlh_code_of_conduct", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "agrees_to_mlh_data_sharing": { + "name": "agrees_to_mlh_data_sharing", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "date_created": { + "name": "date_created", + "type": "date", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_created": { + "name": "time_created", + "type": "time", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_hacker_user_id_auth_user_id_fk": { + "name": "knight_hacks_hacker_user_id_auth_user_id_fk", + "tableFrom": "knight_hacks_hacker", + "tableTo": "auth_user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_hacker_attendee": { + "name": "knight_hacks_hacker_attendee", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "hacker_id": { + "name": "hacker_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "hackathon_id": { + "name": "hackathon_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "time_applied": { + "name": "time_applied", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_confirmed": { + "name": "time_confirmed", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "points": { + "name": "points", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "class": { + "name": "class", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false, + "default": null + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_hacker_attendee_hacker_id_knight_hacks_hacker_id_fk": { + "name": "knight_hacks_hacker_attendee_hacker_id_knight_hacks_hacker_id_fk", + "tableFrom": "knight_hacks_hacker_attendee", + "tableTo": "knight_hacks_hacker", + "columnsFrom": ["hacker_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knight_hacks_hacker_attendee_hackathon_id_knight_hacks_hackathon_id_fk": { + "name": "knight_hacks_hacker_attendee_hackathon_id_knight_hacks_hackathon_id_fk", + "tableFrom": "knight_hacks_hacker_attendee", + "tableTo": "knight_hacks_hackathon", + "columnsFrom": ["hackathon_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_hacker_event_attendee": { + "name": "knight_hacks_hacker_event_attendee", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "hacker_att_id": { + "name": "hacker_att_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "hackathon_id": { + "name": "hackathon_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_hacker_event_attendee_hacker_att_id_knight_hacks_hacker_attendee_id_fk": { + "name": "knight_hacks_hacker_event_attendee_hacker_att_id_knight_hacks_hacker_attendee_id_fk", + "tableFrom": "knight_hacks_hacker_event_attendee", + "tableTo": "knight_hacks_hacker_attendee", + "columnsFrom": ["hacker_att_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knight_hacks_hacker_event_attendee_hackathon_id_knight_hacks_hackathon_id_fk": { + "name": "knight_hacks_hacker_event_attendee_hackathon_id_knight_hacks_hackathon_id_fk", + "tableFrom": "knight_hacks_hacker_event_attendee", + "tableTo": "knight_hacks_hackathon", + "columnsFrom": ["hackathon_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knight_hacks_hacker_event_attendee_event_id_knight_hacks_event_id_fk": { + "name": "knight_hacks_hacker_event_attendee_event_id_knight_hacks_event_id_fk", + "tableFrom": "knight_hacks_hacker_event_attendee", + "tableTo": "knight_hacks_event", + "columnsFrom": ["event_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_issue": { + "name": "knight_hacks_issue", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "status": { + "name": "status", + "type": "issue_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "links": { + "name": "links", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "event": { + "name": "event", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "issue_priority", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "team": { + "name": "team", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "creator": { + "name": "creator", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "parent": { + "name": "parent", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_team_idx": { + "name": "issue_team_idx", + "columns": [ + { + "expression": "team", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_creator_idx": { + "name": "issue_creator_idx", + "columns": [ + { + "expression": "creator", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_status_idx": { + "name": "issue_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_date_idx": { + "name": "issue_date_idx", + "columns": [ + { + "expression": "date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_parent_idx": { + "name": "issue_parent_idx", + "columns": [ + { + "expression": "parent", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_priority_idx": { + "name": "issue_priority_idx", + "columns": [ + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knight_hacks_issue_event_knight_hacks_event_id_fk": { + "name": "knight_hacks_issue_event_knight_hacks_event_id_fk", + "tableFrom": "knight_hacks_issue", + "tableTo": "knight_hacks_event", + "columnsFrom": ["event"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "knight_hacks_issue_team_auth_roles_id_fk": { + "name": "knight_hacks_issue_team_auth_roles_id_fk", + "tableFrom": "knight_hacks_issue", + "tableTo": "auth_roles", + "columnsFrom": ["team"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "knight_hacks_issue_creator_auth_user_id_fk": { + "name": "knight_hacks_issue_creator_auth_user_id_fk", + "tableFrom": "knight_hacks_issue", + "tableTo": "auth_user", + "columnsFrom": ["creator"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "issue_parent_fk": { + "name": "issue_parent_fk", + "tableFrom": "knight_hacks_issue", + "tableTo": "knight_hacks_issue", + "columnsFrom": ["parent"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_issues_to_teams_visibility": { + "name": "knight_hacks_issues_to_teams_visibility", + "schema": "", + "columns": { + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_issues_to_teams_visibility_issue_id_knight_hacks_issue_id_fk": { + "name": "knight_hacks_issues_to_teams_visibility_issue_id_knight_hacks_issue_id_fk", + "tableFrom": "knight_hacks_issues_to_teams_visibility", + "tableTo": "knight_hacks_issue", + "columnsFrom": ["issue_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knight_hacks_issues_to_teams_visibility_team_id_auth_roles_id_fk": { + "name": "knight_hacks_issues_to_teams_visibility_team_id_auth_roles_id_fk", + "tableFrom": "knight_hacks_issues_to_teams_visibility", + "tableTo": "auth_roles", + "columnsFrom": ["team_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "knight_hacks_issues_to_teams_visibility_issue_id_team_id_pk": { + "name": "knight_hacks_issues_to_teams_visibility_issue_id_team_id_pk", + "columns": ["issue_id", "team_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_issues_to_users_assignment": { + "name": "knight_hacks_issues_to_users_assignment", + "schema": "", + "columns": { + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_issues_to_users_assignment_issue_id_knight_hacks_issue_id_fk": { + "name": "knight_hacks_issues_to_users_assignment_issue_id_knight_hacks_issue_id_fk", + "tableFrom": "knight_hacks_issues_to_users_assignment", + "tableTo": "knight_hacks_issue", + "columnsFrom": ["issue_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knight_hacks_issues_to_users_assignment_user_id_auth_user_id_fk": { + "name": "knight_hacks_issues_to_users_assignment_user_id_auth_user_id_fk", + "tableFrom": "knight_hacks_issues_to_users_assignment", + "tableTo": "auth_user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "knight_hacks_issues_to_users_assignment_issue_id_user_id_pk": { + "name": "knight_hacks_issues_to_users_assignment_issue_id_user_id_pk", + "columns": ["issue_id", "user_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_judged_submission": { + "name": "knight_hacks_judged_submission", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "hackathon_id": { + "name": "hackathon_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "submission_id": { + "name": "submission_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "judge_id": { + "name": "judge_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "private_feedback": { + "name": "private_feedback", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "public_feedback": { + "name": "public_feedback", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "originality_rating": { + "name": "originality_rating", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "design_rating": { + "name": "design_rating", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "technical_understanding_rating": { + "name": "technical_understanding_rating", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "implementation_rating": { + "name": "implementation_rating", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "wow_factor_rating": { + "name": "wow_factor_rating", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_judged_submission_hackathon_id_knight_hacks_hackathon_id_fk": { + "name": "knight_hacks_judged_submission_hackathon_id_knight_hacks_hackathon_id_fk", + "tableFrom": "knight_hacks_judged_submission", + "tableTo": "knight_hacks_hackathon", + "columnsFrom": ["hackathon_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "knight_hacks_judged_submission_submission_id_knight_hacks_submissions_id_fk": { + "name": "knight_hacks_judged_submission_submission_id_knight_hacks_submissions_id_fk", + "tableFrom": "knight_hacks_judged_submission", + "tableTo": "knight_hacks_submissions", + "columnsFrom": ["submission_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "knight_hacks_judged_submission_judge_id_knight_hacks_judges_id_fk": { + "name": "knight_hacks_judged_submission_judge_id_knight_hacks_judges_id_fk", + "tableFrom": "knight_hacks_judged_submission", + "tableTo": "knight_hacks_judges", + "columnsFrom": ["judge_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_judges": { + "name": "knight_hacks_judges", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "room_name": { + "name": "room_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "challenge_id": { + "name": "challenge_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_judges_challenge_id_knight_hacks_challenges_id_fk": { + "name": "knight_hacks_judges_challenge_id_knight_hacks_challenges_id_fk", + "tableFrom": "knight_hacks_judges", + "tableTo": "knight_hacks_challenges", + "columnsFrom": ["challenge_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_member": { + "name": "knight_hacks_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "first_name": { + "name": "first_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "last_name": { + "name": "last_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "discord_user": { + "name": "discord_user", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "age": { + "name": "age", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "phone_number": { + "name": "phone_number", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "school": { + "name": "school", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "level_of_study": { + "name": "level_of_study", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "major": { + "name": "major", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'Computer Science'" + }, + "gender": { + "name": "gender", + "type": "gender", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'Prefer not to answer'" + }, + "race_or_ethnicity": { + "name": "race_or_ethnicity", + "type": "race_or_ethnicity", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'Prefer not to answer'" + }, + "guild_profile_visible": { + "name": "guild_profile_visible", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "tagline": { + "name": "tagline", + "type": "varchar(80)", + "primaryKey": false, + "notNull": false + }, + "about": { + "name": "about", + "type": "varchar(500)", + "primaryKey": false, + "notNull": false + }, + "profile_picture_url": { + "name": "profile_picture_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "shirt_size": { + "name": "shirt_size", + "type": "shirt_size", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "github_profile_url": { + "name": "github_profile_url", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "linkedin_profile_url": { + "name": "linkedin_profile_url", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "website_url": { + "name": "website_url", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "resume_url": { + "name": "resume_url", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "dob": { + "name": "dob", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "grad_date": { + "name": "grad_date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "company": { + "name": "company", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "points": { + "name": "points", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "date_created": { + "name": "date_created", + "type": "date", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_created": { + "name": "time_created", + "type": "time", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_member_user_id_auth_user_id_fk": { + "name": "knight_hacks_member_user_id_auth_user_id_fk", + "tableFrom": "knight_hacks_member", + "tableTo": "auth_user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "knight_hacks_member_email_unique": { + "name": "knight_hacks_member_email_unique", + "nullsNotDistinct": false, + "columns": ["email"] + }, + "knight_hacks_member_phoneNumber_unique": { + "name": "knight_hacks_member_phoneNumber_unique", + "nullsNotDistinct": false, + "columns": ["phone_number"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_companies": { + "name": "knight_hacks_companies", + "schema": "", + "columns": { + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_sponsor": { + "name": "knight_hacks_sponsor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "logo_url": { + "name": "logo_url", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "website_url": { + "name": "website_url", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_submissions": { + "name": "knight_hacks_submissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "challenge_id": { + "name": "challenge_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "hackathon_id": { + "name": "hackathon_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_submissions_challenge_id_knight_hacks_challenges_id_fk": { + "name": "knight_hacks_submissions_challenge_id_knight_hacks_challenges_id_fk", + "tableFrom": "knight_hacks_submissions", + "tableTo": "knight_hacks_challenges", + "columnsFrom": ["challenge_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knight_hacks_submissions_team_id_knight_hacks_teams_id_fk": { + "name": "knight_hacks_submissions_team_id_knight_hacks_teams_id_fk", + "tableFrom": "knight_hacks_submissions", + "tableTo": "knight_hacks_teams", + "columnsFrom": ["team_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knight_hacks_submissions_hackathon_id_knight_hacks_hackathon_id_fk": { + "name": "knight_hacks_submissions_hackathon_id_knight_hacks_hackathon_id_fk", + "tableFrom": "knight_hacks_submissions", + "tableTo": "knight_hacks_hackathon", + "columnsFrom": ["hackathon_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "knight_hacks_submissions_teamId_challengeId_unique": { + "name": "knight_hacks_submissions_teamId_challengeId_unique", + "nullsNotDistinct": false, + "columns": ["team_id", "challenge_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_teams": { + "name": "knight_hacks_teams", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "hackathon_id": { + "name": "hackathon_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_title": { + "name": "project_title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "submission_url": { + "name": "submission_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "project_created_at": { + "name": "project_created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "is_project_submitted": { + "name": "is_project_submitted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "devpost_url": { + "name": "devpost_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "universities": { + "name": "universities", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "emails": { + "name": "emails", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "match_key": { + "name": "match_key", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_teams_hackathon_id_knight_hacks_hackathon_id_fk": { + "name": "knight_hacks_teams_hackathon_id_knight_hacks_hackathon_id_fk", + "tableFrom": "knight_hacks_teams", + "tableTo": "knight_hacks_hackathon", + "columnsFrom": ["hackathon_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "knight_hacks_teams_matchKey_unique": { + "name": "knight_hacks_teams_matchKey_unique", + "nullsNotDistinct": false, + "columns": ["match_key"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_template": { + "name": "knight_hacks_template", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_trpc_form_connection": { + "name": "knight_hacks_trpc_form_connection", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "form": { + "name": "form", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "proc": { + "name": "proc", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "connections": { + "name": "connections", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_trpc_form_connection_form_knight_hacks_form_schemas_id_fk": { + "name": "knight_hacks_trpc_form_connection_form_knight_hacks_form_schemas_id_fk", + "tableFrom": "knight_hacks_trpc_form_connection", + "tableTo": "knight_hacks_form_schemas", + "columnsFrom": ["form"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.event_tag": { + "name": "event_tag", + "schema": "public", + "values": [ + "GBM", + "Social", + "Kickstart", + "Project Launch", + "Hello World", + "Sponsorship", + "Tech Exploration", + "Class Support", + "Workshop", + "OPS", + "Collabs", + "Check-in", + "Merch", + "Food", + "Ceremony", + "CAREER-FAIR", + "RSO-FAIR" + ] + }, + "public.gender": { + "name": "gender", + "schema": "public", + "values": [ + "Man", + "Woman", + "Non-binary", + "Prefer to self-describe", + "Prefer not to answer" + ] + }, + "public.hackathon_application_state": { + "name": "hackathon_application_state", + "schema": "public", + "values": [ + "withdrawn", + "pending", + "accepted", + "waitlisted", + "checkedin", + "confirmed", + "denied" + ] + }, + "public.issue_priority": { + "name": "issue_priority", + "schema": "public", + "values": ["LOWEST", "LOW", "MEDIUM", "HIGH", "HIGHEST"] + }, + "public.issue_status": { + "name": "issue_status", + "schema": "public", + "values": ["BACKLOG", "PLANNING", "IN_PROGRESS", "FINISHED"] + }, + "public.race_or_ethnicity": { + "name": "race_or_ethnicity", + "schema": "public", + "values": [ + "White", + "Black or African American", + "Hispanic / Latino / Spanish Origin", + "Asian", + "Native Hawaiian or Other Pacific Islander", + "Native American or Alaskan Native", + "Middle Eastern", + "Prefer not to answer", + "Other" + ] + }, + "public.shirt_size": { + "name": "shirt_size", + "schema": "public", + "values": ["XS", "S", "M", "L", "XL", "2XL", "3XL"] + }, + "public.sponsor_tier": { + "name": "sponsor_tier", + "schema": "public", + "values": ["gold", "silver", "bronze", "other"] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/packages/db/drizzle/meta/_journal.json b/packages/db/drizzle/meta/_journal.json index eee7605d2..ea17bf096 100644 --- a/packages/db/drizzle/meta/_journal.json +++ b/packages/db/drizzle/meta/_journal.json @@ -22,6 +22,13 @@ "when": 1776104121304, "tag": "0002_dapper_forge", "breakpoints": true + }, + { + "idx": 3, + "version": "7", + "when": 1776108776095, + "tag": "0003_hot_killraven", + "breakpoints": true } ] } diff --git a/packages/db/src/schemas/auth.ts b/packages/db/src/schemas/auth.ts index 9d87700af..b1f9464ac 100644 --- a/packages/db/src/schemas/auth.ts +++ b/packages/db/src/schemas/auth.ts @@ -43,6 +43,7 @@ export const Roles = createTable("roles", (t) => ({ .varchar({ length: 32 }) .notNull() .default(ISSUE.DEFAULT_ISSUE_REMINDER_CHANNEL_ID), + teamHexcodeColor: t.varchar({ length: 7 }), })); export const InsertRolesSchema = createInsertSchema(Roles); From 26c6f0b6bed55dea967bd2edfdfb6dc3d34737b1 Mon Sep 17 00:00:00 2001 From: Dylan Vidal Date: Mon, 13 Apr 2026 15:37:31 -0400 Subject: [PATCH 11/21] grrr format --- apps/blade/src/app/_components/issues/issue-fetcher-pane.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/blade/src/app/_components/issues/issue-fetcher-pane.tsx b/apps/blade/src/app/_components/issues/issue-fetcher-pane.tsx index 26c4175dd..33d701e31 100644 --- a/apps/blade/src/app/_components/issues/issue-fetcher-pane.tsx +++ b/apps/blade/src/app/_components/issues/issue-fetcher-pane.tsx @@ -189,9 +189,7 @@ export function IssueFetcherPane(props: IssueFetcherPaneProps) { issues: isReady ? issues : [], blockedParentIds: isReady ? blockedParentIds : new Set(), roleNameById: isReady ? roleNameById : new Map(), - roleColorById: isReady - ? roleColorById - : new Map(), + roleColorById: isReady ? roleColorById : new Map(), isLoading: combinedIsLoading, error: combinedErrorMessage, refresh, From 3d8334df3effc78a9dbab9733c70c6ee6863e299 Mon Sep 17 00:00:00 2001 From: Dylan Vidal Date: Mon, 13 Apr 2026 15:39:09 -0400 Subject: [PATCH 12/21] or permission --- apps/blade/src/app/admin/issues/list/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/blade/src/app/admin/issues/list/page.tsx b/apps/blade/src/app/admin/issues/list/page.tsx index c5ee1dd94..d04c6969c 100644 --- a/apps/blade/src/app/admin/issues/list/page.tsx +++ b/apps/blade/src/app/admin/issues/list/page.tsx @@ -11,7 +11,7 @@ export default async function AdminIssuesListPage() { if (!session) redirect(SIGN_IN_PATH); const hasAccess = await api.roles.hasPermission({ - and: [ + or: [ "READ_ISSUES", "EDIT_ISSUES", "EDIT_ISSUE_TEMPLATES", From e77b4907ebf974e5f635f1307065aec496bce2d2 Mon Sep 17 00:00:00 2001 From: Dylan Vidal Date: Mon, 13 Apr 2026 15:41:28 -0400 Subject: [PATCH 13/21] move /issues/id to /admin/issues/id --- .../issue-calendar/calendar-day-agenda.tsx | 2 +- .../issue-calendar/calendar-issue-dialog.tsx | 4 +- .../issue-kanban/issues-kanban.tsx | 2 +- .../_components/issue-list/issues-list.tsx | 2 +- apps/blade/src/app/admin/issues/[id]/page.tsx | 128 ++++++++++++++++++ apps/blade/src/app/issues/[id]/page.tsx | 110 +-------------- apps/cron/src/crons/issue-reminders.ts | 2 +- 7 files changed, 136 insertions(+), 114 deletions(-) create mode 100644 apps/blade/src/app/admin/issues/[id]/page.tsx diff --git a/apps/blade/src/app/_components/issue-calendar/calendar-day-agenda.tsx b/apps/blade/src/app/_components/issue-calendar/calendar-day-agenda.tsx index 869e7a0b2..0641fa979 100644 --- a/apps/blade/src/app/_components/issue-calendar/calendar-day-agenda.tsx +++ b/apps/blade/src/app/_components/issue-calendar/calendar-day-agenda.tsx @@ -103,7 +103,7 @@ export function IssueDayAgenda(props: { const copyIssueLink = useCallback((issueId: string) => { const origin = typeof window !== "undefined" ? window.location.origin : ""; - const url = `${origin}/issues/${issueId}`; + const url = `${origin}/admin/issues/${issueId}`; void navigator.clipboard.writeText(url).then( () => { toast.success("Issue link copied"); diff --git a/apps/blade/src/app/_components/issue-calendar/calendar-issue-dialog.tsx b/apps/blade/src/app/_components/issue-calendar/calendar-issue-dialog.tsx index 8108921af..6c78ac53d 100644 --- a/apps/blade/src/app/_components/issue-calendar/calendar-issue-dialog.tsx +++ b/apps/blade/src/app/_components/issue-calendar/calendar-issue-dialog.tsx @@ -113,7 +113,7 @@ export function CalendarIssueDialog({ async function handleCopyIssueUrl() { if (!issue || typeof window === "undefined") return; - const url = `${window.location.origin}/issues/${issue.id}`; + const url = `${window.location.origin}/admin/issues/${issue.id}`; try { await navigator.clipboard.writeText(url); toast.success("Issue link copied"); @@ -146,7 +146,7 @@ export function CalendarIssueDialog({ asChild > {issue.name} diff --git a/apps/blade/src/app/_components/issue-kanban/issues-kanban.tsx b/apps/blade/src/app/_components/issue-kanban/issues-kanban.tsx index 1bb47930e..8b4f963a5 100644 --- a/apps/blade/src/app/_components/issue-kanban/issues-kanban.tsx +++ b/apps/blade/src/app/_components/issue-kanban/issues-kanban.tsx @@ -179,7 +179,7 @@ export function KanbanBoard() {
    {issue.name} diff --git a/apps/blade/src/app/_components/issue-list/issues-list.tsx b/apps/blade/src/app/_components/issue-list/issues-list.tsx index 6b79f2b66..5a649cb84 100644 --- a/apps/blade/src/app/_components/issue-list/issues-list.tsx +++ b/apps/blade/src/app/_components/issue-list/issues-list.tsx @@ -241,7 +241,7 @@ export function IssuesList() { >
    {issue.name} diff --git a/apps/blade/src/app/admin/issues/[id]/page.tsx b/apps/blade/src/app/admin/issues/[id]/page.tsx new file mode 100644 index 000000000..c2480acb4 --- /dev/null +++ b/apps/blade/src/app/admin/issues/[id]/page.tsx @@ -0,0 +1,128 @@ +import { notFound, redirect } from "next/navigation"; +import { z } from "zod"; + +import { auth } from "@forge/auth"; + +import { SIGN_IN_PATH } from "~/consts"; +import { api } from "~/trpc/server"; + +interface AdminIssuePageProps { + params: Promise<{ + id: string; + }>; +} + +export default async function AdminIssuePage({ + params, +}: AdminIssuePageProps) { + const session = await auth(); + if (!session) redirect(SIGN_IN_PATH); + + const hasAccess = await api.roles.hasPermission({ or: ["READ_ISSUES"] }); + if (!hasAccess) notFound(); + + const { id } = await params; + if (!z.string().uuid().safeParse(id).success) notFound(); + + let issue; + try { + issue = await api.issues.getIssue({ id }); + } catch { + notFound(); + } + + return ( +
    +
    +

    Issue

    +

    {issue.name}

    +
    + +
    +
    +

    Status

    +

    {issue.status}

    +
    + +
    +

    Due Date

    +

    + {issue.date + ? new Date(issue.date).toLocaleDateString() + : "No due date"} +

    +
    +
    + +
    +

    Team

    +

    {issue.team.name}

    +
    + +
    +

    Description

    +

    + {issue.description} +

    +
    + +
    +
    +

    Assignees

    +
    + {issue.userAssignments.length > 0 ? ( +
      + {issue.userAssignments.map((assignment) => ( +
    • + {assignment.user.name ?? assignment.user.discordUserId} +
    • + ))} +
    + ) : ( +

    Unassigned

    + )} +
    +
    + +
    +

    Visible Teams

    +
    + {issue.teamVisibility.length > 0 ? ( +
      + {issue.teamVisibility.map((visibility) => ( +
    • {visibility.team.name}
    • + ))} +
    + ) : ( +

    No team visibility rules

    + )} +
    +
    +
    + +
    +

    Links

    +
    + {issue.links && issue.links.length > 0 ? ( + + ) : ( +

    No links

    + )} +
    +
    +
    + ); +} diff --git a/apps/blade/src/app/issues/[id]/page.tsx b/apps/blade/src/app/issues/[id]/page.tsx index 3fe716184..b751e0aca 100644 --- a/apps/blade/src/app/issues/[id]/page.tsx +++ b/apps/blade/src/app/issues/[id]/page.tsx @@ -1,10 +1,8 @@ -import { notFound, redirect } from "next/navigation"; -import { z } from "zod"; +import { redirect } from "next/navigation"; import { auth } from "@forge/auth"; import { SIGN_IN_PATH } from "~/consts"; -import { api } from "~/trpc/server"; interface IssuePageProps { params: Promise<{ @@ -16,110 +14,6 @@ export default async function IssuePage({ params }: IssuePageProps) { const session = await auth(); if (!session) redirect(SIGN_IN_PATH); - const hasAccess = await api.roles.hasPermission({ or: ["READ_ISSUES"] }); - if (!hasAccess) notFound(); - const { id } = await params; - if (!z.string().uuid().safeParse(id).success) notFound(); - let issue; - try { - issue = await api.issues.getIssue({ id }); - } catch { - notFound(); - } - - return ( -
    -
    -

    Issue

    -

    {issue.name}

    -
    - -
    -
    -

    Status

    -

    {issue.status}

    -
    - -
    -

    Due Date

    -

    - {issue.date - ? new Date(issue.date).toLocaleDateString() - : "No due date"} -

    -
    -
    - -
    -

    Team

    -

    {issue.team.name}

    -
    - -
    -

    Description

    -

    - {issue.description} -

    -
    - -
    -
    -

    Assignees

    -
    - {issue.userAssignments.length > 0 ? ( -
      - {issue.userAssignments.map((assignment) => ( -
    • - {assignment.user.name ?? assignment.user.discordUserId} -
    • - ))} -
    - ) : ( -

    Unassigned

    - )} -
    -
    - -
    -

    Visible Teams

    -
    - {issue.teamVisibility.length > 0 ? ( -
      - {issue.teamVisibility.map((visibility) => ( -
    • {visibility.team.name}
    • - ))} -
    - ) : ( -

    No team visibility rules

    - )} -
    -
    -
    - -
    -

    Links

    -
    - {issue.links && issue.links.length > 0 ? ( - - ) : ( -

    No links

    - )} -
    -
    -
    - ); + redirect(`/admin/issues/${id}`); } diff --git a/apps/cron/src/crons/issue-reminders.ts b/apps/cron/src/crons/issue-reminders.ts index 7f10c2347..b8165c999 100644 --- a/apps/cron/src/crons/issue-reminders.ts +++ b/apps/cron/src/crons/issue-reminders.ts @@ -181,7 +181,7 @@ const truncateReminderLine = (line: string, maxLength: number): string => { }; const getIssueUrl = (issueId: string): string => { - return `${env.BLADE_URL.replace(/\/$/, "")}/issues/${issueId}`; + return `${env.BLADE_URL.replace(/\/$/, "")}/admin/issues/${issueId}`; }; const getAllowedMentions = ( From bef87c68fbdac27cc5c78902439b531019b787c3 Mon Sep 17 00:00:00 2001 From: Dylan Vidal Date: Mon, 13 Apr 2026 15:41:43 -0400 Subject: [PATCH 14/21] cleanup --- apps/blade/src/app/issues/[id]/page.tsx | 19 ----------- apps/blade/src/app/issues/calendar/page.tsx | 38 --------------------- apps/blade/src/app/issues/kanban/page.tsx | 32 ----------------- apps/blade/src/app/issues/list/page.tsx | 32 ----------------- 4 files changed, 121 deletions(-) delete mode 100644 apps/blade/src/app/issues/[id]/page.tsx delete mode 100644 apps/blade/src/app/issues/calendar/page.tsx delete mode 100644 apps/blade/src/app/issues/kanban/page.tsx delete mode 100644 apps/blade/src/app/issues/list/page.tsx diff --git a/apps/blade/src/app/issues/[id]/page.tsx b/apps/blade/src/app/issues/[id]/page.tsx deleted file mode 100644 index b751e0aca..000000000 --- a/apps/blade/src/app/issues/[id]/page.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { redirect } from "next/navigation"; - -import { auth } from "@forge/auth"; - -import { SIGN_IN_PATH } from "~/consts"; - -interface IssuePageProps { - params: Promise<{ - id: string; - }>; -} - -export default async function IssuePage({ params }: IssuePageProps) { - const session = await auth(); - if (!session) redirect(SIGN_IN_PATH); - - const { id } = await params; - redirect(`/admin/issues/${id}`); -} diff --git a/apps/blade/src/app/issues/calendar/page.tsx b/apps/blade/src/app/issues/calendar/page.tsx deleted file mode 100644 index bd35f7b61..000000000 --- a/apps/blade/src/app/issues/calendar/page.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { notFound, redirect } from "next/navigation"; - -import { auth } from "@forge/auth"; - -import Calendar from "~/app/_components/issue-calendar/calendar"; -import { SessionNavbar } from "~/app/_components/navigation/session-navbar"; -import { SIGN_IN_PATH } from "~/consts"; -import { api, HydrateClient } from "~/trpc/server"; - -export default async function Events() { - const session = await auth(); - if (!session) { - redirect(SIGN_IN_PATH); - } - - const hasAccess = await api.roles.hasPermission({ - and: [ - "READ_ISSUES", - "EDIT_ISSUES", - "EDIT_ISSUE_TEMPLATES", - "READ_ISSUE_TEMPLATES", - ], - }); - if (!hasAccess) notFound(); - - return ( - -
    -
    - -
    -
    - -
    -
    -
    - ); -} diff --git a/apps/blade/src/app/issues/kanban/page.tsx b/apps/blade/src/app/issues/kanban/page.tsx deleted file mode 100644 index 72aaaa270..000000000 --- a/apps/blade/src/app/issues/kanban/page.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { notFound, redirect } from "next/navigation"; - -import { auth } from "@forge/auth"; - -import { KanbanBoard } from "~/app/_components/issue-kanban/issues-kanban"; -import { SessionNavbar } from "~/app/_components/navigation/session-navbar"; -import { SIGN_IN_PATH } from "~/consts"; -import { api, HydrateClient } from "~/trpc/server"; - -export default async function KanbanPage() { - const session = await auth(); - if (!session) redirect(SIGN_IN_PATH); - - const hasAccess = await api.roles.hasPermission({ - and: [ - "READ_ISSUES", - "EDIT_ISSUES", - "EDIT_ISSUE_TEMPLATES", - "READ_ISSUE_TEMPLATES", - ], - }); - if (!hasAccess) notFound(); - - return ( - - -
    - -
    -
    - ); -} diff --git a/apps/blade/src/app/issues/list/page.tsx b/apps/blade/src/app/issues/list/page.tsx deleted file mode 100644 index dade766ab..000000000 --- a/apps/blade/src/app/issues/list/page.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { notFound, redirect } from "next/navigation"; - -import { auth } from "@forge/auth"; - -import { IssuesList } from "~/app/_components/issue-list/issues-list"; -import { SessionNavbar } from "~/app/_components/navigation/session-navbar"; -import { SIGN_IN_PATH } from "~/consts"; -import { api, HydrateClient } from "~/trpc/server"; - -export default async function IssueListPage() { - const session = await auth(); - if (!session) redirect(SIGN_IN_PATH); - - const hasAccess = await api.roles.hasPermission({ - and: [ - "READ_ISSUES", - "EDIT_ISSUES", - "EDIT_ISSUE_TEMPLATES", - "READ_ISSUE_TEMPLATES", - ], - }); - if (!hasAccess) notFound(); - - return ( - - -
    - -
    -
    - ); -} From 718a20b404bcca87509ba1c558f5232410df9e8e Mon Sep 17 00:00:00 2001 From: Dylan Vidal Date: Mon, 13 Apr 2026 15:44:12 -0400 Subject: [PATCH 15/21] format AGAIN bruh --- apps/blade/src/app/admin/issues/[id]/page.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/blade/src/app/admin/issues/[id]/page.tsx b/apps/blade/src/app/admin/issues/[id]/page.tsx index c2480acb4..dd45b346d 100644 --- a/apps/blade/src/app/admin/issues/[id]/page.tsx +++ b/apps/blade/src/app/admin/issues/[id]/page.tsx @@ -12,9 +12,7 @@ interface AdminIssuePageProps { }>; } -export default async function AdminIssuePage({ - params, -}: AdminIssuePageProps) { +export default async function AdminIssuePage({ params }: AdminIssuePageProps) { const session = await auth(); if (!session) redirect(SIGN_IN_PATH); From 8b13723541bf1ed107ec0c3c00da9a3a2911a3bc Mon Sep 17 00:00:00 2001 From: Dylan Vidal Date: Mon, 13 Apr 2026 15:55:34 -0400 Subject: [PATCH 16/21] visibility fix --- .../_components/issues/create-edit-dialog.tsx | 20 ++- .../src/app/_components/issues/issue-node.tsx | 4 +- packages/api/src/routers/issues.ts | 126 ++++++++++-------- 3 files changed, 86 insertions(+), 64 deletions(-) diff --git a/apps/blade/src/app/_components/issues/create-edit-dialog.tsx b/apps/blade/src/app/_components/issues/create-edit-dialog.tsx index e7eb37e6c..431b98008 100644 --- a/apps/blade/src/app/_components/issues/create-edit-dialog.tsx +++ b/apps/blade/src/app/_components/issues/create-edit-dialog.tsx @@ -137,6 +137,11 @@ type CreateEditDialogComponentProps = Omit< const defaultForm = (): ISSUE.IssueEditNode => newIssueNode(); +function ensureTeamVisible(teamId: string, roleIds: string[] | undefined) { + if (!teamId) return roleIds ?? []; + return Array.from(new Set([...(roleIds ?? []), teamId])); +} + function templateToIssueNodes( items: ISSUE.IssueTemplate[], parentDate: Date, @@ -301,8 +306,12 @@ export function CreateEditDialog(props: CreateEditDialogComponentProps) { [rolesData], ); const safeVisibilityIds = useMemo( - () => formValues.roles.filter((roleId) => roleIdSet.has(roleId)), - [formValues.roles, roleIdSet], + () => + ensureTeamVisible( + effectiveTeam, + formValues.roles.filter((roleId) => roleIdSet.has(roleId)), + ), + [effectiveTeam, formValues.roles, roleIdSet], ); const safeEventVisibilityIds = useMemo( () => @@ -447,7 +456,10 @@ export function CreateEditDialog(props: CreateEditDialogComponentProps) { priority: node.priority, team: node.team, event: eventId, - teamVisibilityIds: node.roles.length > 0 ? node.roles : undefined, + teamVisibilityIds: + ensureTeamVisible(node.team, node.roles).length > 0 + ? ensureTeamVisible(node.team, node.roles) + : undefined, assigneeIds: node.assigneeIds, children: node.children.length > 0 @@ -872,7 +884,7 @@ export function CreateEditDialog(props: CreateEditDialogComponentProps) { updateForm("roles", ids)} description="Teams who can see and manage the issue" /> diff --git a/apps/blade/src/app/_components/issues/issue-node.tsx b/apps/blade/src/app/_components/issues/issue-node.tsx index 13c6bedbd..8b8aee142 100644 --- a/apps/blade/src/app/_components/issues/issue-node.tsx +++ b/apps/blade/src/app/_components/issues/issue-node.tsx @@ -283,7 +283,9 @@ export function IssueNode({ update("roles", ids)} description="Teams who can see and manage this sub-issue" /> diff --git a/packages/api/src/routers/issues.ts b/packages/api/src/routers/issues.ts index 6755dc102..44b3d4c07 100644 --- a/packages/api/src/routers/issues.ts +++ b/packages/api/src/routers/issues.ts @@ -3,7 +3,7 @@ import { TRPCError } from "@trpc/server"; import { z } from "zod"; import { ISSUE } from "@forge/consts"; -import { and, eq, exists, inArray, sql } from "@forge/db"; +import { and, eq, exists, inArray, or, sql } from "@forge/db"; import { db } from "@forge/db/client"; import { Permissions, User } from "@forge/db/schemas/auth"; import { @@ -36,6 +36,10 @@ type IssueCreateNode = z.infer & { children?: IssueCreateNode[]; }; +function ensureTeamVisible(teamId: string, teamVisibilityIds?: string[]) { + return Array.from(new Set([...(teamVisibilityIds ?? []), teamId])); +} + const issueCreateSchema: z.ZodType = baseIssueCreateSchema.extend({ children: z.lazy(() => z.array(issueCreateSchema)).optional(), @@ -64,9 +68,14 @@ async function createChildIssues( message: "Failed to create sub-issue.", }); } - if (teamVisibilityIds?.length) { + const ensuredTeamVisibilityIds = ensureTeamVisible( + rest.team, + teamVisibilityIds, + ); + + if (ensuredTeamVisibilityIds.length > 0) { await tx.insert(IssuesToTeamsVisibility).values( - teamVisibilityIds.map((teamId) => ({ + ensuredTeamVisibilityIds.map((teamId) => ({ issueId: created.id, teamId, })), @@ -117,6 +126,36 @@ async function collectIssueSubtreeIds( return allIds; } +async function issueVisibilityFilterForUser( + userId: string, + isOfficer: boolean, +) { + if (isOfficer) { + return sql`TRUE`; + } + + const userRoles = ( + await db.query.Permissions.findMany({ + where: eq(Permissions.userId, userId), + }) + ).map((permission) => permission.roleId); + + if (userRoles.length === 0) { + return sql`FALSE`; + } + + const visibleIssueIds = await db + .select({ issueId: IssuesToTeamsVisibility.issueId }) + .from(IssuesToTeamsVisibility) + .where(inArray(IssuesToTeamsVisibility.teamId, userRoles)); + + const visibilityIssueIds = visibleIssueIds.map((row) => row.issueId); + + return visibilityIssueIds.length > 0 + ? or(inArray(Issue.team, userRoles), inArray(Issue.id, visibilityIssueIds)) + : inArray(Issue.team, userRoles); +} + const baseIssueTemplateSchema = z.object({ title: z.string().min(1, "Title is required"), description: z.string().optional(), @@ -188,6 +227,10 @@ export const issuesRouter = { return await db.transaction(async (tx) => { const { teamVisibilityIds, assigneeIds, children, ...rest } = input; + const ensuredTeamVisibilityIds = ensureTeamVisible( + input.team, + teamVisibilityIds, + ); await permissionsServer.validateAssigneesBelongToTeam( tx, @@ -213,9 +256,9 @@ export const issuesRouter = { }); } - if (teamVisibilityIds?.length) { + if (ensuredTeamVisibilityIds.length > 0) { await tx.insert(IssuesToTeamsVisibility).values( - teamVisibilityIds.map((teamId) => ({ + ensuredTeamVisibilityIds.map((teamId) => ({ issueId: issue.id, teamId, })), @@ -248,31 +291,10 @@ export const issuesRouter = { .query(async ({ ctx, input }) => { permissions.controlPerms.or(["READ_ISSUES"], ctx); - let visibilityFilter; - - if (ctx.session.permissions.IS_OFFICER) { - visibilityFilter = sql`TRUE`; - } else { - const userRoles = ( - await db.query.Permissions.findMany({ - where: eq(Permissions.userId, ctx.session.user.id), - }) - ).map((p) => p.roleId); - visibilityFilter = - userRoles.length === 0 - ? sql`FALSE` - : exists( - db - .select() - .from(IssuesToTeamsVisibility) - .where( - and( - eq(IssuesToTeamsVisibility.issueId, Issue.id), - inArray(IssuesToTeamsVisibility.teamId, userRoles), - ), - ), - ); - } + const visibilityFilter = await issueVisibilityFilterForUser( + ctx.session.user.id, + Boolean(ctx.session.permissions.IS_OFFICER), + ); const issue = await db.query.Issue.findFirst({ where: and(eq(Issue.id, input.id), visibilityFilter), with: { @@ -319,31 +341,10 @@ export const issuesRouter = { ); } - let visibilityFilter; - - if (ctx.session.permissions.IS_OFFICER) { - visibilityFilter = sql`TRUE`; - } else { - const userRoles = ( - await db.query.Permissions.findMany({ - where: eq(Permissions.userId, ctx.session.user.id), - }) - ).map((p) => p.roleId); - visibilityFilter = - userRoles.length === 0 - ? sql`FALSE` - : exists( - db - .select() - .from(IssuesToTeamsVisibility) - .where( - and( - eq(IssuesToTeamsVisibility.issueId, Issue.id), - inArray(IssuesToTeamsVisibility.teamId, userRoles), - ), - ), - ); - } + const visibilityFilter = await issueVisibilityFilterForUser( + ctx.session.user.id, + Boolean(ctx.session.permissions.IS_OFFICER), + ); if (input?.assigneeIds?.length) { filters.push( @@ -422,6 +423,10 @@ export const issuesRouter = { ); const { id, assigneeIds, teamVisibilityIds, ...fields } = input; + const ensuredTeamVisibilityIds = + teamVisibilityIds !== undefined + ? ensureTeamVisible(assignmentTeamId, teamVisibilityIds) + : undefined; const updateData = Object.fromEntries( (Object.entries(fields) as [string, unknown][]).filter( ([, v]) => v !== undefined, @@ -432,15 +437,18 @@ export const issuesRouter = { await tx.update(Issue).set(updateData).where(eq(Issue.id, id)); } - if (teamVisibilityIds !== undefined) { + if (ensuredTeamVisibilityIds !== undefined) { await tx .delete(IssuesToTeamsVisibility) .where(eq(IssuesToTeamsVisibility.issueId, id)); - if (teamVisibilityIds.length > 0) { + if (ensuredTeamVisibilityIds.length > 0) { await tx .insert(IssuesToTeamsVisibility) .values( - teamVisibilityIds.map((teamId) => ({ issueId: id, teamId })), + ensuredTeamVisibilityIds.map((teamId) => ({ + issueId: id, + teamId, + })), ); } } @@ -458,7 +466,7 @@ export const issuesRouter = { if ( Object.keys(updateData).length === 0 && - (teamVisibilityIds !== undefined || assigneeIds !== undefined) + (ensuredTeamVisibilityIds !== undefined || assigneeIds !== undefined) ) { await tx .update(Issue) From 35916814f35d9a6977614a2f00d7ad6ed2c05548 Mon Sep 17 00:00:00 2001 From: Dylan Vidal Date: Mon, 13 Apr 2026 15:58:46 -0400 Subject: [PATCH 17/21] format --- packages/api/src/routers/issues.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/api/src/routers/issues.ts b/packages/api/src/routers/issues.ts index 44b3d4c07..0cce1a27b 100644 --- a/packages/api/src/routers/issues.ts +++ b/packages/api/src/routers/issues.ts @@ -442,14 +442,12 @@ export const issuesRouter = { .delete(IssuesToTeamsVisibility) .where(eq(IssuesToTeamsVisibility.issueId, id)); if (ensuredTeamVisibilityIds.length > 0) { - await tx - .insert(IssuesToTeamsVisibility) - .values( - ensuredTeamVisibilityIds.map((teamId) => ({ - issueId: id, - teamId, - })), - ); + await tx.insert(IssuesToTeamsVisibility).values( + ensuredTeamVisibilityIds.map((teamId) => ({ + issueId: id, + teamId, + })), + ); } } From b791de1bcb78051962907b3d03550c4995b582b4 Mon Sep 17 00:00:00 2001 From: Dylan Vidal Date: Tue, 14 Apr 2026 11:30:04 -0400 Subject: [PATCH 18/21] update reminder formatting --- apps/cron/src/crons/issue-reminders.ts | 99 ++++++++++++++++++-------- 1 file changed, 68 insertions(+), 31 deletions(-) diff --git a/apps/cron/src/crons/issue-reminders.ts b/apps/cron/src/crons/issue-reminders.ts index b8165c999..8f3305730 100644 --- a/apps/cron/src/crons/issue-reminders.ts +++ b/apps/cron/src/crons/issue-reminders.ts @@ -20,10 +20,10 @@ const ISSUE_REMINDER_DAYS = { } as const; const ISSUE_REMINDER_DAY_LABELS: Record = { - Fourteen: "Due in 14 days", - Seven: "Due in 7 days", - Three: "Due in 3 days", - One: "Due in 1 day", + Fourteen: "14 days", + Seven: "7 days", + Three: "3 days", + One: "1 day", Overdue: "Overdue", }; @@ -41,6 +41,9 @@ type IssueReminderDay = interface IssueReminderTarget { issueId: string; issueName: string; + issuePriority: (typeof ISSUE.PRIORITY)[number]; + issueUpdatedAt: Date; + issueDueDate: Date; teamId: string; teamDiscordRoleId: string; assigneeDiscordUserIds: string[]; @@ -100,6 +103,8 @@ const getIssueReminderDiffDays = (date: Date, now = new Date()): number => { const buildIssueReminderTarget = (issue: { id: string; name: string; + priority: (typeof ISSUE.PRIORITY)[number]; + updatedAt: Date; team: string; date: Date | null; teamDiscordRoleId: string; @@ -113,6 +118,9 @@ const buildIssueReminderTarget = (issue: { return { issueId: issue.id, issueName: issue.name, + issuePriority: issue.priority, + issueUpdatedAt: issue.updatedAt, + issueDueDate: issue.date, teamId: issue.team, teamDiscordRoleId: issue.teamDiscordRoleId, assigneeDiscordUserIds: issue.assigneeDiscordUserIds, @@ -163,15 +171,54 @@ const sanitizeIssueReminderTitle = (title: string): string => { .trim(); }; +const formatIssueReminderDate = (date: Date): string => { + return new Intl.DateTimeFormat("en-US", { + timeZone: ISSUE_REMINDER_TIMEZONE, + year: "numeric", + month: "2-digit", + day: "2-digit", + }).format(date); +}; + +const formatIssueReminderDateTime = (date: Date): string => { + return new Intl.DateTimeFormat("en-US", { + timeZone: ISSUE_REMINDER_TIMEZONE, + year: "numeric", + month: "2-digit", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + }).format(date); +}; + +const ISSUE_PRIORITY_RANK: Record<(typeof ISSUE.PRIORITY)[number], number> = + ISSUE.PRIORITY.reduce( + (acc, priority, index) => ({ + ...acc, + [priority]: index, + }), + {} as Record<(typeof ISSUE.PRIORITY)[number], number>, + ); + +const sortIssueReminderTargets = ( + targets: IssueReminderTarget[], +): IssueReminderTarget[] => { + return [...targets].sort((a, b) => { + const priorityDiff = + ISSUE_PRIORITY_RANK[b.issuePriority] - ISSUE_PRIORITY_RANK[a.issuePriority]; + if (priorityDiff !== 0) return priorityDiff; + return a.issueName.localeCompare(b.issueName, undefined, { + sensitivity: "base", + }); + }); +}; + const formatIssueReminder = (target: IssueReminderTarget): string => { - const mentions = getIssueMentionTargets(target).join(", "); + const mentionTargets = getIssueMentionTargets(target); + const assigneeOrTeam = mentionTargets.join(", "); const issueUrl = getIssueUrl(target.issueId); const issueTitle = sanitizeIssueReminderTitle(target.issueName); - const overdueSuffix = - target.day === ISSUE_REMINDER_DAYS.Overdue && target.overdueDays !== null - ? ` (${target.overdueDays} days)` - : ""; - return `[${issueTitle}](<${issueUrl}>)${overdueSuffix} ${mentions}`; + return `[${issueTitle}](<${issueUrl}>) | ${assigneeOrTeam} | ${target.issuePriority} | Updated: ${formatIssueReminderDateTime(target.issueUpdatedAt)} | Due: ${formatIssueReminderDateTime(target.issueDueDate)}`; }; const truncateReminderLine = (line: string, maxLength: number): string => { @@ -206,31 +253,25 @@ const getAllowedMentions = ( }; }; -const formatIssueReminderEmbedDescription = (content: string): string => { - return content - .replace(/^# Issue Reminders\n?/, "") - .replace(/^## (.+)$/gm, "**$1**") - .trim(); -}; - const splitChannelReminderMessages = ( grouped: Partial>, ): { content: string; targets: IssueReminderTarget[] }[] => { const chunks: { content: string; targets: IssueReminderTarget[] }[] = []; - let currentContent = "# Issue Reminders"; + let currentContent = `## Issue Reminders - ${formatIssueReminderDate(new Date())}`; let currentTargets: IssueReminderTarget[] = []; for (const day of ISSUE_REMINDER_DAY_ORDER) { const targets = grouped[day]; if (!targets || targets.length === 0) continue; - const sectionLines = targets.map(formatIssueReminder); - const sectionContent = `## ${ISSUE_REMINDER_DAY_LABELS[day]}\n${sectionLines.join("\n")}`; + const sortedTargets = sortIssueReminderTargets(targets); + const sectionLines = sortedTargets.map(formatIssueReminder); + const sectionContent = `### ${ISSUE_REMINDER_DAY_LABELS[day]}\n${sectionLines.join("\n")}`; const nextContent = `${currentContent}\n${sectionContent}`; if (nextContent.length <= MAX_DISCORD_MESSAGE_LENGTH) { currentContent = nextContent; - currentTargets.push(...targets); + currentTargets.push(...sortedTargets); continue; } @@ -238,7 +279,7 @@ const splitChannelReminderMessages = ( chunks.push({ content: currentContent, targets: currentTargets }); } - let sectionChunkContent = `# Issue Reminders\n\n## ${ISSUE_REMINDER_DAY_LABELS[day]}`; + let sectionChunkContent = `## Issue Reminders - ${formatIssueReminderDate(new Date())}\n\n### ${ISSUE_REMINDER_DAY_LABELS[day]}`; let sectionChunkTargets: IssueReminderTarget[] = []; const sectionHeaderLength = `${sectionChunkContent}\n`.length; @@ -247,7 +288,7 @@ const splitChannelReminderMessages = ( sectionLines[index] ?? "", MAX_DISCORD_MESSAGE_LENGTH - sectionHeaderLength, ); - const target = targets[index]; + const target = sortedTargets[index]; if (!target) continue; const nextSectionChunkContent = `${sectionChunkContent}\n${line}`; if (nextSectionChunkContent.length > MAX_DISCORD_MESSAGE_LENGTH) { @@ -258,7 +299,7 @@ const splitChannelReminderMessages = ( }); } - sectionChunkContent = `# Issue Reminders\n\n## ${ISSUE_REMINDER_DAY_LABELS[day]}\n${line}`; + sectionChunkContent = `## Issue Reminders - ${formatIssueReminderDate(new Date())}\n\n### ${ISSUE_REMINDER_DAY_LABELS[day]}\n${line}`; sectionChunkTargets = [target]; continue; } @@ -285,13 +326,7 @@ const sendIssueReminderChunk = async ( try { await api.post(Routes.channelMessages(channelId), { body: { - embeds: [ - { - title: "Issue Reminders", - description: formatIssueReminderEmbedDescription(chunk.content), - color: 0xcca4f4, - }, - ], + content: chunk.content, allowed_mentions: getAllowedMentions(chunk.targets), }, }); @@ -357,6 +392,8 @@ export const issueReminders = new CronBuilder({ return buildIssueReminderTarget({ id: issue.id, name: issue.name, + priority: issue.priority, + updatedAt: issue.updatedAt, team: issue.team, date: issue.date, teamDiscordRoleId: role.discordRoleId, From c3c8d6e737d799adba6b227b555ee1f47eccd302 Mon Sep 17 00:00:00 2001 From: Dylan Vidal Date: Tue, 14 Apr 2026 11:31:47 -0400 Subject: [PATCH 19/21] format --- apps/cron/src/crons/issue-reminders.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/cron/src/crons/issue-reminders.ts b/apps/cron/src/crons/issue-reminders.ts index 8f3305730..cbfc7428b 100644 --- a/apps/cron/src/crons/issue-reminders.ts +++ b/apps/cron/src/crons/issue-reminders.ts @@ -205,7 +205,8 @@ const sortIssueReminderTargets = ( ): IssueReminderTarget[] => { return [...targets].sort((a, b) => { const priorityDiff = - ISSUE_PRIORITY_RANK[b.issuePriority] - ISSUE_PRIORITY_RANK[a.issuePriority]; + ISSUE_PRIORITY_RANK[b.issuePriority] - + ISSUE_PRIORITY_RANK[a.issuePriority]; if (priorityDiff !== 0) return priorityDiff; return a.issueName.localeCompare(b.issueName, undefined, { sensitivity: "base", From 87bf85638c0ccb09db371c63eb3739d045ec9bbc Mon Sep 17 00:00:00 2001 From: Dylan Vidal Date: Tue, 14 Apr 2026 11:36:53 -0400 Subject: [PATCH 20/21] enum changes --- packages/consts/src/issue.ts | 18 +- .../db/drizzle/0004_youthful_galactus.sql | 8 + packages/db/drizzle/meta/0004_snapshot.json | 2742 +++++++++++++++++ packages/db/drizzle/meta/_journal.json | 7 + 4 files changed, 2766 insertions(+), 9 deletions(-) create mode 100644 packages/db/drizzle/0004_youthful_galactus.sql create mode 100644 packages/db/drizzle/meta/0004_snapshot.json diff --git a/packages/consts/src/issue.ts b/packages/consts/src/issue.ts index 5575b69ed..e37ffe2c0 100644 --- a/packages/consts/src/issue.ts +++ b/packages/consts/src/issue.ts @@ -2,19 +2,19 @@ export const DEFAULT_ISSUE_REMINDER_CHANNEL_ID = "1459204271655489567"; export const DEV_ISSUE_REMINDER_CHANNEL_ID = "1263902679457992845"; export const ISSUE_STATUS = [ - "BACKLOG", - "PLANNING", - "IN_PROGRESS", - "FINISHED", + "Backlog", + "Planning", + "In Progress", + "Finished", ] as const; -export const PRIORITY = ["LOWEST", "LOW", "MEDIUM", "HIGH", "HIGHEST"] as const; +export const PRIORITY = ["Lowest", "Low", "Medium", "High", "Highest"] as const; export const STATUS_COLORS: Record<(typeof ISSUE_STATUS)[number], string> = { - BACKLOG: "bg-slate-400", - PLANNING: "bg-amber-400", - IN_PROGRESS: "bg-emerald-400", - FINISHED: "bg-rose-400", + Backlog: "bg-slate-400", + Planning: "bg-amber-400", + "In Progress": "bg-emerald-400", + Finished: "bg-rose-400", }; export const TASK_DUE_HOURS = 23; diff --git a/packages/db/drizzle/0004_youthful_galactus.sql b/packages/db/drizzle/0004_youthful_galactus.sql new file mode 100644 index 000000000..09429a7f6 --- /dev/null +++ b/packages/db/drizzle/0004_youthful_galactus.sql @@ -0,0 +1,8 @@ +ALTER TABLE "knight_hacks_issue" ALTER COLUMN "priority" SET DATA TYPE text;--> statement-breakpoint +DROP TYPE "public"."issue_priority";--> statement-breakpoint +CREATE TYPE "public"."issue_priority" AS ENUM('Lowest', 'Low', 'Medium', 'High', 'Highest');--> statement-breakpoint +ALTER TABLE "knight_hacks_issue" ALTER COLUMN "priority" SET DATA TYPE "public"."issue_priority" USING "priority"::"public"."issue_priority";--> statement-breakpoint +ALTER TABLE "knight_hacks_issue" ALTER COLUMN "status" SET DATA TYPE text;--> statement-breakpoint +DROP TYPE "public"."issue_status";--> statement-breakpoint +CREATE TYPE "public"."issue_status" AS ENUM('Backlog', 'Planning', 'In Progress', 'Finished');--> statement-breakpoint +ALTER TABLE "knight_hacks_issue" ALTER COLUMN "status" SET DATA TYPE "public"."issue_status" USING "status"::"public"."issue_status"; \ No newline at end of file diff --git a/packages/db/drizzle/meta/0004_snapshot.json b/packages/db/drizzle/meta/0004_snapshot.json new file mode 100644 index 000000000..a2ac882fc --- /dev/null +++ b/packages/db/drizzle/meta/0004_snapshot.json @@ -0,0 +1,2742 @@ +{ + "id": "e9c3b450-eddc-4738-bdb9-b33a461431a2", + "prevId": "3c88a643-8696-4819-afc0-11117b8e124e", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.auth_account": { + "name": "auth_account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "provider_account_id": { + "name": "provider_account_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "refresh_token": { + "name": "refresh_token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "auth_account_user_id_auth_user_id_fk": { + "name": "auth_account_user_id_auth_user_id_fk", + "tableFrom": "auth_account", + "tableTo": "auth_user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "auth_account_provider_provider_account_id_pk": { + "name": "auth_account_provider_provider_account_id_pk", + "columns": ["provider", "provider_account_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_judge_session": { + "name": "auth_judge_session", + "schema": "", + "columns": { + "session_token": { + "name": "session_token", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + }, + "room_name": { + "name": "room_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires": { + "name": "expires", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_permissions": { + "name": "auth_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "role_id": { + "name": "role_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "auth_permissions_role_id_auth_roles_id_fk": { + "name": "auth_permissions_role_id_auth_roles_id_fk", + "tableFrom": "auth_permissions", + "tableTo": "auth_roles", + "columnsFrom": ["role_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "auth_permissions_user_id_auth_user_id_fk": { + "name": "auth_permissions_user_id_auth_user_id_fk", + "tableFrom": "auth_permissions", + "tableTo": "auth_user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_roles": { + "name": "auth_roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "discord_role_id": { + "name": "discord_role_id", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "permissions": { + "name": "permissions", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "issue_reminder_channel": { + "name": "issue_reminder_channel", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'1459204271655489567'" + }, + "team_hexcode_color": { + "name": "team_hexcode_color", + "type": "varchar(7)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "auth_roles_discordRoleId_unique": { + "name": "auth_roles_discordRoleId_unique", + "nullsNotDistinct": false, + "columns": ["discord_role_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_session": { + "name": "auth_session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "session_token": { + "name": "session_token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "expires": { + "name": "expires", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "varchar(1024)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "auth_session_user_id_auth_user_id_fk": { + "name": "auth_session_user_id_auth_user_id_fk", + "tableFrom": "auth_session", + "tableTo": "auth_user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_user": { + "name": "auth_user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "discord_user_id": { + "name": "discord_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_verification": { + "name": "auth_verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_challenges": { + "name": "knight_hacks_challenges", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hackathon_id": { + "name": "hackathon_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sponsor": { + "name": "sponsor", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_challenges_hackathon_id_knight_hacks_hackathon_id_fk": { + "name": "knight_hacks_challenges_hackathon_id_knight_hacks_hackathon_id_fk", + "tableFrom": "knight_hacks_challenges", + "tableTo": "knight_hacks_hackathon", + "columnsFrom": ["hackathon_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "knight_hacks_challenges_title_hackathonId_unique": { + "name": "knight_hacks_challenges_title_hackathonId_unique", + "nullsNotDistinct": false, + "columns": ["title", "hackathon_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_dues_payment": { + "name": "knight_hacks_dues_payment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "member_id": { + "name": "member_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "payment_date": { + "name": "payment_date", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "year": { + "name": "year", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_dues_payment_member_id_knight_hacks_member_id_fk": { + "name": "knight_hacks_dues_payment_member_id_knight_hacks_member_id_fk", + "tableFrom": "knight_hacks_dues_payment", + "tableTo": "knight_hacks_member", + "columnsFrom": ["member_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "knight_hacks_dues_payment_memberId_year_unique": { + "name": "knight_hacks_dues_payment_memberId_year_unique", + "nullsNotDistinct": false, + "columns": ["member_id", "year"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_event": { + "name": "knight_hacks_event", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "discord_id": { + "name": "discord_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "google_id": { + "name": "google_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "tag": { + "name": "tag", + "type": "event_tag", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "start_datetime": { + "name": "start_datetime", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "end_datetime": { + "name": "end_datetime", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "location": { + "name": "location", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "dues_paying": { + "name": "dues_paying", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_operations_calendar": { + "name": "is_operations_calendar", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "roles": { + "name": "roles", + "type": "varchar(255)[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "points": { + "name": "points", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "hackathon_id": { + "name": "hackathon_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "discord_channel_id": { + "name": "discord_channel_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_event_hackathon_id_knight_hacks_hackathon_id_fk": { + "name": "knight_hacks_event_hackathon_id_knight_hacks_hackathon_id_fk", + "tableFrom": "knight_hacks_event", + "tableTo": "knight_hacks_hackathon", + "columnsFrom": ["hackathon_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_event_attendee": { + "name": "knight_hacks_event_attendee", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "member_id": { + "name": "member_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_event_attendee_member_id_knight_hacks_member_id_fk": { + "name": "knight_hacks_event_attendee_member_id_knight_hacks_member_id_fk", + "tableFrom": "knight_hacks_event_attendee", + "tableTo": "knight_hacks_member", + "columnsFrom": ["member_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knight_hacks_event_attendee_event_id_knight_hacks_event_id_fk": { + "name": "knight_hacks_event_attendee_event_id_knight_hacks_event_id_fk", + "tableFrom": "knight_hacks_event_attendee", + "tableTo": "knight_hacks_event", + "columnsFrom": ["event_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_event_feedback": { + "name": "knight_hacks_event_feedback", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "member_id": { + "name": "member_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "overall_event_rating": { + "name": "overall_event_rating", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "fun_rating": { + "name": "fun_rating", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "learned_rating": { + "name": "learned_rating", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "heard_about_us": { + "name": "heard_about_us", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "additional_feedback": { + "name": "additional_feedback", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "similar_event": { + "name": "similar_event", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_event_feedback_member_id_knight_hacks_member_id_fk": { + "name": "knight_hacks_event_feedback_member_id_knight_hacks_member_id_fk", + "tableFrom": "knight_hacks_event_feedback", + "tableTo": "knight_hacks_member", + "columnsFrom": ["member_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knight_hacks_event_feedback_event_id_knight_hacks_event_id_fk": { + "name": "knight_hacks_event_feedback_event_id_knight_hacks_event_id_fk", + "tableFrom": "knight_hacks_event_feedback", + "tableTo": "knight_hacks_event", + "columnsFrom": ["event_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_form_response": { + "name": "knight_hacks_form_response", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "form": { + "name": "form", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "response_data": { + "name": "response_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "edited_at": { + "name": "edited_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_form_response_form_knight_hacks_form_schemas_id_fk": { + "name": "knight_hacks_form_response_form_knight_hacks_form_schemas_id_fk", + "tableFrom": "knight_hacks_form_response", + "tableTo": "knight_hacks_form_schemas", + "columnsFrom": ["form"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "knight_hacks_form_response_user_id_auth_user_id_fk": { + "name": "knight_hacks_form_response_user_id_auth_user_id_fk", + "tableFrom": "knight_hacks_form_response", + "tableTo": "auth_user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_form_response_roles": { + "name": "knight_hacks_form_response_roles", + "schema": "", + "columns": { + "form_id": { + "name": "form_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_form_response_roles_form_id_knight_hacks_form_schemas_id_fk": { + "name": "knight_hacks_form_response_roles_form_id_knight_hacks_form_schemas_id_fk", + "tableFrom": "knight_hacks_form_response_roles", + "tableTo": "knight_hacks_form_schemas", + "columnsFrom": ["form_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knight_hacks_form_response_roles_role_id_auth_roles_id_fk": { + "name": "knight_hacks_form_response_roles_role_id_auth_roles_id_fk", + "tableFrom": "knight_hacks_form_response_roles", + "tableTo": "auth_roles", + "columnsFrom": ["role_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "knight_hacks_form_response_roles_form_id_role_id_pk": { + "name": "knight_hacks_form_response_roles_form_id_role_id_pk", + "columns": ["form_id", "role_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_form_section_roles": { + "name": "knight_hacks_form_section_roles", + "schema": "", + "columns": { + "section_id": { + "name": "section_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_form_section_roles_section_id_knight_hacks_form_sections_id_fk": { + "name": "knight_hacks_form_section_roles_section_id_knight_hacks_form_sections_id_fk", + "tableFrom": "knight_hacks_form_section_roles", + "tableTo": "knight_hacks_form_sections", + "columnsFrom": ["section_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knight_hacks_form_section_roles_role_id_auth_roles_id_fk": { + "name": "knight_hacks_form_section_roles_role_id_auth_roles_id_fk", + "tableFrom": "knight_hacks_form_section_roles", + "tableTo": "auth_roles", + "columnsFrom": ["role_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "knight_hacks_form_section_roles_section_id_role_id_pk": { + "name": "knight_hacks_form_section_roles_section_id_role_id_pk", + "columns": ["section_id", "role_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_form_sections": { + "name": "knight_hacks_form_sections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "order": { + "name": "order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "knight_hacks_form_sections_name_unique": { + "name": "knight_hacks_form_sections_name_unique", + "nullsNotDistinct": false, + "columns": ["name"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_form_schemas": { + "name": "knight_hacks_form_schemas", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "slug_name": { + "name": "slug_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "dues_only": { + "name": "dues_only", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "allow_resubmission": { + "name": "allow_resubmission", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "allow_edit": { + "name": "allow_edit", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "form_data": { + "name": "form_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "form_validator_json": { + "name": "form_validator_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "section": { + "name": "section", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'General'" + }, + "section_id": { + "name": "section_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "is_closed": { + "name": "is_closed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_form_schemas_section_id_knight_hacks_form_sections_id_fk": { + "name": "knight_hacks_form_schemas_section_id_knight_hacks_form_sections_id_fk", + "tableFrom": "knight_hacks_form_schemas", + "tableTo": "knight_hacks_form_sections", + "columnsFrom": ["section_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "knight_hacks_form_schemas_slugName_unique": { + "name": "knight_hacks_form_schemas_slugName_unique", + "nullsNotDistinct": false, + "columns": ["slug_name"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_hackathon": { + "name": "knight_hacks_hackathon", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "theme": { + "name": "theme", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "application_open": { + "name": "application_open", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "application_deadline": { + "name": "application_deadline", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "confirmation_deadline": { + "name": "confirmation_deadline", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "start_date": { + "name": "start_date", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "end_date": { + "name": "end_date", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_hackathon_sponsor": { + "name": "knight_hacks_hackathon_sponsor", + "schema": "", + "columns": { + "hackathon_id": { + "name": "hackathon_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sponsor_id": { + "name": "sponsor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "tier": { + "name": "tier", + "type": "sponsor_tier", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_hackathon_sponsor_hackathon_id_knight_hacks_hackathon_id_fk": { + "name": "knight_hacks_hackathon_sponsor_hackathon_id_knight_hacks_hackathon_id_fk", + "tableFrom": "knight_hacks_hackathon_sponsor", + "tableTo": "knight_hacks_hackathon", + "columnsFrom": ["hackathon_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knight_hacks_hackathon_sponsor_sponsor_id_knight_hacks_sponsor_id_fk": { + "name": "knight_hacks_hackathon_sponsor_sponsor_id_knight_hacks_sponsor_id_fk", + "tableFrom": "knight_hacks_hackathon_sponsor", + "tableTo": "knight_hacks_sponsor", + "columnsFrom": ["sponsor_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_hacker": { + "name": "knight_hacks_hacker", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "first_name": { + "name": "first_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "last_name": { + "name": "last_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "gender": { + "name": "gender", + "type": "gender", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'Prefer not to answer'" + }, + "discord_user": { + "name": "discord_user", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "age": { + "name": "age", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "country": { + "name": "country", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'United States of America'" + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "phone_number": { + "name": "phone_number", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "school": { + "name": "school", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "level_of_study": { + "name": "level_of_study", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "major": { + "name": "major", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'Computer Science'" + }, + "race_or_ethnicity": { + "name": "race_or_ethnicity", + "type": "race_or_ethnicity", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'Prefer not to answer'" + }, + "shirt_size": { + "name": "shirt_size", + "type": "shirt_size", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "github_profile_url": { + "name": "github_profile_url", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "linkedin_profile_url": { + "name": "linkedin_profile_url", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "website_url": { + "name": "website_url", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "resume_url": { + "name": "resume_url", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "dob": { + "name": "dob", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "grad_date": { + "name": "grad_date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "survey_1": { + "name": "survey_1", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "survey_2": { + "name": "survey_2", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_first_time": { + "name": "is_first_time", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "food_allergies": { + "name": "food_allergies", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agrees_to_receive_emails_from_mlh": { + "name": "agrees_to_receive_emails_from_mlh", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "agrees_to_mlh_code_of_conduct": { + "name": "agrees_to_mlh_code_of_conduct", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "agrees_to_mlh_data_sharing": { + "name": "agrees_to_mlh_data_sharing", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "date_created": { + "name": "date_created", + "type": "date", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_created": { + "name": "time_created", + "type": "time", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_hacker_user_id_auth_user_id_fk": { + "name": "knight_hacks_hacker_user_id_auth_user_id_fk", + "tableFrom": "knight_hacks_hacker", + "tableTo": "auth_user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_hacker_attendee": { + "name": "knight_hacks_hacker_attendee", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "hacker_id": { + "name": "hacker_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "hackathon_id": { + "name": "hackathon_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "time_applied": { + "name": "time_applied", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_confirmed": { + "name": "time_confirmed", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "points": { + "name": "points", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "class": { + "name": "class", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false, + "default": null + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_hacker_attendee_hacker_id_knight_hacks_hacker_id_fk": { + "name": "knight_hacks_hacker_attendee_hacker_id_knight_hacks_hacker_id_fk", + "tableFrom": "knight_hacks_hacker_attendee", + "tableTo": "knight_hacks_hacker", + "columnsFrom": ["hacker_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knight_hacks_hacker_attendee_hackathon_id_knight_hacks_hackathon_id_fk": { + "name": "knight_hacks_hacker_attendee_hackathon_id_knight_hacks_hackathon_id_fk", + "tableFrom": "knight_hacks_hacker_attendee", + "tableTo": "knight_hacks_hackathon", + "columnsFrom": ["hackathon_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_hacker_event_attendee": { + "name": "knight_hacks_hacker_event_attendee", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "hacker_att_id": { + "name": "hacker_att_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "hackathon_id": { + "name": "hackathon_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_hacker_event_attendee_hacker_att_id_knight_hacks_hacker_attendee_id_fk": { + "name": "knight_hacks_hacker_event_attendee_hacker_att_id_knight_hacks_hacker_attendee_id_fk", + "tableFrom": "knight_hacks_hacker_event_attendee", + "tableTo": "knight_hacks_hacker_attendee", + "columnsFrom": ["hacker_att_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knight_hacks_hacker_event_attendee_hackathon_id_knight_hacks_hackathon_id_fk": { + "name": "knight_hacks_hacker_event_attendee_hackathon_id_knight_hacks_hackathon_id_fk", + "tableFrom": "knight_hacks_hacker_event_attendee", + "tableTo": "knight_hacks_hackathon", + "columnsFrom": ["hackathon_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knight_hacks_hacker_event_attendee_event_id_knight_hacks_event_id_fk": { + "name": "knight_hacks_hacker_event_attendee_event_id_knight_hacks_event_id_fk", + "tableFrom": "knight_hacks_hacker_event_attendee", + "tableTo": "knight_hacks_event", + "columnsFrom": ["event_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_issue": { + "name": "knight_hacks_issue", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "status": { + "name": "status", + "type": "issue_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "links": { + "name": "links", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "event": { + "name": "event", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "issue_priority", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "team": { + "name": "team", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "creator": { + "name": "creator", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "parent": { + "name": "parent", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "issue_team_idx": { + "name": "issue_team_idx", + "columns": [ + { + "expression": "team", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_creator_idx": { + "name": "issue_creator_idx", + "columns": [ + { + "expression": "creator", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_status_idx": { + "name": "issue_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_date_idx": { + "name": "issue_date_idx", + "columns": [ + { + "expression": "date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_parent_idx": { + "name": "issue_parent_idx", + "columns": [ + { + "expression": "parent", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "issue_priority_idx": { + "name": "issue_priority_idx", + "columns": [ + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knight_hacks_issue_event_knight_hacks_event_id_fk": { + "name": "knight_hacks_issue_event_knight_hacks_event_id_fk", + "tableFrom": "knight_hacks_issue", + "tableTo": "knight_hacks_event", + "columnsFrom": ["event"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "knight_hacks_issue_team_auth_roles_id_fk": { + "name": "knight_hacks_issue_team_auth_roles_id_fk", + "tableFrom": "knight_hacks_issue", + "tableTo": "auth_roles", + "columnsFrom": ["team"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "knight_hacks_issue_creator_auth_user_id_fk": { + "name": "knight_hacks_issue_creator_auth_user_id_fk", + "tableFrom": "knight_hacks_issue", + "tableTo": "auth_user", + "columnsFrom": ["creator"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "issue_parent_fk": { + "name": "issue_parent_fk", + "tableFrom": "knight_hacks_issue", + "tableTo": "knight_hacks_issue", + "columnsFrom": ["parent"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_issues_to_teams_visibility": { + "name": "knight_hacks_issues_to_teams_visibility", + "schema": "", + "columns": { + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_issues_to_teams_visibility_issue_id_knight_hacks_issue_id_fk": { + "name": "knight_hacks_issues_to_teams_visibility_issue_id_knight_hacks_issue_id_fk", + "tableFrom": "knight_hacks_issues_to_teams_visibility", + "tableTo": "knight_hacks_issue", + "columnsFrom": ["issue_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knight_hacks_issues_to_teams_visibility_team_id_auth_roles_id_fk": { + "name": "knight_hacks_issues_to_teams_visibility_team_id_auth_roles_id_fk", + "tableFrom": "knight_hacks_issues_to_teams_visibility", + "tableTo": "auth_roles", + "columnsFrom": ["team_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "knight_hacks_issues_to_teams_visibility_issue_id_team_id_pk": { + "name": "knight_hacks_issues_to_teams_visibility_issue_id_team_id_pk", + "columns": ["issue_id", "team_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_issues_to_users_assignment": { + "name": "knight_hacks_issues_to_users_assignment", + "schema": "", + "columns": { + "issue_id": { + "name": "issue_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_issues_to_users_assignment_issue_id_knight_hacks_issue_id_fk": { + "name": "knight_hacks_issues_to_users_assignment_issue_id_knight_hacks_issue_id_fk", + "tableFrom": "knight_hacks_issues_to_users_assignment", + "tableTo": "knight_hacks_issue", + "columnsFrom": ["issue_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knight_hacks_issues_to_users_assignment_user_id_auth_user_id_fk": { + "name": "knight_hacks_issues_to_users_assignment_user_id_auth_user_id_fk", + "tableFrom": "knight_hacks_issues_to_users_assignment", + "tableTo": "auth_user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "knight_hacks_issues_to_users_assignment_issue_id_user_id_pk": { + "name": "knight_hacks_issues_to_users_assignment_issue_id_user_id_pk", + "columns": ["issue_id", "user_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_judged_submission": { + "name": "knight_hacks_judged_submission", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "hackathon_id": { + "name": "hackathon_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "submission_id": { + "name": "submission_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "judge_id": { + "name": "judge_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "private_feedback": { + "name": "private_feedback", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "public_feedback": { + "name": "public_feedback", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "originality_rating": { + "name": "originality_rating", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "design_rating": { + "name": "design_rating", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "technical_understanding_rating": { + "name": "technical_understanding_rating", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "implementation_rating": { + "name": "implementation_rating", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "wow_factor_rating": { + "name": "wow_factor_rating", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_judged_submission_hackathon_id_knight_hacks_hackathon_id_fk": { + "name": "knight_hacks_judged_submission_hackathon_id_knight_hacks_hackathon_id_fk", + "tableFrom": "knight_hacks_judged_submission", + "tableTo": "knight_hacks_hackathon", + "columnsFrom": ["hackathon_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "knight_hacks_judged_submission_submission_id_knight_hacks_submissions_id_fk": { + "name": "knight_hacks_judged_submission_submission_id_knight_hacks_submissions_id_fk", + "tableFrom": "knight_hacks_judged_submission", + "tableTo": "knight_hacks_submissions", + "columnsFrom": ["submission_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "knight_hacks_judged_submission_judge_id_knight_hacks_judges_id_fk": { + "name": "knight_hacks_judged_submission_judge_id_knight_hacks_judges_id_fk", + "tableFrom": "knight_hacks_judged_submission", + "tableTo": "knight_hacks_judges", + "columnsFrom": ["judge_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_judges": { + "name": "knight_hacks_judges", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "room_name": { + "name": "room_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "challenge_id": { + "name": "challenge_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_judges_challenge_id_knight_hacks_challenges_id_fk": { + "name": "knight_hacks_judges_challenge_id_knight_hacks_challenges_id_fk", + "tableFrom": "knight_hacks_judges", + "tableTo": "knight_hacks_challenges", + "columnsFrom": ["challenge_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_member": { + "name": "knight_hacks_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "first_name": { + "name": "first_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "last_name": { + "name": "last_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "discord_user": { + "name": "discord_user", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "age": { + "name": "age", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "phone_number": { + "name": "phone_number", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "school": { + "name": "school", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "level_of_study": { + "name": "level_of_study", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "major": { + "name": "major", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'Computer Science'" + }, + "gender": { + "name": "gender", + "type": "gender", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'Prefer not to answer'" + }, + "race_or_ethnicity": { + "name": "race_or_ethnicity", + "type": "race_or_ethnicity", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'Prefer not to answer'" + }, + "guild_profile_visible": { + "name": "guild_profile_visible", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "tagline": { + "name": "tagline", + "type": "varchar(80)", + "primaryKey": false, + "notNull": false + }, + "about": { + "name": "about", + "type": "varchar(500)", + "primaryKey": false, + "notNull": false + }, + "profile_picture_url": { + "name": "profile_picture_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "shirt_size": { + "name": "shirt_size", + "type": "shirt_size", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "github_profile_url": { + "name": "github_profile_url", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "linkedin_profile_url": { + "name": "linkedin_profile_url", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "website_url": { + "name": "website_url", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "resume_url": { + "name": "resume_url", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "dob": { + "name": "dob", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "grad_date": { + "name": "grad_date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "company": { + "name": "company", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "points": { + "name": "points", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "date_created": { + "name": "date_created", + "type": "date", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "time_created": { + "name": "time_created", + "type": "time", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_member_user_id_auth_user_id_fk": { + "name": "knight_hacks_member_user_id_auth_user_id_fk", + "tableFrom": "knight_hacks_member", + "tableTo": "auth_user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "knight_hacks_member_email_unique": { + "name": "knight_hacks_member_email_unique", + "nullsNotDistinct": false, + "columns": ["email"] + }, + "knight_hacks_member_phoneNumber_unique": { + "name": "knight_hacks_member_phoneNumber_unique", + "nullsNotDistinct": false, + "columns": ["phone_number"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_companies": { + "name": "knight_hacks_companies", + "schema": "", + "columns": { + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_sponsor": { + "name": "knight_hacks_sponsor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "logo_url": { + "name": "logo_url", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "website_url": { + "name": "website_url", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_submissions": { + "name": "knight_hacks_submissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "challenge_id": { + "name": "challenge_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "hackathon_id": { + "name": "hackathon_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_submissions_challenge_id_knight_hacks_challenges_id_fk": { + "name": "knight_hacks_submissions_challenge_id_knight_hacks_challenges_id_fk", + "tableFrom": "knight_hacks_submissions", + "tableTo": "knight_hacks_challenges", + "columnsFrom": ["challenge_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knight_hacks_submissions_team_id_knight_hacks_teams_id_fk": { + "name": "knight_hacks_submissions_team_id_knight_hacks_teams_id_fk", + "tableFrom": "knight_hacks_submissions", + "tableTo": "knight_hacks_teams", + "columnsFrom": ["team_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knight_hacks_submissions_hackathon_id_knight_hacks_hackathon_id_fk": { + "name": "knight_hacks_submissions_hackathon_id_knight_hacks_hackathon_id_fk", + "tableFrom": "knight_hacks_submissions", + "tableTo": "knight_hacks_hackathon", + "columnsFrom": ["hackathon_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "knight_hacks_submissions_teamId_challengeId_unique": { + "name": "knight_hacks_submissions_teamId_challengeId_unique", + "nullsNotDistinct": false, + "columns": ["team_id", "challenge_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_teams": { + "name": "knight_hacks_teams", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "hackathon_id": { + "name": "hackathon_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "project_title": { + "name": "project_title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "submission_url": { + "name": "submission_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "project_created_at": { + "name": "project_created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "is_project_submitted": { + "name": "is_project_submitted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "devpost_url": { + "name": "devpost_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "universities": { + "name": "universities", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "emails": { + "name": "emails", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "match_key": { + "name": "match_key", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_teams_hackathon_id_knight_hacks_hackathon_id_fk": { + "name": "knight_hacks_teams_hackathon_id_knight_hacks_hackathon_id_fk", + "tableFrom": "knight_hacks_teams", + "tableTo": "knight_hacks_hackathon", + "columnsFrom": ["hackathon_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "knight_hacks_teams_matchKey_unique": { + "name": "knight_hacks_teams_matchKey_unique", + "nullsNotDistinct": false, + "columns": ["match_key"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_template": { + "name": "knight_hacks_template", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knight_hacks_trpc_form_connection": { + "name": "knight_hacks_trpc_form_connection", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "form": { + "name": "form", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "proc": { + "name": "proc", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "connections": { + "name": "connections", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "knight_hacks_trpc_form_connection_form_knight_hacks_form_schemas_id_fk": { + "name": "knight_hacks_trpc_form_connection_form_knight_hacks_form_schemas_id_fk", + "tableFrom": "knight_hacks_trpc_form_connection", + "tableTo": "knight_hacks_form_schemas", + "columnsFrom": ["form"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.event_tag": { + "name": "event_tag", + "schema": "public", + "values": [ + "GBM", + "Social", + "Kickstart", + "Project Launch", + "Hello World", + "Sponsorship", + "Tech Exploration", + "Class Support", + "Workshop", + "OPS", + "Collabs", + "Check-in", + "Merch", + "Food", + "Ceremony", + "CAREER-FAIR", + "RSO-FAIR" + ] + }, + "public.gender": { + "name": "gender", + "schema": "public", + "values": [ + "Man", + "Woman", + "Non-binary", + "Prefer to self-describe", + "Prefer not to answer" + ] + }, + "public.hackathon_application_state": { + "name": "hackathon_application_state", + "schema": "public", + "values": [ + "withdrawn", + "pending", + "accepted", + "waitlisted", + "checkedin", + "confirmed", + "denied" + ] + }, + "public.issue_priority": { + "name": "issue_priority", + "schema": "public", + "values": ["Lowest", "Low", "Medium", "High", "Highest"] + }, + "public.issue_status": { + "name": "issue_status", + "schema": "public", + "values": ["Backlog", "Planning", "In Progress", "Finished"] + }, + "public.race_or_ethnicity": { + "name": "race_or_ethnicity", + "schema": "public", + "values": [ + "White", + "Black or African American", + "Hispanic / Latino / Spanish Origin", + "Asian", + "Native Hawaiian or Other Pacific Islander", + "Native American or Alaskan Native", + "Middle Eastern", + "Prefer not to answer", + "Other" + ] + }, + "public.shirt_size": { + "name": "shirt_size", + "schema": "public", + "values": ["XS", "S", "M", "L", "XL", "2XL", "3XL"] + }, + "public.sponsor_tier": { + "name": "sponsor_tier", + "schema": "public", + "values": ["gold", "silver", "bronze", "other"] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/packages/db/drizzle/meta/_journal.json b/packages/db/drizzle/meta/_journal.json index ea17bf096..7ba672237 100644 --- a/packages/db/drizzle/meta/_journal.json +++ b/packages/db/drizzle/meta/_journal.json @@ -29,6 +29,13 @@ "when": 1776108776095, "tag": "0003_hot_killraven", "breakpoints": true + }, + { + "idx": 4, + "version": "7", + "when": 1776180772365, + "tag": "0004_youthful_galactus", + "breakpoints": true } ] } From 9a6faea18378e444cade6970a2eae2bd81f74336 Mon Sep 17 00:00:00 2001 From: Dylan Vidal Date: Tue, 14 Apr 2026 11:43:27 -0400 Subject: [PATCH 21/21] typecheck --- .../_components/issue-calendar/calendar-day-agenda.tsx | 2 +- .../_components/issue-calendar/calendar-issue-dialog.tsx | 2 +- .../issue-calendar/calendar-status-dot-legend.tsx | 8 ++++---- .../blade/src/app/_components/issue-calendar/calendar.tsx | 6 +++--- .../src/app/_components/issue-kanban/issues-kanban.tsx | 2 +- apps/blade/src/app/_components/issue-list/issues-list.tsx | 4 ++-- .../src/app/_components/issues/issue-fetcher-pane.tsx | 2 +- apps/cron/src/crons/issue-reminders.ts | 2 +- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/apps/blade/src/app/_components/issue-calendar/calendar-day-agenda.tsx b/apps/blade/src/app/_components/issue-calendar/calendar-day-agenda.tsx index 0641fa979..4a8174c35 100644 --- a/apps/blade/src/app/_components/issue-calendar/calendar-day-agenda.tsx +++ b/apps/blade/src/app/_components/issue-calendar/calendar-day-agenda.tsx @@ -49,7 +49,7 @@ function assigneeDisplayNames(issue: Issue): string[] { } function isOverdueIssue(issue: Issue) { - if (issue.status === "FINISHED" || !issue.date) return false; + if (issue.status === "Finished" || !issue.date) return false; const dueDate = new Date(issue.date); const todayStart = new Date(); todayStart.setHours(0, 0, 0, 0); diff --git a/apps/blade/src/app/_components/issue-calendar/calendar-issue-dialog.tsx b/apps/blade/src/app/_components/issue-calendar/calendar-issue-dialog.tsx index 6c78ac53d..1af3ad6f3 100644 --- a/apps/blade/src/app/_components/issue-calendar/calendar-issue-dialog.tsx +++ b/apps/blade/src/app/_components/issue-calendar/calendar-issue-dialog.tsx @@ -43,7 +43,7 @@ function isIssueOverdue( status: GetIssueResult["status"], date: Date | string | null | undefined, ) { - if (status === "FINISHED" || !date) return false; + if (status === "Finished" || !date) return false; const dueDate = new Date(date); const todayStart = new Date(); todayStart.setHours(0, 0, 0, 0); diff --git a/apps/blade/src/app/_components/issue-calendar/calendar-status-dot-legend.tsx b/apps/blade/src/app/_components/issue-calendar/calendar-status-dot-legend.tsx index 2eec2b5ea..f0da1799c 100644 --- a/apps/blade/src/app/_components/issue-calendar/calendar-status-dot-legend.tsx +++ b/apps/blade/src/app/_components/issue-calendar/calendar-status-dot-legend.tsx @@ -4,10 +4,10 @@ import { ISSUE } from "@forge/consts"; const STATUS_LEGEND_LABEL: Record<(typeof ISSUE.ISSUE_STATUS)[number], string> = { - BACKLOG: "Backlog", - PLANNING: "Planning", - IN_PROGRESS: "In Progress", - FINISHED: "Finished", + Backlog: "Backlog", + Planning: "Planning", + "In Progress": "In Progress", + Finished: "Finished", }; export function IssueStatusDotLegend() { diff --git a/apps/blade/src/app/_components/issue-calendar/calendar.tsx b/apps/blade/src/app/_components/issue-calendar/calendar.tsx index 97989b1da..60f465c96 100644 --- a/apps/blade/src/app/_components/issue-calendar/calendar.tsx +++ b/apps/blade/src/app/_components/issue-calendar/calendar.tsx @@ -171,7 +171,7 @@ export default function CalendarView() { }, [paneData, rawPaneIssues, deferredPaneIssues]); const openCount = useMemo( - () => rawPaneIssues.filter((issue) => issue.status !== "FINISHED").length, + () => rawPaneIssues.filter((issue) => issue.status !== "Finished").length, [rawPaneIssues], ); const closedCount = rawPaneIssues.length - openCount; @@ -232,7 +232,7 @@ export default function CalendarView() { const baseClassNames = [ "calendar-issue", issue.event ? "calendar-issue--linked" : "calendar-issue--task", - ...(issue.status === "FINISHED" ? ["calendar-issue--finished"] : []), + ...(issue.status === "Finished" ? ["calendar-issue--finished"] : []), ] as string[]; const useAllDayBand = !issue.event && isDefaultTaskDueMoment(d); @@ -404,7 +404,7 @@ export default function CalendarView() { return undefined; } const ex = arg.event.extendedProps as { issueStatus?: IssueCalendarStatus }; - const status: IssueCalendarStatus = ex.issueStatus ?? "BACKLOG"; + const status: IssueCalendarStatus = ex.issueStatus ?? "Backlog"; const statusLabel = issueStatusLabel(status); return (
    issues.filter((issue) => issue.status !== "FINISHED").length, + () => issues.filter((issue) => issue.status !== "Finished").length, [issues], ); const closedCount = issues.length - openCount; diff --git a/apps/blade/src/app/_components/issue-list/issues-list.tsx b/apps/blade/src/app/_components/issue-list/issues-list.tsx index 5a649cb84..ce95daca2 100644 --- a/apps/blade/src/app/_components/issue-list/issues-list.tsx +++ b/apps/blade/src/app/_components/issue-list/issues-list.tsx @@ -50,7 +50,7 @@ type SortField = "name" | "status" | "date" | "team" | "updatedAt"; type SortOrder = "asc" | "desc" | null; function isOverdueIssue(issue: ISSUE.IssueFetcherPaneIssue) { - if (issue.status === "FINISHED" || !issue.date) return false; + if (issue.status === "Finished" || !issue.date) return false; const dueDate = new Date(issue.date); const todayStart = new Date(); todayStart.setHours(0, 0, 0, 0); @@ -100,7 +100,7 @@ export function IssuesList() { const error = paneData?.error ?? null; const openCount = useMemo( - () => issues.filter((issue) => issue.status !== "FINISHED").length, + () => issues.filter((issue) => issue.status !== "Finished").length, [issues], ); const closedCount = issues.length - openCount; diff --git a/apps/blade/src/app/_components/issues/issue-fetcher-pane.tsx b/apps/blade/src/app/_components/issues/issue-fetcher-pane.tsx index 33d701e31..297796eff 100644 --- a/apps/blade/src/app/_components/issues/issue-fetcher-pane.tsx +++ b/apps/blade/src/app/_components/issues/issue-fetcher-pane.tsx @@ -149,7 +149,7 @@ export function IssueFetcherPane(props: IssueFetcherPaneProps) { const blockedParents = new Set(); for (const issue of allIssues) { - if (issue.parent && issue.status !== "FINISHED") { + if (issue.parent && issue.status !== "Finished") { blockedParents.add(issue.parent); } } diff --git a/apps/cron/src/crons/issue-reminders.ts b/apps/cron/src/crons/issue-reminders.ts index cbfc7428b..c41a6e151 100644 --- a/apps/cron/src/crons/issue-reminders.ts +++ b/apps/cron/src/crons/issue-reminders.ts @@ -344,7 +344,7 @@ export const issueReminders = new CronBuilder({ color: 2, }).addCron("0 9 * * *", async () => { const issues = await db.query.Issue.findMany({ - where: and(isNotNull(Issue.date), ne(Issue.status, "FINISHED")), + where: and(isNotNull(Issue.date), ne(Issue.status, "Finished")), with: { userAssignments: { with: {