diff --git a/drizzle.config.ts b/drizzle.config.ts index f8a330a..858862b 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -3,13 +3,13 @@ import type { Config } from "drizzle-kit"; import { env } from "@/env"; export default { - schema: [ - "./src/server/db/schema.ts", // data schema - "./src/server/db/auth-schema.ts", // auth schema - ], - dialect: "postgresql", - dbCredentials: { - url: env.DATABASE_URL, - }, - tablesFilter: ["hackathon_*"], + schema: [ + "./src/server/db/schema.ts", // data schema + "./src/server/db/auth-schema.ts", // auth schema + ], + dialect: "postgresql", + dbCredentials: { + url: env.DATABASE_URL, + }, + tablesFilter: ["hackathon_*"], } satisfies Config; diff --git a/src/server/db/auth-schema.ts b/src/server/db/auth-schema.ts index d9bc44a..1e0c14f 100644 --- a/src/server/db/auth-schema.ts +++ b/src/server/db/auth-schema.ts @@ -1,196 +1,195 @@ import { - type InferInsertModel, - type InferSelectModel, - relations, + type InferInsertModel, + type InferSelectModel, + relations, } from "drizzle-orm"; import { - boolean, - index, - pgTable, - pgTableCreator, - text, - timestamp, + boolean, + index, + pgTableCreator, + text, + timestamp, } from "drizzle-orm/pg-core"; export const createTable = pgTableCreator((name) => `hackathon_${name}`); export const user = createTable("user", { - id: text("id").primaryKey(), - name: text("name").notNull(), - email: text("email").notNull().unique(), - emailVerified: boolean("email_verified").default(false).notNull(), - image: text("image"), - createdAt: timestamp("created_at").defaultNow().notNull(), - updatedAt: timestamp("updated_at") - .defaultNow() - .$onUpdate(() => /* @__PURE__ */ new Date()) - .notNull(), - role: text("role"), - banned: boolean("banned").default(false), - banReason: text("ban_reason"), - banExpires: timestamp("ban_expires"), - dietaryRestrictions: text("dietary_restrictions"), - school: text("school"), - faculty: text("faculty"), + id: text("id").primaryKey(), + name: text("name").notNull(), + email: text("email").notNull().unique(), + emailVerified: boolean("email_verified").default(false).notNull(), + image: text("image"), + createdAt: timestamp("created_at").defaultNow().notNull(), + updatedAt: timestamp("updated_at") + .defaultNow() + .$onUpdate(() => /* @__PURE__ */ new Date()) + .notNull(), + role: text("role"), + banned: boolean("banned").default(false), + banReason: text("ban_reason"), + banExpires: timestamp("ban_expires"), + dietaryRestrictions: text("dietary_restrictions"), + school: text("school"), + faculty: text("faculty"), }); export const session = createTable( - "session", - { - id: text("id").primaryKey(), - expiresAt: timestamp("expires_at").notNull(), - token: text("token").notNull().unique(), - createdAt: timestamp("created_at").defaultNow().notNull(), - updatedAt: timestamp("updated_at") - .$onUpdate(() => /* @__PURE__ */ new Date()) - .notNull(), - ipAddress: text("ip_address"), - userAgent: text("user_agent"), - userId: text("user_id") - .notNull() - .references(() => user.id, { onDelete: "cascade" }), - activeOrganizationId: text("active_organization_id"), - impersonatedBy: text("impersonated_by"), - }, - (table) => [index("session_userId_idx").on(table.userId)], + "session", + { + id: text("id").primaryKey(), + expiresAt: timestamp("expires_at").notNull(), + token: text("token").notNull().unique(), + createdAt: timestamp("created_at").defaultNow().notNull(), + updatedAt: timestamp("updated_at") + .$onUpdate(() => /* @__PURE__ */ new Date()) + .notNull(), + ipAddress: text("ip_address"), + userAgent: text("user_agent"), + userId: text("user_id") + .notNull() + .references(() => user.id, { onDelete: "cascade" }), + activeOrganizationId: text("active_organization_id"), + impersonatedBy: text("impersonated_by"), + }, + (table) => [index("session_userId_idx").on(table.userId)], ); export const account = createTable( - "account", - { - id: text("id").primaryKey(), - accountId: text("account_id").notNull(), - providerId: text("provider_id").notNull(), - userId: text("user_id") - .notNull() - .references(() => user.id, { onDelete: "cascade" }), - accessToken: text("access_token"), - refreshToken: text("refresh_token"), - idToken: text("id_token"), - accessTokenExpiresAt: timestamp("access_token_expires_at"), - refreshTokenExpiresAt: timestamp("refresh_token_expires_at"), - scope: text("scope"), - password: text("password"), - createdAt: timestamp("created_at").defaultNow().notNull(), - updatedAt: timestamp("updated_at") - .$onUpdate(() => /* @__PURE__ */ new Date()) - .notNull(), - }, - (table) => [index("account_userId_idx").on(table.userId)], + "account", + { + id: text("id").primaryKey(), + accountId: text("account_id").notNull(), + providerId: text("provider_id").notNull(), + userId: text("user_id") + .notNull() + .references(() => user.id, { onDelete: "cascade" }), + accessToken: text("access_token"), + refreshToken: text("refresh_token"), + idToken: text("id_token"), + accessTokenExpiresAt: timestamp("access_token_expires_at"), + refreshTokenExpiresAt: timestamp("refresh_token_expires_at"), + scope: text("scope"), + password: text("password"), + createdAt: timestamp("created_at").defaultNow().notNull(), + updatedAt: timestamp("updated_at") + .$onUpdate(() => /* @__PURE__ */ new Date()) + .notNull(), + }, + (table) => [index("account_userId_idx").on(table.userId)], ); export const verification = createTable( - "verification", - { - id: text("id").primaryKey(), - identifier: text("identifier").notNull(), - value: text("value").notNull(), - expiresAt: timestamp("expires_at").notNull(), - createdAt: timestamp("created_at").defaultNow().notNull(), - updatedAt: timestamp("updated_at") - .defaultNow() - .$onUpdate(() => /* @__PURE__ */ new Date()) - .notNull(), - }, - (table) => [index("verification_identifier_idx").on(table.identifier)], + "verification", + { + id: text("id").primaryKey(), + identifier: text("identifier").notNull(), + value: text("value").notNull(), + expiresAt: timestamp("expires_at").notNull(), + createdAt: timestamp("created_at").defaultNow().notNull(), + updatedAt: timestamp("updated_at") + .defaultNow() + .$onUpdate(() => /* @__PURE__ */ new Date()) + .notNull(), + }, + (table) => [index("verification_identifier_idx").on(table.identifier)], ); export const organization = createTable("organization", { - id: text("id").primaryKey(), - name: text("name").notNull(), - slug: text("slug").notNull().unique(), - logo: text("logo"), - createdAt: timestamp("created_at").notNull(), - metadata: text("metadata"), + id: text("id").primaryKey(), + name: text("name").notNull(), + slug: text("slug").notNull().unique(), + logo: text("logo"), + createdAt: timestamp("created_at").notNull(), + metadata: text("metadata"), }); export const member = createTable( - "member", - { - id: text("id").primaryKey(), - organizationId: text("organization_id") - .notNull() - .references(() => organization.id, { onDelete: "cascade" }), - userId: text("user_id") - .notNull() - .references(() => user.id, { onDelete: "cascade" }), - role: text("role").default("member").notNull(), - createdAt: timestamp("created_at").notNull(), - }, - (table) => [ - index("member_organizationId_idx").on(table.organizationId), - index("member_userId_idx").on(table.userId), - ], + "member", + { + id: text("id").primaryKey(), + organizationId: text("organization_id") + .notNull() + .references(() => organization.id, { onDelete: "cascade" }), + userId: text("user_id") + .notNull() + .references(() => user.id, { onDelete: "cascade" }), + role: text("role").default("member").notNull(), + createdAt: timestamp("created_at").notNull(), + }, + (table) => [ + index("member_organizationId_idx").on(table.organizationId), + index("member_userId_idx").on(table.userId), + ], ); export const invitation = createTable( - "invitation", - { - id: text("id").primaryKey(), - organizationId: text("organization_id") - .notNull() - .references(() => organization.id, { onDelete: "cascade" }), - email: text("email").notNull(), - role: text("role"), - status: text("status").default("pending").notNull(), - expiresAt: timestamp("expires_at").notNull(), - createdAt: timestamp("created_at").defaultNow().notNull(), - inviterId: text("inviter_id") - .notNull() - .references(() => user.id, { onDelete: "cascade" }), - }, - (table) => [ - index("invitation_organizationId_idx").on(table.organizationId), - index("invitation_email_idx").on(table.email), - ], + "invitation", + { + id: text("id").primaryKey(), + organizationId: text("organization_id") + .notNull() + .references(() => organization.id, { onDelete: "cascade" }), + email: text("email").notNull(), + role: text("role"), + status: text("status").default("pending").notNull(), + expiresAt: timestamp("expires_at").notNull(), + createdAt: timestamp("created_at").defaultNow().notNull(), + inviterId: text("inviter_id") + .notNull() + .references(() => user.id, { onDelete: "cascade" }), + }, + (table) => [ + index("invitation_organizationId_idx").on(table.organizationId), + index("invitation_email_idx").on(table.email), + ], ); export const userRelations = relations(user, ({ many }) => ({ - sessions: many(session), - accounts: many(account), - members: many(member), - invitations: many(invitation), + sessions: many(session), + accounts: many(account), + members: many(member), + invitations: many(invitation), })); export const sessionRelations = relations(session, ({ one }) => ({ - user: one(user, { - fields: [session.userId], - references: [user.id], - }), + user: one(user, { + fields: [session.userId], + references: [user.id], + }), })); export const accountRelations = relations(account, ({ one }) => ({ - user: one(user, { - fields: [account.userId], - references: [user.id], - }), + user: one(user, { + fields: [account.userId], + references: [user.id], + }), })); export const organizationRelations = relations(organization, ({ many }) => ({ - members: many(member), - invitations: many(invitation), + members: many(member), + invitations: many(invitation), })); export const memberRelations = relations(member, ({ one }) => ({ - organization: one(organization, { - fields: [member.organizationId], - references: [organization.id], - }), - user: one(user, { - fields: [member.userId], - references: [user.id], - }), + organization: one(organization, { + fields: [member.organizationId], + references: [organization.id], + }), + user: one(user, { + fields: [member.userId], + references: [user.id], + }), })); export const invitationRelations = relations(invitation, ({ one }) => ({ - organization: one(organization, { - fields: [invitation.organizationId], - references: [organization.id], - }), - user: one(user, { - fields: [invitation.inviterId], - references: [user.id], - }), + organization: one(organization, { + fields: [invitation.organizationId], + references: [organization.id], + }), + user: one(user, { + fields: [invitation.inviterId], + references: [user.id], + }), })); export type UserSelectType = InferSelectModel; diff --git a/src/server/db/schema.ts b/src/server/db/schema.ts index 0f82868..723937a 100644 --- a/src/server/db/schema.ts +++ b/src/server/db/schema.ts @@ -5,6 +5,7 @@ import { pgTableCreator, text, timestamp, + uniqueIndex, uuid, } from "drizzle-orm/pg-core"; import { organization, user } from "./auth-schema"; @@ -35,17 +36,73 @@ export const judgingRounds = createTable("judging_round", { .notNull(), }); +export const judgingRooms = createTable("judging_room", { + id: uuid("id").primaryKey().defaultRandom(), + roundId: uuid("round_id") + .references(() => judgingRounds.id, { onDelete: "cascade" }) + .notNull(), + roomLink: text("room_link").notNull(), // link to the video meeting + createdAt: timestamp("created_at", { withTimezone: true }) + .defaultNow() + .notNull(), + updatedAt: timestamp("updated_at", { withTimezone: true }) + .defaultNow() + .$onUpdate(() => new Date()) + .notNull(), +}); + +export const judgingRoomJudges = createTable( + "judging_room_judge", + { + id: uuid("id").primaryKey().defaultRandom(), + roomId: uuid("room_id") + .notNull() + .references(() => judgingRooms.id, { onDelete: "cascade" }), + judgeId: text("judge_id") + .notNull() + .references(() => user.id, { onDelete: "cascade" }), + createdAt: timestamp("created_at", { withTimezone: true }) + .defaultNow() + .notNull(), + }, + (table) => [ + uniqueIndex("judging_room_judge_room_judge_uniq").on( + table.roomId, + table.judgeId, + ), + ], +); + +export const judgingRoomAdmins = createTable( + "judging_room_admin", + { + id: uuid("id").primaryKey().defaultRandom(), + roomId: uuid("room_id") + .notNull() + .references(() => judgingRooms.id, { onDelete: "cascade" }), + adminId: text("admin_id") + .notNull() + .references(() => user.id, { onDelete: "cascade" }), + createdAt: timestamp("created_at", { withTimezone: true }) + .defaultNow() + .notNull(), + }, + (table) => [ + uniqueIndex("judging_room_admin_room_admin_uniq").on( + table.roomId, + table.adminId, + ), + ], +); + export const judgingAssignments = createTable("judging_assignment", { id: uuid("id").primaryKey().defaultRandom(), - judgeId: text("judge_id") - .notNull() - .references(() => user.id, { onDelete: "cascade" }), teamId: text("team_id") .notNull() .references(() => organization.id, { onDelete: "cascade" }), - roundId: uuid("round_id") + roomId: uuid("room_id") .notNull() - .references(() => judgingRounds.id, { onDelete: "cascade" }), + .references(() => judgingRooms.id, { onDelete: "cascade" }), timeSlot: timestamp("time_slot", { withTimezone: true }), createdAt: timestamp("created_at", { withTimezone: true }) .defaultNow() @@ -70,20 +127,22 @@ export const judgingRoundRelations = relations(judgingRounds, ({ many }) => ({ assignments: many(judgingAssignments), })); +// each judging room can have many admins and many judges +export const judgingRoomRelations = relations(judgingRooms, ({ many }) => ({ + admins: many(judgingRoomAdmins), + judges: many(judgingRoomJudges), +})); + export const judgingAssignmentRelations = relations( judgingAssignments, ({ one, many }) => ({ - judge: one(user, { - fields: [judgingAssignments.judgeId], - references: [user.id], - }), team: one(organization, { fields: [judgingAssignments.teamId], references: [organization.id], }), - round: one(judgingRounds, { - fields: [judgingAssignments.roundId], - references: [judgingRounds.id], + room: one(judgingRooms, { + fields: [judgingAssignments.roomId], + references: [judgingRooms.id], }), scores: many(scores), }),