// src/lib/server/db/auth.schema.ts import { relations } from 'drizzle-orm'; import { pgTable, text, timestamp, boolean, index, integer, uniqueIndex } from 'drizzle-orm/pg-core'; import { jsonb } from './jsonb'; import type { ConnectionProfile } from '#lib/auth-identity.js'; export const user = pgTable('user', { id: text('id').primaryKey(), name: text('name').notNull(), // Only actual contact addresses belong here. OAuth identity lives in account. email: text('contact_email').unique(), // Better Auth's required email field is mapped to this private compatibility field. authEmail: text('email').notNull().unique(), emailVerified: boolean('email_verified').default(false).notNull(), image: text('image'), // ── Staff role: 'player' | 'referee' | 'admin' ── role: text('role').default('player').notNull(), rootAdmin: boolean('root_admin').default(false).notNull(), mergedIntoUserId: text('merged_into_user_id'), osuId: integer('osu_id'), countryCode: text('country_code'), discordId: text('discord_id'), bannedAt: timestamp('banned_at', { withTimezone: true }), timezone: text('timezone'), /** Local hours (0–23) the player can play. Empty means no preference. */ availableHours: jsonb('available_hours').$type(), createdAt: timestamp('created_at').defaultNow().notNull(), updatedAt: timestamp('updated_at') .defaultNow() .$onUpdate(() => new Date()) .notNull() }); export const session = pgTable( '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(() => new Date()) .notNull(), ipAddress: text('ip_address'), userAgent: text('user_agent'), userId: text('user_id') .notNull() .references(() => user.id, { onDelete: 'cascade' }) }, (table) => [index('session_userId_idx').on(table.userId)] ); export const account = pgTable( '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'), profile: jsonb('profile').$type(), createdAt: timestamp('created_at').defaultNow().notNull(), updatedAt: timestamp('updated_at') .$onUpdate(() => new Date()) .notNull() }, (table) => [ index('account_userId_idx').on(table.userId), uniqueIndex('account_provider_identity_idx').on(table.providerId, table.accountId), uniqueIndex('account_user_provider_idx').on(table.userId, table.providerId) ] ); export const verification = pgTable( '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(() => new Date()) .notNull() }, (table) => [index('verification_identifier_idx').on(table.identifier)] ); export const userRelations = relations(user, ({ many }) => ({ sessions: many(session), accounts: many(account) })); export const sessionRelations = relations(session, ({ one }) => ({ user: one(user, { fields: [session.userId], references: [user.id] }) })); export const accountRelations = relations(account, ({ one }) => ({ user: one(user, { fields: [account.userId], references: [user.id] }) }));