sendou.ink/app/db/tables.ts
Kalle 77978c450f
Some checks are pending
E2E Tests / e2e (push) Waiting to run
Tests and checks on push / run-checks-and-tests (push) Waiting to run
Updates translation progress / update-translation-progress-issue (push) Waiting to run
New user page (#2812)
Co-authored-by: hfcRed <hfcred@gmx.net>
2026-02-16 19:26:57 +02:00

1249 lines
32 KiB
TypeScript

import type {
ColumnType,
GeneratedAlways,
Insertable,
JSONColumnType,
Selectable,
Updateable,
} from "kysely";
import type { AssociationVisibility } from "~/features/associations/associations-types";
import type { tags } from "~/features/calendar/calendar-constants";
import type { CalendarFilters } from "~/features/calendar/calendar-types";
import type { TieredSkill } from "~/features/mmr/tiered.server";
import type { Notification as NotificationValue } from "~/features/notifications/notifications-types";
import type { ScrimFilters } from "~/features/scrims/scrims-types";
import type { TEAM_MEMBER_ROLES } from "~/features/team/team-constants";
import type { TournamentTierNumber } from "~/features/tournament/core/tiering";
import type * as PickBan from "~/features/tournament-bracket/core/PickBan";
import type * as Progression from "~/features/tournament-bracket/core/Progression";
import type { StoredWidget } from "~/features/user-page/core/widgets/types";
import type { ParticipantResult } from "~/modules/brackets-model";
import type {
Ability,
MainWeaponId,
ModeShort,
StageId,
} from "~/modules/in-game-lists/types";
import type { JSONColumnTypeNullable } from "~/utils/kysely.server";
type Generated<T> =
T extends ColumnType<infer S, infer I, infer U>
? ColumnType<S, I | undefined, U>
: ColumnType<T, T | undefined, T>;
export type MemberRole = (typeof TEAM_MEMBER_ROLES)[number];
/** In SQLite booleans are presented as 0 (false) and 1 (true) */
export type DBBoolean = number;
export interface Team {
avatarImgId: number | null;
bannerImgId: number | null;
bio: string | null;
createdAt: Generated<number>;
css: JSONColumnTypeNullable<Record<string, string>>;
customUrl: string;
deletedAt: number | null;
id: GeneratedAlways<number>;
inviteCode: string;
name: string;
bsky: string | null;
/** Team's tag, typically used in-game in front of users' names to indicate they are a member of the team. */
tag: string | null;
}
export interface TeamMember {
createdAt: Generated<number>;
isOwner: Generated<number>;
isManager: Generated<number>;
leftAt: number | null;
role: MemberRole | null;
teamId: number;
userId: number;
isMainTeam: DBBoolean;
}
export interface Art {
authorId: number;
createdAt: Generated<number>;
description: string | null;
id: GeneratedAlways<number>;
imgId: number;
isShowcase: Generated<DBBoolean>;
}
export interface ArtTag {
authorId: number;
createdAt: Generated<number>;
id: GeneratedAlways<number>;
name: string;
}
export interface ArtUserMetadata {
artId: number;
userId: number;
}
export interface TaggedArt {
artId: number;
tagId: number;
}
export interface Badge {
id: GeneratedAlways<number>;
code: string;
displayName: string;
hue: number | null;
/** Who made the badge? If null, a legacy badge. */
authorId: number | null;
}
export interface BadgeManager {
badgeId: number;
userId: number;
}
export type BadgeOwner = {
badgeId: number;
userId: number;
/** Which tournament the badge is from, if null was added manually by a badge manager as opposed to once a tournament was finalized. */
tournamentId: number | null;
};
export interface Build {
clothesGearSplId: number;
description: string | null;
headGearSplId: number;
id: GeneratedAlways<number>;
modes: JSONColumnTypeNullable<ModeShort[]>;
ownerId: number;
private: DBBoolean | null;
shoesGearSplId: number;
title: string;
updatedAt: Generated<number>;
}
export type GearType = "HEAD" | "CLOTHES" | "SHOES";
export interface BuildAbility {
ability: Ability;
buildId: number;
gearType: GearType;
slotIndex: number;
/** 10 if main ability, 3 if sub */
abilityPoints: GeneratedAlways<number>;
}
export interface BuildWeapon {
buildId: number;
weaponSplId: MainWeaponId;
/** Has the owner of this build reached top 500 of X Rank with this weapon? Denormalized for performance reasons. */
isTop500: Generated<DBBoolean>;
/** Plus tier or 4 if none. Denormalized for performance reasons. */
tier: Generated<number>;
/** Last time the build was updated. Denormalized for performance reasons. */
updatedAt: Generated<number>;
}
export type CalendarEventTag = keyof typeof tags;
export interface CalendarEvent {
authorId: number;
bracketUrl: string;
description: string | null;
discordInviteCode: string | null;
id: GeneratedAlways<number>;
discordUrl: GeneratedAlways<string | null>;
name: string;
participantCount: number | null;
tags: string | null;
hidden: Generated<DBBoolean>;
tournamentId: number | null;
organizationId: number | null;
avatarImgId: number | null;
}
export interface CalendarEventBadge {
badgeId: number;
eventId: number;
}
export interface CalendarEventDate {
eventId: number;
id: GeneratedAlways<number>;
startTime: number;
}
export interface CalendarEventResultPlayer {
name: string | null;
teamId: number;
userId: number | null;
}
export interface CalendarEventResultTeam {
eventId: number;
id: GeneratedAlways<number>;
name: string;
placement: number;
}
export interface FreshPlusTier {
tier: number | null;
userId: number;
}
export interface Group {
chatCode: string | null;
createdAt: Generated<number>;
id: GeneratedAlways<number>;
inviteCode: string;
latestActionAt: Generated<number>;
status: "PREPARING" | "ACTIVE" | "INACTIVE";
teamId: number | null;
}
export interface GroupLike {
createdAt: Generated<number>;
likerGroupId: number;
targetGroupId: number;
isRechallenge: DBBoolean | null;
}
type CalculatingSkill = {
calculated: false;
matchesCount: number;
matchesCountNeeded: number;
/** Freshly calculated skill */
newSp?: number;
};
export type UserSkillDifference =
| {
calculated: true;
spDiff: number;
}
| CalculatingSkill;
export type GroupSkillDifference =
| {
calculated: true;
oldSp: number;
newSp: number;
}
| CalculatingSkill;
export type ParsedMemento = {
users: Record<
number,
{
plusTier?: PlusTier["tier"];
skill?: TieredSkill | "CALCULATING";
skillDifference?: UserSkillDifference;
}
>;
groups: Record<
number,
{
tier?: TieredSkill["tier"];
skillDifference?: GroupSkillDifference;
}
>;
modePreferences?: Partial<
Record<ModeShort, Array<{ userId: number; preference?: Preference }>>
>;
/** mapPreferences of season 2 */
mapPreferences?: Array<{ userId: number; preference?: Preference }[]>;
pools: Array<{ userId: number; pool: UserMapModePreferences["pool"] }>;
};
export interface GroupMatch {
alphaGroupId: number;
bravoGroupId: number;
chatCode: string | null;
createdAt: Generated<number>;
id: GeneratedAlways<number>;
memento: JSONColumnTypeNullable<ParsedMemento>;
reportedAt: number | null;
reportedByUserId: number | null;
}
export interface GroupMatchMap {
id: GeneratedAlways<number>;
index: number;
matchId: number;
mode: ModeShort;
source: string;
stageId: StageId;
winnerGroupId: number | null;
}
export interface GroupMember {
createdAt: Generated<number>;
groupId: number;
note: string | null;
role: "OWNER" | "MANAGER" | "REGULAR";
userId: number;
}
export interface PrivateUserNote {
authorId: number;
targetId: number;
text: string | null;
sentiment: "POSITIVE" | "NEUTRAL" | "NEGATIVE";
updatedAt: Generated<number>;
}
/** Log-in links generated via the Lohi Discord bot commands. */
export interface LogInLink {
code: string;
expiresAt: number;
userId: number;
}
export type LFGType =
| "PLAYER_FOR_TEAM"
| "PLAYER_FOR_COACH"
| "TEAM_FOR_PLAYER"
| "TEAM_FOR_COACH"
| "TEAM_FOR_SCRIM"
| "COACH_FOR_TEAM";
export const LFG_TYPES = [
"PLAYER_FOR_TEAM",
"PLAYER_FOR_COACH",
"TEAM_FOR_PLAYER",
"TEAM_FOR_COACH",
"TEAM_FOR_SCRIM",
"COACH_FOR_TEAM",
] as const;
export interface LFGPost {
id: GeneratedAlways<number>;
type: LFGType;
text: string;
/** e.g. Europe/Helsinki */
timezone: string;
authorId: number;
teamId: number | null;
plusTierVisibility: number | null;
languages: string | null;
updatedAt: Generated<number>;
createdAt: GeneratedAlways<number>;
}
export interface MapPoolMap {
calendarEventId: number | null;
mode: ModeShort;
stageId: StageId;
tieBreakerCalendarEventId: number | null;
tournamentTeamId: number | null;
}
export interface MapResult {
losses: number;
mode: ModeShort;
season: number;
stageId: StageId;
userId: number;
wins: number;
}
export interface PlayerResult {
mapLosses: number;
mapWins: number;
otherUserId: number;
ownerUserId: number;
season: number;
setLosses: number;
setWins: number;
type: string;
}
export interface PlusSuggestion {
authorId: number;
createdAt: GeneratedAlways<number>;
id: GeneratedAlways<number>;
month: number;
suggestedId: number;
text: string;
tier: number;
year: number;
}
export interface PlusTier {
tier: number;
userId: number;
}
export interface PlusVote {
authorId: number;
month: number;
score: number;
tier: number;
validAfter: number;
votedId: number;
year: number;
}
export interface PlusVotingResult {
votedId: number;
tier: number;
score: number;
month: number;
year: number;
wasSuggested: DBBoolean;
passedVoting: DBBoolean;
}
export interface ReportedWeapon {
groupMatchMapId: number | null;
userId: number;
weaponSplId: MainWeaponId;
}
export interface Skill {
groupMatchId: number | null;
id: GeneratedAlways<number>;
identifier: string | null;
matchesCount: number;
mu: number;
ordinal: number;
sigma: number;
season: number;
tournamentId: number | null;
userId: number | null;
createdAt: number | null;
}
export interface SkillTeamUser {
skillId: number;
userId: number;
}
/** Used for tournament auto-seeding. Calculates off tournament matches same as SP but does not have seasonal resets. */
export interface SeedingSkill {
mu: number;
ordinal: number;
sigma: number;
userId: number;
type: "RANKED" | "UNRANKED";
}
export interface SplatoonPlayer {
id: GeneratedAlways<number>;
splId: string;
userId: number | null;
/** Players best XP across both divisions. Denormalized for performance. */
peakXp: number | null;
}
export interface TaggedArt {
artId: number;
tagId: number;
}
// AUTO = style where teams pick their map pool ahead of time and the map lists are automatically made for each round
// could also have the traditional style where TO picks the maps later
type TournamentMapPickingStyle =
| "TO"
| "AUTO_ALL"
| "AUTO_SZ"
| "AUTO_TC"
| "AUTO_RM"
| "AUTO_CB";
export interface TournamentSettings {
bracketProgression: Progression.ParsedBracket[];
/** @deprecated use bracketProgression instead */
teamsPerGroup?: number;
/** @deprecated use bracketProgression instead */
thirdPlaceMatch?: boolean;
isRanked?: boolean;
enableNoScreenToggle?: boolean;
/** Enable the subs tab, default true */
enableSubs?: boolean;
requireInGameNames?: boolean;
isInvitational?: boolean;
/** Can teams add subs on their own while tournament is in progress? */
autonomousSubs?: boolean;
/** Timestamp (SQLite format) when reg closes, if missing then means closes at start time */
regClosesAt?: number;
/** @deprecated use bracketProgression instead */
swiss?: {
groupCount: number;
roundCount: number;
};
minMembersPerTeam?: number;
/** Maximum number of team members that can be registered (only applies to 4v4 tournaments) */
maxMembersPerTeam?: number;
isTest?: boolean;
}
export interface CastedMatchesInfo {
/** Array for match ID's that are locked because they are pending to be casted */
lockedMatches: number[];
/** What matches are streamed currently & where */
castedMatches: { twitchAccount: string; matchId: number }[];
}
export interface Tournament {
settings: JSONColumnType<TournamentSettings>;
id: GeneratedAlways<number>;
mapPickingStyle: TournamentMapPickingStyle;
/** Maps prepared ahead of time for rounds. Follows settings.bracketProgression order. Null in the spot if not defined yet for that bracket. */
preparedMaps: JSONColumnTypeNullable<(PreparedMaps | null)[]>;
castTwitchAccounts: JSONColumnTypeNullable<string[]>;
castedMatchesInfo: JSONColumnTypeNullable<CastedMatchesInfo>;
rules: string | null;
/** Related "parent tournament", the tournament that contains the original sign-ups (for leagues) */
parentTournamentId: number | null;
/** Is the tournament finalized meaning all the matches are played and TO has locked it making it read-only */
isFinalized: Generated<DBBoolean>;
/** Snapshot of teams and rosters when seeds were last saved. Used to detect NEW teams/players. */
seedingSnapshot: JSONColumnTypeNullable<SeedingSnapshot>;
/** Tournament tier based on top teams' skill. 1=X, 2=S+, 3=S, 4=A+, 5=A, 6=B+, 7=B, 8=C+, 9=C */
tier: TournamentTierNumber | null;
}
export interface SeedingSnapshot {
savedAt: number;
teams: Array<{
teamId: number;
members: Array<{ userId: number; username: string }>;
}>;
}
export interface PreparedMaps {
authorId: number;
createdAt: number;
maps: Array<TournamentRoundMaps & { roundId: number; groupId: number }>;
eliminationTeamCount?: number;
}
export interface TournamentBadgeOwner {
badgeId: number;
userId: number;
tournamentId: number | null;
}
/** A group is a logical structure used to group multiple rounds together.
- In round-robin stages, a group is a pool.
- In swiss, a group is also a pool (can have one or multiple groups)
- In elimination stages, a group is a bracket.
- A single elimination stage can have one or two groups:
- The unique bracket.
- If enabled, the Consolation Final.
- A double elimination stage can have two or three groups:
- Upper and lower brackets.
- If enabled, the Grand Final.
*/
export interface TournamentGroup {
id: GeneratedAlways<number>;
number: number;
stageId: number;
}
export const TournamentMatchStatus = {
/** The two matches leading to this one are not completed yet. */
Locked: 0,
/** One participant is ready and waiting for the other one. */
Waiting: 1,
/** Both participants are ready to start. */
Ready: 2,
/** The match is running. */
Running: 3,
/** The match is completed. */
Completed: 4,
};
export interface TournamentMatch {
chatCode: string | null;
groupId: number;
id: GeneratedAlways<number>;
number: number;
opponentOne: JSONColumnType<ParticipantResult>;
opponentTwo: JSONColumnType<ParticipantResult>;
roundId: number;
stageId: number;
status: (typeof TournamentMatchStatus)[keyof typeof TournamentMatchStatus];
// set when match becomes ongoing (both teams ready and no earlier matches for either team)
// for swiss: set at creation time
startedAt: number | null;
}
/** Represents one decision, pick or ban, during tournaments pick/ban (counterpick, ban 2) phase. */
export interface TournamentMatchPickBanEvent {
type: "PICK" | "BAN";
stageId: StageId;
mode: ModeShort;
matchId: number;
authorId: number;
number: number;
createdAt: GeneratedAlways<number>;
}
export interface TournamentMatchGameResult {
createdAt: Generated<number>;
id: GeneratedAlways<number>;
matchId: number;
mode: ModeShort;
number: number;
reporterId: number;
source: string;
stageId: StageId;
winnerTeamId: number;
opponentOnePoints: number | null;
opponentTwoPoints: number | null;
}
export interface TournamentMatchGameResultParticipant {
matchGameResultId: number;
userId: number;
tournamentTeamId: number;
}
export type WinLossParticipationArray = Array<"W" | "L" | null>;
export interface TournamentResult {
isHighlight: Generated<DBBoolean>;
participantCount: number;
placement: number;
tournamentId: number;
tournamentTeamId: number;
/**
* The result of sets in the tournament.
* E.g. ["W", "L", null] would mean the user won the first set, lost the second and did not play the third.
* */
setResults: JSONColumnType<WinLossParticipationArray>;
/** The SP change in total after the finalization of a ranked tournament. */
spDiff: number | null;
userId: number;
/** Division label for tournaments with multiple starting brackets (e.g., "D1", "D2") */
div: string | null;
}
export interface TournamentRoundMaps {
list?: Array<{ mode: ModeShort; stageId: StageId }> | null;
count: number;
type: "BEST_OF" | "PLAY_ALL";
pickBan?: PickBan.Type | null;
}
/**
* A round is a logical structure used to group multiple matches together.
- In round-robin stages, a round can be viewed as a list of matches that can be played at the same time.
- In swiss, a round is a list of matches that are played at the same time.
- In elimination stages, a round is a round of a bracket, e.g. 8th finals, semi-finals, etc.
*/
export interface TournamentRound {
groupId: number;
id: GeneratedAlways<number>;
number: number;
stageId: number;
maps: JSONColumnType<TournamentRoundMaps>;
}
// when updating this also update `defaultBracketSettings` in tournament-utils.ts
export interface TournamentStageSettings {
// SE
thirdPlaceMatch?: boolean;
// RR
teamsPerGroup?: number;
// SWISS
groupCount?: number;
// SWISS
roundCount?: number;
/** (Swiss only) Number of wins required for a team to advance early. When set, teams advance at this win count and are eliminated at (roundCount - advanceThreshold + 1) losses. */
advanceThreshold?: number;
}
export const TOURNAMENT_STAGE_TYPES = [
"single_elimination",
"double_elimination",
"round_robin",
"swiss",
] as const;
/** A stage is an intermediate phase in a tournament. In essence a bracket. */
export interface TournamentStage {
id: GeneratedAlways<number>;
name: string;
number: number;
settings: string;
tournamentId: number;
type: (typeof TOURNAMENT_STAGE_TYPES)[number];
// not Generated<> because SQLite doesn't allow altering tables to add columns with default values :(
createdAt: number | null;
}
/** Tournament sub post, shown in a list of subs available for teams to pick from. */
export interface TournamentSub {
bestWeapons: string;
/** 0 = no, 1 = yes, 2 = listen only */
canVc: number;
createdAt: Generated<number>;
message: string | null;
okWeapons: string | null;
tournamentId: number;
userId: number;
visibility: "+1" | "+2" | "+3" | "ALL";
}
export interface TournamentStaff {
tournamentId: number;
userId: number;
role: "ORGANIZER" | "STREAMER";
}
export interface TournamentTeam {
createdAt: Generated<number>;
id: GeneratedAlways<number>;
inviteCode: string;
name: string;
prefersNotToHost: Generated<DBBoolean>;
droppedOut: Generated<DBBoolean>;
seed: number | null;
/** For formats that have many starting brackets, where should the team start? */
startingBracketIdx: number | null;
activeRosterUserIds: JSONColumnTypeNullable<number[]>;
tournamentId: number;
teamId: number | null;
avatarImgId: number | null;
}
export interface TournamentTeamCheckIn {
checkedInAt: number;
/** Which bracket checked in for. If missing is check in for the whole event. */
bracketIdx: number | null;
tournamentTeamId: number;
/** Indicates that this bracket defaults to checked in and this team has been explicitly checked out from it */
isCheckOut: Generated<number>;
}
export interface TournamentTeamMember {
createdAt: Generated<number>;
isOwner: Generated<number>;
inGameName: string | null;
tournamentTeamId: number;
userId: number;
}
export interface TournamentOrganization {
id: GeneratedAlways<number>;
name: string;
slug: string;
description: string | null;
socials: JSONColumnTypeNullable<string[]>;
avatarImgId: number | null;
isEstablished: Generated<DBBoolean>;
}
export const TOURNAMENT_ORGANIZATION_ROLES = [
"ADMIN",
"MEMBER",
"ORGANIZER",
"STREAMER",
] as const;
type TournamentOrganizationRole =
(typeof TOURNAMENT_ORGANIZATION_ROLES)[number];
export interface TournamentOrganizationMember {
organizationId: number;
userId: number;
role: TournamentOrganizationRole;
roleDisplayName: string | null;
}
export interface TournamentOrganizationBadge {
organizationId: number;
badgeId: number;
}
export interface TournamentOrganizationSeries {
id: GeneratedAlways<number>;
organizationId: number;
name: string;
description: string | null;
substringMatches: JSONColumnType<string[]>;
showLeaderboard: Generated<number>;
tierHistory: JSONColumnTypeNullable<TournamentTierNumber[]>;
}
export interface TournamentBracketProgressionOverride {
sourceBracketIdx: number;
destinationBracketIdx: number;
tournamentTeamId: number;
tournamentId: number;
}
export interface TournamentOrganizationBannedUser {
organizationId: number;
userId: number;
privateNote: string | null;
updatedAt: Generated<number>;
expiresAt: number | null;
}
/** Indicates a user trusts another. Allows direct adding to groups/teams without invite links. */
export interface TrustRelationship {
trustGiverUserId: number;
trustReceiverUserId: number;
lastUsedAt: number;
}
export interface UnvalidatedUserSubmittedImage {
id: GeneratedAlways<number>;
submitterUserId: number;
url: string;
/** When was the image validated? If `null` should be hidden from other users. */
validatedAt: number | null;
}
export interface UnvalidatedVideo {
eventId: number | null;
id: GeneratedAlways<number>;
submitterUserId: number;
title: string;
type: string;
validatedAt: number | null;
youtubeDate: number;
youtubeId: string;
}
// missing means "neutral"
export type Preference = "AVOID" | "PREFER";
export interface UserMapModePreferences {
modes: Array<{
mode: ModeShort;
/** Users opinion on the mode, `undefined` means neutral */
preference?: Preference;
}>;
pool: Array<{
mode: ModeShort;
stages: StageId[];
}>;
}
export interface QWeaponPool {
weaponSplId: MainWeaponId;
isFavorite: number;
}
export const BUILD_SORT_IDENTIFIERS = [
"UPDATED_AT",
"TOP_500",
"WEAPON_POOL",
"WEAPON_IN_GAME_ORDER",
"ALPHABETICAL_TITLE",
"MODE",
"HEADGEAR_ID",
"CLOTHES_ID",
"SHOES_ID",
"PUBLIC_BUILD",
"PRIVATE_BUILD",
] as const;
export type BuildSort = (typeof BUILD_SORT_IDENTIFIERS)[number];
export interface UserPreferences {
disableBuildAbilitySorting?: boolean;
disallowScrimPickupsFromUntrusted?: boolean;
defaultCalendarFilters?: CalendarFilters;
defaultScrimsFilters?: ScrimFilters;
/**
* What time format the user prefers?
*
* "auto" = use browser default (default value)
* "24h" = 24 hour format (e.g. 14:00)
* "12h" = 12 hour format (e.g. 2:00 PM)
* */
clockFormat?: "24h" | "12h" | "auto";
/** Is the new widget based user page enabled? (Supporter early preview) */
newProfileEnabled?: boolean;
}
export const SUBJECT_PRONOUNS = ["he", "she", "they", "it", "any"] as const;
export const OBJECT_PRONOUNS = [
"him",
"her",
"them",
"its",
"all",
...SUBJECT_PRONOUNS,
] as const;
export type Pronouns = {
subject: (typeof SUBJECT_PRONOUNS)[number];
object: (typeof OBJECT_PRONOUNS)[number];
};
export interface User {
/** 1 = permabanned, timestamp = ban active till then */
banned: Generated<number | null>;
bannedReason: string | null;
bio: string | null;
commissionsOpen: Generated<number | null>;
commissionsOpenedAt: number | null;
commissionText: string | null;
country: string | null;
css: JSONColumnTypeNullable<Record<string, string>>;
customUrl: string | null;
discordAvatar: string | null;
discordId: string;
discordName: string;
customName: string | null;
/** coalesce(customName, discordName) */
username: ColumnType<string, never, never>;
discordUniqueName: string | null;
/** User's favorite badges they want to show on the front page of the badge display. Index = 0 big badge. */
favoriteBadgeIds: ColumnType<number[] | null, string | null, string | null>;
id: GeneratedAlways<number>;
inGameName: string | null;
isArtist: Generated<DBBoolean | null>;
isVideoAdder: Generated<DBBoolean | null>;
isTournamentOrganizer: Generated<DBBoolean | null>;
isApiAccesser: Generated<DBBoolean | null>;
languages: string | null;
motionSens: number | null;
pronouns: JSONColumnTypeNullable<Pronouns>;
patronSince: number | null;
patronTier: number | null;
patronTill: number | null;
showDiscordUniqueName: Generated<DBBoolean>;
stickSens: number | null;
twitch: string | null;
bsky: string | null;
battlefy: string | null;
vc: Generated<"YES" | "NO" | "LISTEN_ONLY">;
youtubeId: string | null;
mapModePreferences: JSONColumnTypeNullable<UserMapModePreferences>;
qWeaponPool: JSONColumnTypeNullable<QWeaponPool[]>;
plusSkippedForSeasonNth: number | null;
noScreen: Generated<DBBoolean>;
buildSorting: JSONColumnTypeNullable<BuildSort[]>;
preferences: JSONColumnTypeNullable<UserPreferences>;
/** User creation date. Can be null because we did not always save this. */
createdAt: number | null;
/** Last message used when creating a tournament sub post */
lastSubMessage: string | null;
}
/** Represents User joined with PlusTier table */
export type UserWithPlusTier = Tables["User"] & {
plusTier: PlusTier["tier"] | null;
};
export interface UserResultHighlight {
teamId: number;
userId: number;
}
export interface UserSubmittedImage {
id: GeneratedAlways<number>;
submitterUserId: number | null;
url: string;
validatedAt: number | null;
}
export interface UserWeapon {
createdAt: Generated<number>;
isFavorite: Generated<DBBoolean>;
order: number;
userId: number;
weaponSplId: MainWeaponId;
}
export interface UserFriendCode {
friendCode: string;
userId: number;
submitterUserId: number;
createdAt: GeneratedAlways<number>;
}
export interface UserWidget {
userId: number;
index: number;
widget: JSONColumnType<StoredWidget>;
}
export type ApiTokenType = "read" | "write";
export interface ApiToken {
id: GeneratedAlways<number>;
userId: number;
token: string;
type: Generated<ApiTokenType>;
createdAt: GeneratedAlways<number>;
}
export interface LiveStream {
id: GeneratedAlways<number>;
userId: number | null;
viewerCount: number;
thumbnailUrl: string;
twitch: string | null;
}
export interface BanLog {
id: GeneratedAlways<number>;
userId: number;
banned: number | null;
bannedReason: string | null;
bannedByUserId: number;
createdAt: GeneratedAlways<number>;
}
export interface ModNote {
id: GeneratedAlways<number>;
userId: number;
authorId: number;
text: string;
createdAt: GeneratedAlways<number>;
isDeleted: Generated<DBBoolean>;
}
export interface Video {
eventId: number | null;
id: GeneratedAlways<number>;
submitterUserId: number;
title: string;
type: "SCRIM" | "TOURNAMENT" | "MATCHMAKING" | "CAST" | "SENDOUQ";
validatedAt: number | null;
youtubeDate: number;
youtubeId: string;
}
export interface VideoMatch {
id: GeneratedAlways<number>;
mode: ModeShort;
stageId: StageId;
startsAt: number;
videoId: number;
}
export interface VideoMatchPlayer {
player: number;
playerName: string | null;
playerUserId: number | null;
videoMatchId: number;
weaponSplId: number;
}
export interface XRankPlacement {
badges: string;
bannerSplId: number;
id: GeneratedAlways<number>;
mode: ModeShort;
month: number;
name: string;
nameDiscriminator: string;
playerId: number;
power: number;
rank: number;
region: "WEST" | "JPN";
title: string;
weaponSplId: MainWeaponId;
year: number;
}
export interface ScrimPost {
id: GeneratedAlways<number>;
/** When is the scrim scheduled to happen */
at: number;
/** Optional end of time range indicating team accepts scrims starting between at and rangeEnd */
rangeEnd: number | null;
/** Highest LUTI div accepted */
maxDiv: number | null;
/** Lowest LUTI div accepted */
minDiv: number | null;
/** Who sees the post */
visibility: JSONColumnTypeNullable<AssociationVisibility>;
/** Any additional info */
text: string | null;
/** The key to access the scrim chat, used after scrim is scheduled with another team */
chatCode: string;
/** Refers to the team looking for the team (can also be a pick-up) */
teamId: number | null;
/** Indicates if anyone in the post can manage it */
managedByAnyone: DBBoolean;
/** When the scrim was canceled */
canceledAt: number | null;
/** User id who canceled the scrim */
canceledByUserId: number | null;
/** Reason for canceling the scrim */
cancelReason: string | null;
/** When the post was made was it scheduled for a future time slot (as opposed to looking now) */
isScheduledForFuture: Generated<DBBoolean>;
/** Maps/modes the scrim is available for. If null means no preference unless "mapsTournamentId" is set */
maps: "SZ" | "ALL" | "RANKED" | null;
/** If set, specifies the maps of a tournament to play */
mapsTournamentId: number | null;
createdAt: GeneratedAlways<number>;
updatedAt: Generated<number>;
}
export interface ScrimPostUser {
scrimPostId: number;
userId: number;
/** User is the author of the post */
isOwner: number;
}
export interface ScrimPostRequest {
id: GeneratedAlways<number>;
scrimPostId: number;
teamId: number | null;
message: string | null;
/** Specific time selected by requester (required when post has rangeEnd) */
at: number | null;
isAccepted: Generated<DBBoolean>;
createdAt: GeneratedAlways<number>;
}
export interface ScrimPostRequestUser {
scrimPostRequestId: number;
/** User that made the request */
userId: number;
isOwner: DBBoolean;
}
export interface Association {
id: GeneratedAlways<number>;
name: string;
inviteCode: string;
createdAt: GeneratedAlways<number>;
}
export interface AssociationMember {
userId: number;
associationId: number;
role: "MEMBER" | "ADMIN";
}
export interface Notification {
id: GeneratedAlways<number>;
type: NotificationValue["type"];
meta: JSONColumnTypeNullable<Record<string, number | string>>;
pictureUrl: string | null;
createdAt: GeneratedAlways<number>;
}
export interface NotificationUser {
notificationId: number;
userId: number;
seen: Generated<DBBoolean>;
}
export interface NotificationSubscription {
endpoint: string;
keys: {
auth: string;
p256dh: string;
};
}
/** A subscription of user's browser indicating where push notifications can be sent to. */
export interface NotificationUserSubscription {
id: GeneratedAlways<number>;
userId: number;
subscription: JSONColumnType<NotificationSubscription>;
}
export type Tables = { [P in keyof DB]: Selectable<DB[P]> };
export type TablesInsertable = { [P in keyof DB]: Insertable<DB[P]> };
export type TablesUpdatable = { [P in keyof DB]: Updateable<DB[P]> };
export interface DB {
AllTeam: Team;
AllTeamMember: TeamMember;
ApiToken: ApiToken;
Art: Art;
LiveStream: LiveStream;
ArtTag: ArtTag;
ArtUserMetadata: ArtUserMetadata;
TaggedArt: TaggedArt;
Badge: Badge;
BadgeManager: BadgeManager;
BadgeOwner: BadgeOwner;
TournamentBadgeOwner: TournamentBadgeOwner;
BanLog: BanLog;
ModNote: ModNote;
Build: Build;
BuildAbility: BuildAbility;
BuildWeapon: BuildWeapon;
CalendarEvent: CalendarEvent;
CalendarEventBadge: CalendarEventBadge;
CalendarEventDate: CalendarEventDate;
CalendarEventResultPlayer: CalendarEventResultPlayer;
CalendarEventResultTeam: CalendarEventResultTeam;
FreshPlusTier: FreshPlusTier;
Group: Group;
GroupLike: GroupLike;
GroupMatch: GroupMatch;
GroupMatchMap: GroupMatchMap;
GroupMember: GroupMember;
PrivateUserNote: PrivateUserNote;
LogInLink: LogInLink;
LFGPost: LFGPost;
MapPoolMap: MapPoolMap;
MapResult: MapResult;
PlayerResult: PlayerResult;
PlusSuggestion: PlusSuggestion;
PlusTier: PlusTier;
PlusVote: PlusVote;
PlusVotingResult: PlusVotingResult;
ReportedWeapon: ReportedWeapon;
Skill: Skill;
SkillTeamUser: SkillTeamUser;
SeedingSkill: SeedingSkill;
SplatoonPlayer: SplatoonPlayer;
Team: Team;
TeamMember: TeamMember;
TeamMemberWithSecondary: TeamMember;
Tournament: Tournament;
TournamentStaff: TournamentStaff;
TournamentGroup: TournamentGroup;
TournamentMatch: TournamentMatch;
TournamentMatchPickBanEvent: TournamentMatchPickBanEvent;
TournamentMatchGameResult: TournamentMatchGameResult;
TournamentMatchGameResultParticipant: TournamentMatchGameResultParticipant;
TournamentResult: TournamentResult;
TournamentRound: TournamentRound;
TournamentStage: TournamentStage;
TournamentSub: TournamentSub;
TournamentTeam: TournamentTeam;
TournamentTeamCheckIn: TournamentTeamCheckIn;
TournamentTeamMember: TournamentTeamMember;
TournamentOrganization: TournamentOrganization;
TournamentOrganizationMember: TournamentOrganizationMember;
TournamentOrganizationBadge: TournamentOrganizationBadge;
TournamentOrganizationSeries: TournamentOrganizationSeries;
TournamentBracketProgressionOverride: TournamentBracketProgressionOverride;
TournamentOrganizationBannedUser: TournamentOrganizationBannedUser;
TrustRelationship: TrustRelationship;
UnvalidatedUserSubmittedImage: UnvalidatedUserSubmittedImage;
UnvalidatedVideo: UnvalidatedVideo;
User: User;
UserResultHighlight: UserResultHighlight;
UserSubmittedImage: UserSubmittedImage;
UserWeapon: UserWeapon;
UserFriendCode: UserFriendCode;
UserWidget: UserWidget;
Video: Video;
VideoMatch: VideoMatch;
VideoMatchPlayer: VideoMatchPlayer;
XRankPlacement: XRankPlacement;
ScrimPost: ScrimPost;
ScrimPostUser: ScrimPostUser;
ScrimPostRequest: ScrimPostRequest;
ScrimPostRequestUser: ScrimPostRequestUser;
Association: Association;
AssociationMember: AssociationMember;
Notification: Notification;
NotificationUser: NotificationUser;
NotificationUserSubscription: NotificationUserSubscription;
}