620 lines
24 KiB
TypeScript
620 lines
24 KiB
TypeScript
import {
|
|
pgTable,
|
|
integer,
|
|
text,
|
|
uuid,
|
|
boolean,
|
|
timestamp,
|
|
jsonb,
|
|
index,
|
|
uniqueIndex,
|
|
real
|
|
} from 'drizzle-orm/pg-core';
|
|
import { relations } from 'drizzle-orm';
|
|
|
|
export * from './auth.schema';
|
|
import { user } from './auth.schema';
|
|
|
|
// ── Teams ───────────────────────────────────────────────────────────────
|
|
export const team = pgTable('team', {
|
|
id: uuid('id').primaryKey().defaultRandom(),
|
|
name: text('name').notNull(),
|
|
isPersonal: boolean('is_personal').default(false).notNull(),
|
|
ownerId: text('owner_id'),
|
|
avatarUrl: text('avatar_url'),
|
|
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull()
|
|
});
|
|
|
|
// ── Team Members ────────────────────────────────────────────────────────
|
|
export const teamMember = pgTable('team_member', {
|
|
id: uuid('id').primaryKey().defaultRandom(),
|
|
teamId: uuid('team_id')
|
|
.notNull()
|
|
.references(() => team.id, { onDelete: 'cascade' }),
|
|
userId: text('user_id').notNull(),
|
|
role: text('role').default('member'),
|
|
joinedAt: timestamp('joined_at', { withTimezone: true }).defaultNow()
|
|
});
|
|
|
|
// ── Mappools ────────────────────────────────────────────────────────────
|
|
export const mappool = pgTable('mappool', {
|
|
id: uuid('id').primaryKey().defaultRandom(),
|
|
name: text('name').notNull(),
|
|
createdBy: text('created_by'),
|
|
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
|
verifiedAt: timestamp('verified_at', { withTimezone: true })
|
|
});
|
|
|
|
// ── Mappool Slots ───────────────────────────────────────────────────────
|
|
export const mappoolSlot = pgTable(
|
|
'mappool_slot',
|
|
{
|
|
id: uuid('id').primaryKey().defaultRandom(),
|
|
mappoolId: uuid('mappool_id')
|
|
.notNull()
|
|
.references(() => mappool.id, { onDelete: 'cascade' }),
|
|
category: text('category').notNull(),
|
|
orderInCategory: integer('order_in_category').notNull(),
|
|
beatmapId: text('beatmap_id').notNull(),
|
|
starRating: real('star_rating'),
|
|
bpm: real('bpm'),
|
|
totalLength: integer('total_length'),
|
|
mods: text('mods').array().default([]).notNull(),
|
|
// ── Cached beatmap metadata (stored at insert time → zero API calls on page load) ──
|
|
title: text('title'),
|
|
artist: text('artist'),
|
|
version: text('version'),
|
|
coverUrl: text('cover_url'),
|
|
listCoverUrl: text('list_cover_url')
|
|
},
|
|
(t) => [index('idx_mappool_slot_mappool').on(t.mappoolId)]
|
|
);
|
|
|
|
// ── Matches ─────────────────────────────────────────────────────────────
|
|
export const match = pgTable(
|
|
'match',
|
|
{
|
|
id: uuid('id').primaryKey().defaultRandom(),
|
|
name: text('name'),
|
|
state: text('state').default('CREATED').notNull(),
|
|
config: jsonb('config').notNull(),
|
|
mappoolId: uuid('mappool_id').references(() => mappool.id),
|
|
osuLobbyId: integer('osu_lobby_id'),
|
|
scheduledAt: timestamp('scheduled_at', { withTimezone: true }),
|
|
startedAt: timestamp('started_at', { withTimezone: true }),
|
|
finishedAt: timestamp('finished_at', { withTimezone: true }),
|
|
winnerId: uuid('winner_id').references(() => team.id),
|
|
createdBy: text('created_by'),
|
|
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull()
|
|
},
|
|
(t) => [index('idx_match_state').on(t.state), index('idx_match_created_at').on(t.createdAt)]
|
|
);
|
|
|
|
// ── Match Participants (a team in a match) ──────────────────────────────
|
|
export const matchParticipant = pgTable(
|
|
'match_participant',
|
|
{
|
|
id: uuid('id').primaryKey().defaultRandom(),
|
|
matchId: uuid('match_id')
|
|
.notNull()
|
|
.references(() => match.id, { onDelete: 'cascade' }),
|
|
teamId: uuid('team_id')
|
|
.notNull()
|
|
.references(() => team.id),
|
|
slot: integer('slot').notNull(),
|
|
score: integer('score').default(0).notNull(),
|
|
rollValue: integer('roll_value'),
|
|
pickOrder: integer('pick_order')
|
|
},
|
|
(t) => [
|
|
index('idx_mp_match_team').on(t.matchId, t.teamId),
|
|
uniqueIndex('idx_mp_match_slot').on(t.matchId, t.slot),
|
|
index('idx_mp_match').on(t.matchId)
|
|
]
|
|
);
|
|
|
|
// ── Match Participant Players (a player fielded for this match) ─────────
|
|
export const matchParticipantPlayer = pgTable(
|
|
'match_participant_player',
|
|
{
|
|
id: uuid('id').primaryKey().defaultRandom(),
|
|
participantId: uuid('participant_id')
|
|
.notNull()
|
|
.references(() => matchParticipant.id, { onDelete: 'cascade' }),
|
|
userId: text('user_id').notNull()
|
|
},
|
|
(t) => [
|
|
uniqueIndex('idx_mpp_participant_user').on(t.participantId, t.userId),
|
|
index('idx_mpp_participant').on(t.participantId),
|
|
index('idx_mpp_user').on(t.userId)
|
|
]
|
|
);
|
|
|
|
// ── Match Games ─────────────────────────────────────────────────────────
|
|
export const matchGame = pgTable(
|
|
'match_game',
|
|
{
|
|
id: uuid('id').primaryKey().defaultRandom(),
|
|
matchId: uuid('match_id')
|
|
.notNull()
|
|
.references(() => match.id, { onDelete: 'cascade' }),
|
|
gameNumber: integer('game_number').notNull(),
|
|
mappoolSlotId: uuid('mappool_slot_id')
|
|
.notNull()
|
|
.references(() => mappoolSlot.id),
|
|
pickedByParticipantId: uuid('picked_by_participant_id').references(() => matchParticipant.id),
|
|
winnerParticipantId: uuid('winner_participant_id').references(() => matchParticipant.id),
|
|
state: text('state').default('PENDING').notNull(),
|
|
startedAt: timestamp('started_at', { withTimezone: true }),
|
|
finishedAt: timestamp('finished_at', { withTimezone: true })
|
|
},
|
|
(t) => [index('idx_match_game_match').on(t.matchId)]
|
|
);
|
|
|
|
// ── Match Game Scores (per-player per-map) ──────────────────────────────
|
|
export const matchGameScore = pgTable(
|
|
'match_game_score',
|
|
{
|
|
id: uuid('id').primaryKey().defaultRandom(),
|
|
matchGameId: uuid('match_game_id')
|
|
.notNull()
|
|
.references(() => matchGame.id, { onDelete: 'cascade' }),
|
|
playerId: uuid('player_id')
|
|
.notNull()
|
|
.references(() => matchParticipantPlayer.id),
|
|
score: integer('score').default(0).notNull(),
|
|
accuracy: real('accuracy').default(0).notNull(),
|
|
maxCombo: integer('max_combo').default(0).notNull(),
|
|
count300: integer('count_300').default(0).notNull(),
|
|
count100: integer('count_100').default(0).notNull(),
|
|
count50: integer('count_50').default(0).notNull(),
|
|
countMiss: integer('count_miss').default(0).notNull(),
|
|
mods: text('mods').array().default([]).notNull(),
|
|
passed: boolean('passed').default(false).notNull(),
|
|
pp: real('pp')
|
|
},
|
|
(t) => [index('idx_mgs_game').on(t.matchGameId)]
|
|
);
|
|
|
|
// ── Match Queue ─────────────────────────────────────────────────────────
|
|
export const matchQueue = pgTable(
|
|
'match_queue',
|
|
{
|
|
id: uuid('id').primaryKey().defaultRandom(),
|
|
userId: text('user_id').notNull(),
|
|
teamId: uuid('team_id')
|
|
.notNull()
|
|
.references(() => team.id),
|
|
elo: integer('elo').default(1000).notNull(),
|
|
joinedAt: timestamp('joined_at', { withTimezone: true }).defaultNow().notNull()
|
|
},
|
|
(t) => [uniqueIndex('idx_queue_user').on(t.userId), index('idx_queue_elo').on(t.elo)]
|
|
);
|
|
|
|
// ── Player ELO ──────────────────────────────────────────────────────────
|
|
export const playerRating = pgTable('player_rating', {
|
|
id: uuid('id').primaryKey().defaultRandom(),
|
|
userId: text('user_id').notNull().unique(),
|
|
elo: integer('elo').default(1000).notNull(),
|
|
wins: integer('wins').default(0).notNull(),
|
|
losses: integer('losses').default(0).notNull(),
|
|
initialElo: integer('initial_elo'),
|
|
osuRankAtSeed: integer('osu_rank_at_seed'),
|
|
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull()
|
|
});
|
|
|
|
// ── Notifications ────────────────────────────────────────────────────
|
|
export const notification = pgTable(
|
|
'notification',
|
|
{
|
|
id: uuid('id').primaryKey().defaultRandom(),
|
|
userId: text('user_id').notNull(),
|
|
type: text('type').notNull(),
|
|
title: text('title').notNull(),
|
|
message: text('message'),
|
|
referenceId: text('reference_id'),
|
|
read: boolean('read').default(false).notNull(),
|
|
actionedAt: timestamp('actioned_at', { withTimezone: true }),
|
|
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull()
|
|
},
|
|
(t) => [
|
|
index('idx_notification_user').on(t.userId),
|
|
index('idx_notification_user_read').on(t.userId, t.read)
|
|
]
|
|
);
|
|
|
|
// ── Match Invites ────────────────────────────────────────────────────
|
|
export const matchInvite = pgTable(
|
|
'match_invite',
|
|
{
|
|
id: uuid('id').primaryKey().defaultRandom(),
|
|
createdBy: text('created_by').notNull(),
|
|
creatorTeamId: uuid('creator_team_id')
|
|
.notNull()
|
|
.references(() => team.id),
|
|
invitedTeamId: uuid('invited_team_id')
|
|
.notNull()
|
|
.references(() => team.id),
|
|
config: jsonb('config').notNull(),
|
|
mappoolId: uuid('mappool_id')
|
|
.notNull()
|
|
.references(() => mappool.id),
|
|
name: text('name'),
|
|
message: text('message'),
|
|
scheduledAt: timestamp('scheduled_at', { withTimezone: true }),
|
|
expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(),
|
|
status: text('status').default('pending').notNull(),
|
|
matchId: uuid('match_id').references(() => match.id),
|
|
respondedAt: timestamp('responded_at', { withTimezone: true }),
|
|
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull()
|
|
},
|
|
(t) => [
|
|
index('idx_invite_invited_team').on(t.invitedTeamId),
|
|
index('idx_invite_created_by').on(t.createdBy),
|
|
index('idx_invite_status').on(t.status)
|
|
]
|
|
);
|
|
|
|
// ── Tournaments ────────────────────────────────────────────────────────
|
|
export const tournament = pgTable(
|
|
'tournament',
|
|
{
|
|
id: uuid('id').primaryKey().defaultRandom(),
|
|
name: text('name').notNull(),
|
|
description: text('description'),
|
|
format: text('format').notNull(), // single_elim | double_elim | groups_bracket
|
|
state: text('state').default('DRAFT').notNull(),
|
|
config: jsonb('config').notNull(),
|
|
maxSlots: integer('max_slots').notNull(),
|
|
bannerUrl: text('banner_url'),
|
|
createdBy: text('created_by').notNull(),
|
|
registrationOpenAt: timestamp('registration_open_at', { withTimezone: true }),
|
|
registrationCloseAt: timestamp('registration_close_at', { withTimezone: true }),
|
|
startAt: timestamp('start_at', { withTimezone: true }),
|
|
finishedAt: timestamp('finished_at', { withTimezone: true }),
|
|
winnerId: uuid('winner_id').references(() => team.id),
|
|
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull()
|
|
},
|
|
(t) => [
|
|
index('idx_tournament_state').on(t.state),
|
|
index('idx_tournament_created_at').on(t.createdAt)
|
|
]
|
|
);
|
|
|
|
// ── Tournament Staff ───────────────────────────────────────────────────
|
|
export const tournamentStaff = pgTable(
|
|
'tournament_staff',
|
|
{
|
|
id: uuid('id').primaryKey().defaultRandom(),
|
|
tournamentId: uuid('tournament_id')
|
|
.notNull()
|
|
.references(() => tournament.id, { onDelete: 'cascade' }),
|
|
userId: text('user_id').notNull(),
|
|
role: text('role').notNull() // organizer | admin | referee | pooler | streamer
|
|
},
|
|
(t) => [
|
|
uniqueIndex('idx_tstaff_tournament_user').on(t.tournamentId, t.userId),
|
|
index('idx_tstaff_tournament').on(t.tournamentId)
|
|
]
|
|
);
|
|
|
|
// ── Tournament Rounds ──────────────────────────────────────────────────
|
|
export const tournamentRound = pgTable(
|
|
'tournament_round',
|
|
{
|
|
id: uuid('id').primaryKey().defaultRandom(),
|
|
tournamentId: uuid('tournament_id')
|
|
.notNull()
|
|
.references(() => tournament.id, { onDelete: 'cascade' }),
|
|
name: text('name').notNull(),
|
|
abbreviation: text('abbreviation'),
|
|
roundOrder: integer('round_order').notNull(),
|
|
bestOf: integer('best_of').notNull(),
|
|
mappoolId: uuid('mappool_id').references(() => mappool.id),
|
|
bracketType: text('bracket_type'), // winners | losers | grand_final | group | null
|
|
scheduledAt: timestamp('scheduled_at', { withTimezone: true })
|
|
},
|
|
(t) => [index('idx_tround_tournament').on(t.tournamentId)]
|
|
);
|
|
|
|
// ── Tournament Registrations ───────────────────────────────────────────
|
|
export const tournamentRegistration = pgTable(
|
|
'tournament_registration',
|
|
{
|
|
id: uuid('id').primaryKey().defaultRandom(),
|
|
tournamentId: uuid('tournament_id')
|
|
.notNull()
|
|
.references(() => tournament.id, { onDelete: 'cascade' }),
|
|
teamId: uuid('team_id')
|
|
.notNull()
|
|
.references(() => team.id),
|
|
userId: text('user_id').notNull(),
|
|
seed: integer('seed'),
|
|
qualifierScore: real('qualifier_score'),
|
|
status: text('status').default('registered').notNull(),
|
|
registeredAt: timestamp('registered_at', { withTimezone: true }).defaultNow().notNull()
|
|
},
|
|
(t) => [
|
|
uniqueIndex('idx_treg_tournament_team').on(t.tournamentId, t.teamId),
|
|
index('idx_treg_tournament').on(t.tournamentId)
|
|
]
|
|
);
|
|
|
|
// ── Tournament Matches (bracket structure) ─────────────────────────────
|
|
export const tournamentMatch = pgTable(
|
|
'tournament_match',
|
|
{
|
|
id: uuid('id').primaryKey().defaultRandom(),
|
|
tournamentId: uuid('tournament_id')
|
|
.notNull()
|
|
.references(() => tournament.id, { onDelete: 'cascade' }),
|
|
roundId: uuid('round_id')
|
|
.notNull()
|
|
.references(() => tournamentRound.id, { onDelete: 'cascade' }),
|
|
matchId: uuid('match_id').references(() => match.id),
|
|
bracketPosition: integer('bracket_position').notNull(),
|
|
winnerGoesToId: uuid('winner_goes_to_id'),
|
|
loserGoesToId: uuid('loser_goes_to_id'),
|
|
winnerSlot: integer('winner_slot'),
|
|
loserSlot: integer('loser_slot'),
|
|
team1Id: uuid('team1_id').references(() => team.id),
|
|
team2Id: uuid('team2_id').references(() => team.id),
|
|
winnerId: uuid('winner_id').references(() => team.id),
|
|
scheduledAt: timestamp('scheduled_at', { withTimezone: true }),
|
|
state: text('state').default('PENDING').notNull()
|
|
},
|
|
(t) => [
|
|
index('idx_tmatch_tournament').on(t.tournamentId),
|
|
index('idx_tmatch_round').on(t.roundId),
|
|
index('idx_tmatch_match').on(t.matchId),
|
|
index('idx_tmatch_state').on(t.state)
|
|
]
|
|
);
|
|
|
|
// ── Tournament Groups ──────────────────────────────────────────────────
|
|
export const tournamentGroup = pgTable(
|
|
'tournament_group',
|
|
{
|
|
id: uuid('id').primaryKey().defaultRandom(),
|
|
tournamentId: uuid('tournament_id')
|
|
.notNull()
|
|
.references(() => tournament.id, { onDelete: 'cascade' }),
|
|
name: text('name').notNull(),
|
|
groupOrder: integer('group_order').notNull()
|
|
},
|
|
(t) => [index('idx_tgroup_tournament').on(t.tournamentId)]
|
|
);
|
|
|
|
// ── Tournament Group Entries ───────────────────────────────────────────
|
|
export const tournamentGroupEntry = pgTable(
|
|
'tournament_group_entry',
|
|
{
|
|
id: uuid('id').primaryKey().defaultRandom(),
|
|
groupId: uuid('group_id')
|
|
.notNull()
|
|
.references(() => tournamentGroup.id, { onDelete: 'cascade' }),
|
|
teamId: uuid('team_id')
|
|
.notNull()
|
|
.references(() => team.id),
|
|
wins: integer('wins').default(0).notNull(),
|
|
losses: integer('losses').default(0).notNull(),
|
|
mapWins: integer('map_wins').default(0).notNull(),
|
|
mapLosses: integer('map_losses').default(0).notNull(),
|
|
seed: integer('seed')
|
|
},
|
|
(t) => [
|
|
uniqueIndex('idx_tge_group_team').on(t.groupId, t.teamId),
|
|
index('idx_tge_group').on(t.groupId)
|
|
]
|
|
);
|
|
|
|
// ── Tournament Qualifier Scores ────────────────────────────────────────
|
|
export const tournamentQualifierScore = pgTable(
|
|
'tournament_qualifier_score',
|
|
{
|
|
id: uuid('id').primaryKey().defaultRandom(),
|
|
tournamentId: uuid('tournament_id')
|
|
.notNull()
|
|
.references(() => tournament.id, { onDelete: 'cascade' }),
|
|
teamId: uuid('team_id')
|
|
.notNull()
|
|
.references(() => team.id),
|
|
mappoolSlotId: uuid('mappool_slot_id')
|
|
.notNull()
|
|
.references(() => mappoolSlot.id),
|
|
totalScore: integer('total_score').notNull(),
|
|
accuracy: real('accuracy'),
|
|
playedAt: timestamp('played_at', { withTimezone: true }).defaultNow().notNull()
|
|
},
|
|
(t) => [
|
|
index('idx_tqs_tournament_team').on(t.tournamentId, t.teamId),
|
|
index('idx_tqs_tournament').on(t.tournamentId)
|
|
]
|
|
);
|
|
|
|
// ── Relations ───────────────────────────────────────────────────────────
|
|
|
|
export const teamRelations = relations(team, ({ many }) => ({
|
|
members: many(teamMember)
|
|
}));
|
|
|
|
export const teamMemberRelations = relations(teamMember, ({ one }) => ({
|
|
team: one(team, { fields: [teamMember.teamId], references: [team.id] })
|
|
}));
|
|
|
|
export const mappoolRelations = relations(mappool, ({ many }) => ({
|
|
slots: many(mappoolSlot)
|
|
}));
|
|
|
|
export const mappoolSlotRelations = relations(mappoolSlot, ({ one }) => ({
|
|
mappool: one(mappool, { fields: [mappoolSlot.mappoolId], references: [mappool.id] })
|
|
}));
|
|
|
|
export const matchRelations = relations(match, ({ one, many }) => ({
|
|
mappool: one(mappool, { fields: [match.mappoolId], references: [mappool.id] }),
|
|
participants: many(matchParticipant),
|
|
games: many(matchGame)
|
|
}));
|
|
|
|
export const matchParticipantRelations = relations(matchParticipant, ({ one, many }) => ({
|
|
match: one(match, { fields: [matchParticipant.matchId], references: [match.id] }),
|
|
team: one(team, { fields: [matchParticipant.teamId], references: [team.id] }),
|
|
players: many(matchParticipantPlayer)
|
|
}));
|
|
|
|
export const matchParticipantPlayerRelations = relations(matchParticipantPlayer, ({ one }) => ({
|
|
participant: one(matchParticipant, {
|
|
fields: [matchParticipantPlayer.participantId],
|
|
references: [matchParticipant.id]
|
|
})
|
|
}));
|
|
|
|
export const matchGameRelations = relations(matchGame, ({ one, many }) => ({
|
|
match: one(match, { fields: [matchGame.matchId], references: [match.id] }),
|
|
slot: one(mappoolSlot, { fields: [matchGame.mappoolSlotId], references: [mappoolSlot.id] }),
|
|
scores: many(matchGameScore)
|
|
}));
|
|
|
|
export const matchGameScoreRelations = relations(matchGameScore, ({ one }) => ({
|
|
game: one(matchGame, { fields: [matchGameScore.matchGameId], references: [matchGame.id] }),
|
|
player: one(matchParticipantPlayer, {
|
|
fields: [matchGameScore.playerId],
|
|
references: [matchParticipantPlayer.id]
|
|
})
|
|
}));
|
|
|
|
export const matchQueueRelations = relations(matchQueue, ({ one }) => ({
|
|
team: one(team, { fields: [matchQueue.teamId], references: [team.id] })
|
|
}));
|
|
|
|
export const notificationRelations = relations(notification, ({}) => ({}));
|
|
|
|
export const matchInviteRelations = relations(matchInvite, ({ one }) => ({
|
|
creatorTeam: one(team, {
|
|
fields: [matchInvite.creatorTeamId],
|
|
references: [team.id],
|
|
relationName: 'inviteCreatorTeam'
|
|
}),
|
|
invitedTeam: one(team, {
|
|
fields: [matchInvite.invitedTeamId],
|
|
references: [team.id],
|
|
relationName: 'inviteInvitedTeam'
|
|
}),
|
|
mappool: one(mappool, { fields: [matchInvite.mappoolId], references: [mappool.id] }),
|
|
match: one(match, { fields: [matchInvite.matchId], references: [match.id] })
|
|
}));
|
|
|
|
// ── Tournament Relations ────────────────────────────────────────────────
|
|
|
|
export const tournamentRelations = relations(tournament, ({ many }) => ({
|
|
rounds: many(tournamentRound),
|
|
registrations: many(tournamentRegistration),
|
|
matches: many(tournamentMatch),
|
|
groups: many(tournamentGroup),
|
|
staff: many(tournamentStaff),
|
|
qualifierScores: many(tournamentQualifierScore)
|
|
}));
|
|
|
|
export const tournamentStaffRelations = relations(tournamentStaff, ({ one }) => ({
|
|
tournament: one(tournament, {
|
|
fields: [tournamentStaff.tournamentId],
|
|
references: [tournament.id]
|
|
})
|
|
}));
|
|
|
|
export const tournamentRoundRelations = relations(tournamentRound, ({ one, many }) => ({
|
|
tournament: one(tournament, {
|
|
fields: [tournamentRound.tournamentId],
|
|
references: [tournament.id]
|
|
}),
|
|
mappool: one(mappool, { fields: [tournamentRound.mappoolId], references: [mappool.id] }),
|
|
matches: many(tournamentMatch)
|
|
}));
|
|
|
|
export const tournamentRegistrationRelations = relations(tournamentRegistration, ({ one }) => ({
|
|
tournament: one(tournament, {
|
|
fields: [tournamentRegistration.tournamentId],
|
|
references: [tournament.id]
|
|
}),
|
|
team: one(team, { fields: [tournamentRegistration.teamId], references: [team.id] })
|
|
}));
|
|
|
|
export const tournamentMatchRelations = relations(tournamentMatch, ({ one }) => ({
|
|
tournament: one(tournament, {
|
|
fields: [tournamentMatch.tournamentId],
|
|
references: [tournament.id]
|
|
}),
|
|
round: one(tournamentRound, {
|
|
fields: [tournamentMatch.roundId],
|
|
references: [tournamentRound.id]
|
|
}),
|
|
match: one(match, { fields: [tournamentMatch.matchId], references: [match.id] }),
|
|
team1: one(team, {
|
|
fields: [tournamentMatch.team1Id],
|
|
references: [team.id],
|
|
relationName: 'tournamentMatchTeam1'
|
|
}),
|
|
team2: one(team, {
|
|
fields: [tournamentMatch.team2Id],
|
|
references: [team.id],
|
|
relationName: 'tournamentMatchTeam2'
|
|
}),
|
|
winner: one(team, {
|
|
fields: [tournamentMatch.winnerId],
|
|
references: [team.id],
|
|
relationName: 'tournamentMatchWinner'
|
|
})
|
|
}));
|
|
|
|
export const tournamentGroupRelations = relations(tournamentGroup, ({ one, many }) => ({
|
|
tournament: one(tournament, {
|
|
fields: [tournamentGroup.tournamentId],
|
|
references: [tournament.id]
|
|
}),
|
|
entries: many(tournamentGroupEntry)
|
|
}));
|
|
|
|
export const tournamentGroupEntryRelations = relations(tournamentGroupEntry, ({ one }) => ({
|
|
group: one(tournamentGroup, {
|
|
fields: [tournamentGroupEntry.groupId],
|
|
references: [tournamentGroup.id]
|
|
}),
|
|
team: one(team, { fields: [tournamentGroupEntry.teamId], references: [team.id] })
|
|
}));
|
|
|
|
// ── Profile Comments ────────────────────────────────────────────────────
|
|
export const profileComment = pgTable(
|
|
'profile_comment',
|
|
{
|
|
id: uuid('id').primaryKey().defaultRandom(),
|
|
// 'user' or 'team'
|
|
targetType: text('target_type').notNull(),
|
|
targetId: text('target_id').notNull(),
|
|
authorId: text('author_id')
|
|
.notNull()
|
|
.references(() => user.id, { onDelete: 'cascade' }),
|
|
content: text('content').notNull(),
|
|
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull()
|
|
},
|
|
(t) => [
|
|
index('idx_profile_comment_target').on(t.targetType, t.targetId),
|
|
index('idx_profile_comment_author').on(t.authorId)
|
|
]
|
|
);
|
|
|
|
export const profileCommentRelations = relations(profileComment, ({ one }) => ({
|
|
author: one(user, { fields: [profileComment.authorId], references: [user.id] })
|
|
}));
|
|
|
|
export const tournamentQualifierScoreRelations = relations(tournamentQualifierScore, ({ one }) => ({
|
|
tournament: one(tournament, {
|
|
fields: [tournamentQualifierScore.tournamentId],
|
|
references: [tournament.id]
|
|
}),
|
|
team: one(team, { fields: [tournamentQualifierScore.teamId], references: [team.id] }),
|
|
slot: one(mappoolSlot, {
|
|
fields: [tournamentQualifierScore.mappoolSlotId],
|
|
references: [mappoolSlot.id]
|
|
})
|
|
}));
|