Split off from tables.ts to tables-json.ts

This commit is contained in:
Kalle
2026-07-26 13:05:50 +03:00
parent a28819654c
commit 13c903ee08
114 changed files with 682 additions and 626 deletions

View File

@@ -2,15 +2,17 @@ import clsx from "clsx";
import { Lock, MessageCircleMore, SquarePen, Trash } from "lucide-react";
import { useTranslation } from "react-i18next";
import { Link } from "react-router";
import type { GearType, Tables, UserWithPlusTier } from "~/db/tables";
import type { Tables } from "~/db/tables";
import { useUser } from "~/features/auth/core/user";
import type { BuildWeaponWithTop500Info } from "~/features/builds/builds-types";
import type {
Ability as AbilityType,
BuildAbilitiesTuple,
GearType,
ModeShort,
} from "~/modules/in-game-lists/types";
import { canonicalWeaponSplId } from "~/modules/in-game-lists/weapon-ids";
import type { UserWithPlusTier } from "~/utils/kysely.server";
import { gearTypeToInitial } from "~/utils/strings";
import {
analyzerPage,

View File

@@ -1,11 +1,11 @@
import { Check, Clipboard, PencilLine } from "lucide-react";
import * as React from "react";
import { useTranslation } from "react-i18next";
import type { CustomTheme } from "~/db/tables-json";
import {
CUSTOM_THEME_VARS,
type CustomTheme,
type CustomThemeVar,
} from "~/db/tables";
} from "~/features/theme/theme-constants";
import { useCopyToClipboard } from "~/hooks/useCopyToClipboard";
import {
ACCENT_CHROMA_MULTIPLIERS,

View File

@@ -5,13 +5,13 @@ import {
SendouSelectItemSection,
} from "~/components/elements/Select";
import { Image } from "~/components/Image";
import type { GearType } from "~/db/tables";
import { brandIds } from "~/modules/in-game-lists/brand-ids";
import {
clothesGearBrandGrouped,
headGearBrandGrouped,
shoesGearBrandGrouped,
} from "~/modules/in-game-lists/gear-ids";
import type { GearType } from "~/modules/in-game-lists/types";
import { brandImageUrl, gearImageUrl } from "~/utils/urls";
import styles from "./WeaponSelect.module.css";

View File

@@ -9,7 +9,10 @@ import {
} from "lucide-react";
import { useTranslation } from "react-i18next";
import { LocaleTime } from "~/components/LocaleTime";
import type { GroupSkillDifference, UserSkillDifference } from "~/db/tables";
import type {
GroupSkillDifference,
UserSkillDifference,
} from "~/db/tables-json";
import { shortStageName } from "~/modules/in-game-lists/stage-ids";
import type {
MainWeaponId,

View File

@@ -36,6 +36,7 @@ import { clearAllTournamentDataCache } from "~/features/tournament-bracket/core/
import * as TournamentLFGRepository from "~/features/tournament-lfg/TournamentLFGRepository.server";
import * as TournamentOrganizationRepository from "~/features/tournament-organization/TournamentOrganizationRepository.server";
import * as UserRepository from "~/features/user-page/UserRepository.server";
import { USER_REPORT_CATEGORIES } from "~/features/user-report/user-report-constants";
import * as VodRepository from "~/features/vods/VodRepository.server";
import {
secondsToHoursMinutesSecondString,
@@ -76,12 +77,8 @@ import {
SEED_TEAM_IMAGES,
SEED_TOURNAMENT_IMAGES,
} from "../../../scripts/seed-art-urls";
import {
type ParsedMemento,
type Tables,
USER_REPORT_CATEGORIES,
type UserMapModePreferences,
} from "../tables";
import type { Tables } from "../tables";
import type { ParsedMemento, UserMapModePreferences } from "../tables-json";
import {
ADMIN_TEST_AVATAR,
AMOUNT_OF_CALENDAR_EVENTS,

246
app/db/tables-json.ts Normal file
View File

@@ -0,0 +1,246 @@
/**
* Shapes of JSON column payloads that have no natural feature home. Kept apart from
* `tables.ts` so that what is declared there stays close to the `DB` interface. Payloads
* that do belong to a feature (e.g. `StoredWidget`, `Notification`) live with that feature
* and are imported by `tables.ts` directly.
*/
import type { CalendarFilters } from "~/features/calendar/calendar-types";
import type { TieredSkill } from "~/features/mmr/tiered.server";
import type { ScrimFilters } from "~/features/scrims/scrims-types";
import type { CustomThemeVar } from "~/features/theme/theme-constants";
import type * as PickBan from "~/features/tournament-bracket/core/PickBan";
import type * as Progression from "~/features/tournament-bracket/core/Progression";
import type {
ActionType,
WhoSide,
} from "~/features/tournament-bracket/tournament-bracket-constants";
import type {
ObjectPronoun,
SubjectPronoun,
} from "~/features/user-page/user-page-constants";
import type {
MainWeaponId,
ModeShort,
StageId,
} from "~/modules/in-game-lists/types";
export type CustomTheme = Omit<Record<CustomThemeVar, number>, "--_chat-h"> & {
"--_chat-h": number | null;
};
// 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 WeaponPoolEntry {
weaponSplId: MainWeaponId;
isFavorite: 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;
/** Is spoiler-free mode enabled? Hides recent tournament results and scores until the user chooses to reveal them. */
spoilerFreeMode?: boolean;
weaponReportDefaultOpen?: boolean;
}
export type Pronouns = {
subject: SubjectPronoun;
object: ObjectPronoun;
};
export interface PeakXP {
/** Peak XP across all divisions */
overall: number;
/** Peak XP (Takoroka division) */
takoroka: number | null;
/** Peak XP (Tentatek division) */
tentatek: number | null;
}
export type UserSkillDifference =
| {
calculated: true;
spDiff: number;
oldSp?: number;
newSp?: number;
}
| CalculatingSkill;
export type GroupSkillDifference =
| {
calculated: true;
oldSp: number;
newSp: number;
}
| CalculatingSkill;
export type ParsedMemento = {
users: Record<
number,
{
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"];
teamName?: string;
}>;
};
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;
isDraft?: boolean;
requireSendouQParticipation?: boolean;
}
export interface CastedMatchesInfo {
/** Array for matches that are locked because they are pending to be casted */
lockedMatches: Array<{ twitchAccount: string; matchId: number }>;
/** What matches are streamed currently & where */
castedMatches: { twitchAccount: string; matchId: number }[];
castedMatchHistory?: Array<{
twitchAccount: string;
matchId: number;
timestamp: number;
}>;
}
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 TournamentRoundMaps {
list?: Array<{ mode: ModeShort; stageId: StageId }> | null;
count: number;
type: "BEST_OF" | "PLAY_ALL";
pickBan?: PickBan.Type | null;
customFlow?: CustomPickBanFlow | null;
}
export interface CustomPickBanStep {
action: ActionType;
side?: WhoSide;
}
export interface CustomPickBanFlow {
preSet: CustomPickBanStep[];
postGame: CustomPickBanStep[];
}
// when updating this also update `defaultBracketSettings` in tournament-utils.ts
export interface TournamentStageSettings {
// SE
thirdPlaceMatch?: boolean;
// RR
teamsPerGroup?: number;
/** (RR only) When true, teams are split into A and B divisions and matches only pair A-vs-B. Only valid on starting brackets. */
hasAbDivisions?: boolean;
// 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 interface TournamentAuditLogMetadata {
bracketIdx?: number;
/** The new in-game name, for `UPDATE_IN_GAME_NAME` events. */
inGameName?: string;
}
/**
* 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.
* */
export type WinLossParticipationArray = Array<"W" | "L" | null>;
export interface NotificationSubscription {
endpoint: string;
keys: {
auth: string;
p256dh: string;
};
}
type CalculatingSkill = {
calculated: false;
matchesCount: number;
matchesCountNeeded: number;
/** Freshly calculated skill */
newSp?: number;
};

View File

@@ -1,3 +1,9 @@
/**
* The database tables, 1:1 with the {@link DB} interface at the bottom. JSON column payload
* shapes live either with their feature or, when they have no natural feature home, in
* `tables-json.ts`; domain constants live in their feature's constants file.
*/
import type {
ColumnType,
GeneratedAlways,
@@ -5,18 +11,49 @@ import type {
JSONColumnType,
Selectable,
} from "kysely";
import type {
CastedMatchesInfo,
CustomTheme,
NotificationSubscription,
ParsedMemento,
PeakXP,
PreparedMaps,
Pronouns,
SeedingSnapshot,
TournamentAuditLogMetadata,
TournamentRoundMaps,
TournamentSettings,
UserMapModePreferences,
UserPreferences,
WeaponPoolEntry,
WinLossParticipationArray,
} from "~/db/tables-json";
import type { ApiTokenType } from "~/features/api/api-types";
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 { LFGType } from "~/features/lfg/lfg-constants";
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 { SplatoonRotationType } from "~/features/splatoon-rotations/splatoon-rotations-constants";
import type {
MemberRole,
MemberRoleType,
} from "~/features/team/team-constants";
import type { XRankPlacementRegion } from "~/features/top-search/top-search-types";
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 {
TournamentAuditLogType,
TournamentMapPickingStyle,
TournamentStaffRole,
} from "~/features/tournament/tournament-constants";
import type { TournamentOrganizationRole } from "~/features/tournament-organization/tournament-organization-constants";
import type { HideableUserCardStat } from "~/features/user-card/user-card-types";
import type { StoredWidget } from "~/features/user-page/core/widgets/types";
import type { ParticipantResult } from "~/modules/brackets-model";
import type { BuildSort } from "~/features/user-page/user-page-constants";
import type { UserReportCategory } from "~/features/user-report/user-report-constants";
import type {
ParticipantResult,
StageType,
Status,
} from "~/modules/brackets-model";
import type {
Ability,
BuildAbilitiesTuple,
@@ -31,51 +68,9 @@ type Generated<T> =
? ColumnType<S, I | undefined, U>
: ColumnType<T, T | undefined, T>;
export type MemberRole = (typeof TEAM_MEMBER_ROLES)[number];
export type MemberRoleType = "PLAYER" | "OTHER";
/** In SQLite booleans are presented as 0 (false) and 1 (true) */
export type DBBoolean = number;
export const CUSTOM_THEME_VARS = [
"--_base-h",
"--_base-c-0",
"--_base-c-1",
"--_base-c-2",
"--_base-c-3",
"--_base-c-4",
"--_base-c-5",
"--_base-c-6",
"--_base-c-7",
"--_acc-h",
"--_acc-c-0",
"--_acc-c-1",
"--_acc-c-2",
"--_acc-c-3",
"--_acc-c-4",
"--_acc-c-5",
"--_second-h",
"--_second-c-0",
"--_second-c-1",
"--_second-c-2",
"--_second-c-3",
"--_second-c-4",
"--_second-c-5",
"--_chat-h",
"--_radius-box",
"--_radius-field",
"--_radius-selector",
"--_border-width",
"--_size-field",
"--_size-selector",
"--_size-spacing",
] as const;
export type CustomThemeVar = (typeof CUSTOM_THEME_VARS)[number];
export type CustomTheme = Omit<Record<CustomThemeVar, number>, "--_chat-h"> & {
"--_chat-h": number | null;
};
export interface Team {
avatarImgId: number | null;
bannerImgId: number | null;
@@ -149,11 +144,11 @@ export interface BadgeManager {
userId: number;
}
export type BadgeOwner = {
export interface BadgeOwner {
badgeId: number;
userId: number;
count: number;
};
}
export interface Build {
clothesGearSplId: number | null;
@@ -172,8 +167,6 @@ export interface Build {
abilitiesSignature: string | null;
}
export type GearType = "HEAD" | "CLOTHES" | "SHOES";
export interface BuildWeapon {
buildId: number;
weaponSplId: MainWeaponId;
@@ -200,8 +193,6 @@ export interface BuildWeaponAbility {
abilityPoints: number;
}
export type CalendarEventTag = keyof typeof tags;
export interface CalendarEvent {
authorId: number;
bracketUrl: string;
@@ -261,56 +252,6 @@ export interface GroupLike {
isRechallenge: DBBoolean | null;
}
type CalculatingSkill = {
calculated: false;
matchesCount: number;
matchesCountNeeded: number;
/** Freshly calculated skill */
newSp?: number;
};
export type UserSkillDifference =
| {
calculated: true;
spDiff: number;
oldSp?: number;
newSp?: number;
}
| CalculatingSkill;
export type GroupSkillDifference =
| {
calculated: true;
oldSp: number;
newSp: number;
}
| CalculatingSkill;
export type ParsedMemento = {
users: Record<
number,
{
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"];
teamName?: string;
}>;
};
export interface GroupMatch {
alphaGroupId: number;
bravoGroupId: number;
@@ -368,23 +309,6 @@ export interface LogInLink {
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;
@@ -500,15 +424,6 @@ export interface SeedingSkill {
type: "RANKED" | "UNRANKED";
}
export interface PeakXP {
/** Peak XP across all divisions */
overall: number;
/** Peak XP (Takoroka division) */
takoroka: number | null;
/** Peak XP (Tentatek division) */
tentatek: number | null;
}
export interface SplatoonPlayer {
id: GeneratedAlways<number>;
splId: string;
@@ -517,62 +432,6 @@ export interface SplatoonPlayer {
peakXp: JSONColumnTypeNullable<PeakXP>;
}
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;
isDraft?: boolean;
requireSendouQParticipation?: boolean;
}
export interface CastedMatchesInfo {
/** Array for matches that are locked because they are pending to be casted */
lockedMatches: Array<{ twitchAccount: string; matchId: number }>;
/** What matches are streamed currently & where */
castedMatches: { twitchAccount: string; matchId: number }[];
castedMatchHistory?: Array<{
twitchAccount: string;
matchId: number;
timestamp: number;
}>;
}
export interface Tournament {
settings: JSONColumnType<TournamentSettings>;
id: GeneratedAlways<number>;
@@ -595,21 +454,6 @@ export interface Tournament {
vodsSyncCount: Generated<number>;
}
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 SavedCalendarEvent {
id: GeneratedAlways<number>;
userId: number;
@@ -644,23 +488,6 @@ export interface TournamentGroup {
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;
@@ -670,7 +497,7 @@ export interface TournamentMatch {
opponentTwo: JSONColumnType<ParticipantResult>;
roundId: number;
stageId: number;
status: (typeof TournamentMatchStatus)[keyof typeof TournamentMatchStatus];
status: Status;
// set when match becomes ongoing (both teams ready and no earlier matches for either team)
// for swiss: set at creation time
startedAt: number | null;
@@ -707,8 +534,6 @@ export interface TournamentMatchGameResultParticipant {
tournamentTeamId: number;
}
export type WinLossParticipationArray = Array<"W" | "L" | null>;
export interface TournamentResult {
isHighlight: Generated<DBBoolean>;
participantCount: number;
@@ -727,46 +552,6 @@ export interface TournamentResult {
div: string | null;
}
export interface TournamentRoundMaps {
list?: Array<{ mode: ModeShort; stageId: StageId }> | null;
count: number;
type: "BEST_OF" | "PLAY_ALL";
pickBan?: PickBan.Type | null;
customFlow?: CustomPickBanFlow | null;
}
export const WHO_SIDES = [
"RANDOM",
"RANDOM_OTHER",
"ALPHA",
"BRAVO",
"HIGHER_SEED",
"LOWER_SEED",
"WINNER",
"LOSER",
] as const;
export type WhoSide = (typeof WHO_SIDES)[number];
export const ACTION_TYPES = [
"ROLL",
"PICK",
"PICK_NO_MODE_REPEAT",
"BAN",
"MODE_PICK",
"MODE_BAN",
] as const;
export type ActionType = (typeof ACTION_TYPES)[number];
export interface CustomPickBanStep {
action: ActionType;
side?: WhoSide;
}
export interface CustomPickBanFlow {
preSet: CustomPickBanStep[];
postGame: CustomPickBanStep[];
}
/**
* A round is a logical structure used to group multiple matches together.
@@ -782,29 +567,6 @@ export interface TournamentRound {
maps: JSONColumnType<TournamentRoundMaps>;
}
// when updating this also update `defaultBracketSettings` in tournament-utils.ts
export interface TournamentStageSettings {
// SE
thirdPlaceMatch?: boolean;
// RR
teamsPerGroup?: number;
/** (RR only) When true, teams are split into A and B divisions and matches only pair A-vs-B. Only valid on starting brackets. */
hasAbDivisions?: boolean;
// 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>;
@@ -812,7 +574,7 @@ export interface TournamentStage {
number: number;
settings: string;
tournamentId: number;
type: (typeof TOURNAMENT_STAGE_TYPES)[number];
type: StageType;
// not Generated<> because SQLite doesn't allow altering tables to add columns with default values :(
createdAt: number | null;
}
@@ -836,9 +598,6 @@ export interface TournamentLFGLike {
createdAt: Generated<number>;
}
export const TOURNAMENT_STAFF_ROLES = ["ORGANIZER", "STREAMER"] as const;
type TournamentStaffRole = (typeof TOURNAMENT_STAFF_ROLES)[number];
export interface TournamentStaff {
tournamentId: number;
userId: number;
@@ -901,22 +660,10 @@ export interface TournamentTeamHistory {
name: string;
}
export const TOURNAMENT_AUDIT_LOG_TYPES = [
"MEMBER_ADDED",
"MEMBER_REMOVED",
"TEAM_REGISTERED",
"TEAM_UNREGISTERED",
"TEAM_CHECKED_IN",
"TEAM_CHECKED_OUT",
"TEAM_DROPPED_OUT",
"TEAM_DROP_OUT_UNDONE",
"UPDATE_IN_GAME_NAME",
] as const;
export interface TournamentAuditLog {
id: GeneratedAlways<number>;
tournamentId: number;
type: (typeof TOURNAMENT_AUDIT_LOG_TYPES)[number];
type: TournamentAuditLogType;
/** The user who performed the action. */
actorUserId: number;
/** The affected member, for member-level events. `null` for team-level events. */
@@ -927,12 +674,6 @@ export interface TournamentAuditLog {
createdAt: number;
}
export interface TournamentAuditLogMetadata {
bracketIdx?: number;
/** The new in-game name, for `UPDATE_IN_GAME_NAME` events. */
inGameName?: string;
}
export interface TournamentOrganization {
id: GeneratedAlways<number>;
name: string;
@@ -943,15 +684,6 @@ export interface TournamentOrganization {
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;
@@ -1031,79 +763,6 @@ export interface UnvalidatedVideo {
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 WeaponPoolEntry {
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;
/** Is spoiler-free mode enabled? Hides recent tournament results and scores until the user chooses to reveal them. */
spoilerFreeMode?: boolean;
weaponReportDefaultOpen?: 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];
};
/** Card stat types that can be hidden from a user's card (kept here so `User.hiddenCardStats` does not import a feature module; keep in sync with the feature's `UserCardStat["type"]`). */
export type HideableUserCardStat = "XP" | "DIV";
export interface User {
/** 1 = permabanned, timestamp = ban active till then */
banned: Generated<number | null>;
@@ -1168,11 +827,6 @@ export interface User {
unverifiedPeakXP: JSONColumnTypeNullable<PeakXP>;
}
/** Represents User joined with PlusTier table */
export type UserWithPlusTier = Tables["User"] & {
plusTier: PlusTier["tier"] | null;
};
export interface UserResultHighlight {
teamId: number;
userId: number;
@@ -1228,8 +882,6 @@ export interface UserWidget {
index: number;
widget: JSONColumnType<StoredWidget>;
}
export type ApiTokenType = "read" | "write";
export interface ApiToken {
id: GeneratedAlways<number>;
userId: number;
@@ -1291,15 +943,6 @@ export interface ModNote {
isDeleted: Generated<DBBoolean>;
}
export const USER_REPORT_CATEGORIES = [
"INAPPROPRIATE_CONTENT",
"ALTING",
"HARASSMENT",
"CHEATING",
"OTHER",
] as const;
export type UserReportCategory = (typeof USER_REPORT_CATEGORIES)[number];
export interface UserReport {
id: GeneratedAlways<number>;
reportedUserId: number;
@@ -1337,9 +980,6 @@ export interface VideoMatchPlayer {
weaponSplId: number;
}
/** `WEST` = Tentatek division, `JPN` = Takoroka division. */
export type XRankPlacementRegion = "WEST" | "JPN";
export interface XRankPlacement {
badges: string;
bannerSplId: number;
@@ -1466,14 +1106,6 @@ export interface NotificationUser {
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>;
@@ -1481,9 +1113,6 @@ export interface NotificationUserSubscription {
subscription: JSONColumnType<NotificationSubscription>;
}
export const SPLATOON_ROTATION_TYPES = ["SERIES", "OPEN", "X"] as const;
export type SplatoonRotationType = (typeof SPLATOON_ROTATION_TYPES)[number];
export interface SplatoonRotation {
id: GeneratedAlways<number>;
type: SplatoonRotationType;

View File

@@ -1,5 +1,5 @@
import type { ApiTokenType } from "~/db/tables";
import * as ApiRepository from "~/features/api/ApiRepository.server";
import type { ApiTokenType } from "~/features/api/api-types";
type CachedToken = { type: ApiTokenType; userId: number };

View File

@@ -1,4 +1,4 @@
import type { Pronouns } from "~/db/tables";
import type { Pronouns } from "~/db/tables-json";
import type { TierName } from "~/features/mmr/mmr-constants";
import type { DataTypes, ValueToArray } from "~/modules/brackets-manager/types";

View File

@@ -1,6 +1,6 @@
import { nanoid } from "nanoid";
import { db } from "~/db/sql";
import type { ApiTokenType } from "~/db/tables";
import type { ApiTokenType } from "~/features/api/api-types";
const API_TOKEN_LENGTH = 20;

View File

@@ -0,0 +1 @@
export type ApiTokenType = "read" | "write";

View File

@@ -10,13 +10,10 @@ import { sql } from "kysely";
import { jsonArrayFrom, jsonObjectFrom } from "kysely/helpers/sqlite";
import * as R from "remeda";
import { db } from "~/db/sql";
import type {
CalendarEventTag,
DB,
Tables,
TournamentSettings,
} from "~/db/tables";
import type { DB, Tables } from "~/db/tables";
import type { TournamentSettings } from "~/db/tables-json";
import { EXCLUDED_TAGS } from "~/features/calendar/calendar-constants";
import type { CalendarEventTag } from "~/features/calendar/calendar-types";
import * as Progression from "~/features/tournament-bracket/core/Progression";
import { getTentativeTier } from "~/features/tournament-organization/core/tentativeTiers.server";
import {

View File

@@ -1,9 +1,9 @@
import type { ActionFunction } from "react-router";
import { redirect } from "react-router";
import type { CalendarEventTag } from "~/db/tables";
import { requireUser } from "~/features/auth/core/user.server";
import * as BadgeRepository from "~/features/badges/BadgeRepository.server";
import * as CalendarRepository from "~/features/calendar/CalendarRepository.server";
import type { CalendarEventTag } from "~/features/calendar/calendar-types";
import * as ShowcaseTournaments from "~/features/front-page/core/ShowcaseTournaments.server";
import { MapPool } from "~/features/map-list-generator/core/map-pool";
import { notify } from "~/features/notifications/core/notify.server";

View File

@@ -1,4 +1,4 @@
import type { CalendarEventTag } from "~/db/tables";
import type { CalendarEventTag } from "~/features/calendar/calendar-types";
export const tags = {
SPECIAL: {

View File

@@ -1,6 +1,9 @@
import { z } from "zod";
import { type CalendarEventTag, TOURNAMENT_STAGE_TYPES } from "~/db/tables";
import { TOURNAMENT } from "~/features/tournament/tournament-constants";
import type { CalendarEventTag } from "~/features/calendar/calendar-types";
import {
TOURNAMENT,
TOURNAMENT_STAGE_TYPES,
} from "~/features/tournament/tournament-constants";
import * as Progression from "~/features/tournament-bracket/core/Progression";
import * as Swiss from "~/features/tournament-bracket/core/Swiss";
import {

View File

@@ -1,9 +1,12 @@
import type { z } from "zod";
import type { CalendarEventTag, Tables } from "~/db/tables";
import type { Tables } from "~/db/tables";
import type { tags } from "~/features/calendar/calendar-constants";
import type { calendarFiltersSearchParamsSchema } from "~/features/calendar/calendar-schemas";
import type { ModeShortWithSpecial } from "~/modules/in-game-lists/types";
import type { CommonUser } from "~/utils/kysely.server";
export type CalendarEventTag = keyof typeof tags;
interface CommonEvent {
id: number;
name: string;

View File

@@ -3,7 +3,7 @@ import { X } from "lucide-react";
import React from "react";
import { useTranslation } from "react-i18next";
import { SendouButton } from "~/components/elements/Button";
import type { CalendarEventTag } from "~/db/tables";
import type { CalendarEventTag } from "~/features/calendar/calendar-types";
import { tags as allTags } from "../calendar-constants";
import styles from "./Tags.module.css";

View File

@@ -1,6 +1,6 @@
import { add, startOfWeek, sub } from "date-fns";
import type { LoaderFunctionArgs } from "react-router";
import type { UserPreferences } from "~/db/tables";
import type { UserPreferences } from "~/db/tables-json";
import { getUser } from "~/features/auth/core/user.server";
import { DAYS_SHOWN_AT_A_TIME } from "~/features/calendar/calendar-constants";
import {

View File

@@ -1,4 +1,15 @@
import { LFG_TYPES, type Tables } from "~/db/tables";
import type { Tables } from "~/db/tables";
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 type LFGType = (typeof LFG_TYPES)[number];
export const LFG = {
MIN_TEXT_LENGTH: 1,

View File

@@ -1,4 +1,4 @@
import { LFG_TYPES, type LFGType } from "~/db/tables";
import { LFG_TYPES, type LFGType } from "~/features/lfg/lfg-constants";
import { languagesUnified } from "~/modules/i18n/config";
import type { MainWeaponId } from "~/modules/in-game-lists/types";
import { assertUnreachable } from "~/utils/types";

View File

@@ -1,6 +1,6 @@
import { afterEach, beforeEach, describe, expect, test } from "vitest";
import { db } from "~/db/sql";
import type { UserMapModePreferences } from "~/db/tables";
import type { UserMapModePreferences } from "~/db/tables-json";
import { dbInsertUsers, dbReset, withUserId } from "~/utils/Test";
import * as MatchProfileRepository from "./MatchProfileRepository.server";

View File

@@ -1,6 +1,7 @@
import * as R from "remeda";
import { db } from "~/db/sql";
import type { Tables, UserMapModePreferences } from "~/db/tables";
import type { Tables } from "~/db/tables";
import type { UserMapModePreferences } from "~/db/tables-json";
import { actorId } from "~/features/auth/core/user.server";
import type { WeaponPoolItem } from "~/form/fields/WeaponPoolFormField";
import type { UnifiedLanguageCode } from "~/modules/i18n/config";

View File

@@ -1,6 +1,7 @@
import { sub } from "date-fns";
import { db } from "~/db/sql";
import type { NotificationSubscription, TablesInsertable } from "~/db/tables";
import type { TablesInsertable } from "~/db/tables";
import type { NotificationSubscription } from "~/db/tables-json";
import { actorId } from "~/features/auth/core/user.server";
import { dateToDatabaseTimestamp } from "../../utils/dates";
import { NOTIFICATIONS } from "./notifications-contants";

View File

@@ -1,9 +1,9 @@
import type { TFunction } from "i18next";
import pLimit from "p-limit";
import { type Urgency, WebPushError } from "web-push";
import type { NotificationSubscription } from "~/db/tables-json";
import { IS_E2E_TEST_RUN } from "~/utils/e2e";
import { APP_ICON_URL } from "~/utils/urls";
import type { NotificationSubscription } from "../../../db/tables";
import { getFixedTForLanguage } from "../../../modules/i18n/i18next.server";
import { logger } from "../../../utils/logger";
import * as NotificationRepository from "../NotificationRepository.server";

View File

@@ -1,7 +1,8 @@
import type { Tables, UserWithPlusTier } from "~/db/tables";
import type { Tables } from "~/db/tables";
import type * as PlusSuggestionRepository from "~/features/plus-suggestions/PlusSuggestionRepository.server";
import { isAdmin } from "~/modules/permissions/utils";
import { allTruthy } from "~/utils/arrays";
import type { UserWithPlusTier } from "~/utils/kysely.server";
import * as Seasons from "../mmr/core/Seasons";
import { isVotingActive } from "../plus-voting/core";

View File

@@ -1,9 +1,9 @@
import type { UserWithPlusTier } from "~/db/tables";
import { getUser } from "~/features/auth/core/user.server";
import { lastCompletedVoting } from "~/features/plus-voting/core";
import * as PlusVotingRepository from "~/features/plus-voting/PlusVotingRepository.server";
import { isSupporter } from "~/modules/permissions/utils";
import invariant from "~/utils/invariant";
import type { UserWithPlusTier } from "~/utils/kysely.server";
import { roundToNDecimalPlaces } from "~/utils/number";
export const loader = async () => {

View File

@@ -3,7 +3,8 @@ import type { ExpressionBuilder, NotNull, Transaction } from "kysely";
import { jsonArrayFrom, jsonObjectFrom } from "kysely/helpers/sqlite";
import * as R from "remeda";
import { db } from "~/db/sql";
import type { DB, ParsedMemento } from "~/db/tables";
import type { DB } from "~/db/tables";
import type { ParsedMemento } from "~/db/tables-json";
import { actorId } from "~/features/auth/core/user.server";
import * as Seasons from "~/features/mmr/core/Seasons";
import type { TournamentMapListMap } from "~/modules/tournament-map-list-generator/types";

View File

@@ -1,7 +1,8 @@
import type { Transaction } from "kysely";
import { ordinal } from "openskill";
import { db } from "~/db/sql";
import type { DB, ParsedMemento, Tables } from "~/db/tables";
import type { DB, Tables } from "~/db/tables";
import type { ParsedMemento } from "~/db/tables-json";
import { identifierToUserIds } from "~/features/mmr/mmr-utils";
import { databaseTimestampNow } from "~/utils/dates";
import type { MementoSkillDifferences } from "./core/skills.server";

View File

@@ -1,5 +1,5 @@
import * as R from "remeda";
import type { ParsedMemento, UserMapModePreferences } from "~/db/tables";
import type { ParsedMemento, UserMapModePreferences } from "~/db/tables-json";
import * as MapList from "~/features/map-list-generator/core/MapList";
import { MapPool } from "~/features/map-list-generator/core/map-pool";
import {

View File

@@ -1,9 +1,9 @@
import { ordinal, type Rating } from "openskill";
import type { Tables } from "~/db/tables";
import type {
GroupSkillDifference,
Tables,
UserSkillDifference,
} from "~/db/tables";
} from "~/db/tables-json";
import { MATCHES_COUNT_NEEDED_FOR_LEADERBOARD } from "~/features/leaderboards/leaderboards-constants";
import * as Seasons from "~/features/mmr/core/Seasons";
import {

View File

@@ -2,7 +2,8 @@ import { sub } from "date-fns";
import { type NotNull, sql, type Transaction } from "kysely";
import { jsonArrayFrom, jsonBuildObject } from "kysely/helpers/sqlite";
import { db } from "~/db/sql";
import type { DB, Tables, UserMapModePreferences } from "~/db/tables";
import type { DB, Tables } from "~/db/tables";
import type { UserMapModePreferences } from "~/db/tables-json";
import { actorId } from "~/features/auth/core/user.server";
import { databaseTimestampNow, dateToDatabaseTimestamp } from "~/utils/dates";
import { shortNanoid } from "~/utils/id";

View File

@@ -2,7 +2,7 @@ import type { ComponentProps } from "react";
import { createMemoryRouter, RouterProvider } from "react-router";
import { describe, expect, test, vi } from "vitest";
import { render } from "vitest-browser-react";
import type { GroupSkillDifference } from "~/db/tables";
import type { GroupSkillDifference } from "~/db/tables-json";
import type { TieredSkill } from "~/features/mmr/tiered.server";
import type {
SQGroup,

View File

@@ -11,7 +11,7 @@ import { SendouPopover } from "~/components/elements/Popover";
import { Image, ModeImage, TierImage, WeaponImage } from "~/components/Image";
import { NoteAvatar } from "~/components/NoteAvatar";
import { SubmitButton } from "~/components/SubmitButton";
import type { ParsedMemento } from "~/db/tables";
import type { ParsedMemento } from "~/db/tables-json";
import { useUser } from "~/features/auth/core/user";
import { MATCHES_COUNT_NEEDED_FOR_LEADERBOARD } from "~/features/leaderboards/leaderboards-constants";
import { ordinalToRoundedSp } from "~/features/mmr/mmr-utils";

View File

@@ -1,6 +1,7 @@
import { isWithinInterval, sub } from "date-fns";
import * as R from "remeda";
import type { DBBoolean, ParsedMemento, Tables } from "~/db/tables";
import type { DBBoolean, Tables } from "~/db/tables";
import type { ParsedMemento } from "~/db/tables-json";
import type { AuthenticatedUser } from "~/features/auth/core/user.server";
import * as Seasons from "~/features/mmr/core/Seasons";
import { defaultOrdinal } from "~/features/mmr/mmr-utils";

View File

@@ -1,6 +1,6 @@
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
import { db } from "~/db/sql";
import type { UserMapModePreferences } from "~/db/tables";
import type { UserMapModePreferences } from "~/db/tables-json";
import type { StageId } from "~/modules/in-game-lists/types";
import { dbInsertUsers, dbReset } from "~/utils/Test";
import { SENDOUQ_BEST_OF } from "../q-constants";

View File

@@ -7,7 +7,7 @@ vi.mock("~/features/chat/ChatSystemMessage.server", () => ({
}));
import { db } from "~/db/sql";
import type { UserMapModePreferences } from "~/db/tables";
import type { UserMapModePreferences } from "~/db/tables-json";
import { BANNED_MAPS } from "~/features/match-profile/banned-maps";
import { stageIds } from "~/modules/in-game-lists/stage-ids";
import invariant from "~/utils/invariant";

View File

@@ -4,7 +4,7 @@ import { useTranslation } from "react-i18next";
import { useLoaderData, useLocation } from "react-router";
import { LinkButton } from "~/components/elements/Button";
import { ModeImage } from "~/components/Image";
import type { Preference, UserMapModePreferences } from "~/db/tables";
import type { Preference, UserMapModePreferences } from "~/db/tables-json";
import { BANNED_MAPS } from "~/features/match-profile/banned-maps";
import { AMOUNT_OF_MAPS_IN_POOL_PER_MODE } from "~/features/match-profile/match-profile-constants";
import { SendouForm } from "~/form/SendouForm";

View File

@@ -1,7 +1,7 @@
import clsx from "clsx";
import { Radio, RadioGroup } from "react-aria-components";
import { useTranslation } from "react-i18next";
import type { Preference } from "~/db/tables";
import type { Preference } from "~/db/tables-json";
import { preferenceEmojiUrl } from "~/utils/urls";
import styles from "./PreferenceRadioGroup.module.css";

View File

@@ -1,5 +1,5 @@
import { z } from "zod";
import type { UserMapModePreferences } from "~/db/tables";
import type { UserMapModePreferences } from "~/db/tables-json";
import {
checkboxGroup,
customField,

View File

@@ -0,0 +1,3 @@
export const SPLATOON_ROTATION_TYPES = ["SERIES", "OPEN", "X"] as const;
export type SplatoonRotationType = (typeof SPLATOON_ROTATION_TYPES)[number];

View File

@@ -1,7 +1,8 @@
import { type Insertable, sql, type Transaction } from "kysely";
import { jsonArrayFrom } from "kysely/helpers/sqlite";
import { db } from "~/db/sql";
import type { CustomTheme, DB, Tables } from "~/db/tables";
import type { DB, Tables } from "~/db/tables";
import type { CustomTheme } from "~/db/tables-json";
import { actorId } from "~/features/auth/core/user.server";
import * as LFGRepository from "~/features/lfg/LFGRepository.server";
import { NON_PLAYER_TEAM_ROLES } from "~/features/team/team-constants";

View File

@@ -1,7 +1,10 @@
import type { ActionFunction } from "react-router";
import { redirect } from "react-router";
import type { MemberRole, MemberRoleType } from "~/db/tables";
import { requireUser } from "~/features/auth/core/user.server";
import type {
MemberRole,
MemberRoleType,
} from "~/features/team/team-constants";
import { parseFormData } from "~/form/parse.server";
import { errorToastIfFalsy, notFoundIfNullish } from "~/utils/remix.server";
import { assertUnreachable } from "~/utils/types";

View File

@@ -24,6 +24,11 @@ export const TEAM_MEMBER_ROLES = [
"CHEERLEADER",
] as const;
export type MemberRole = (typeof TEAM_MEMBER_ROLES)[number];
/** Classifies how a team member's `customRole` should be treated. */
export type MemberRoleType = "PLAYER" | "OTHER";
/** Roles that are not part of a team's active competitive lineup. Excluded when sourcing a roster (e.g. prefilling tournament registration or a scrim post). */
export const NON_PLAYER_TEAM_ROLES: readonly (typeof TEAM_MEMBER_ROLES)[number][] =
["CHEERLEADER", "COACH", "SUB"];

View File

@@ -1,4 +1,8 @@
import type { MemberRole, MemberRoleType, Tables } from "~/db/tables";
import type { Tables } from "~/db/tables";
import type {
MemberRole,
MemberRoleType,
} from "~/features/team/team-constants";
import type * as TeamRepository from "./TeamRepository.server";
import { NON_PLAYER_TEAM_ROLES, TEAM } from "./team-constants";

View File

@@ -0,0 +1,36 @@
/** CSS variables a supporter can override with their own custom theme. */
export const CUSTOM_THEME_VARS = [
"--_base-h",
"--_base-c-0",
"--_base-c-1",
"--_base-c-2",
"--_base-c-3",
"--_base-c-4",
"--_base-c-5",
"--_base-c-6",
"--_base-c-7",
"--_acc-h",
"--_acc-c-0",
"--_acc-c-1",
"--_acc-c-2",
"--_acc-c-3",
"--_acc-c-4",
"--_acc-c-5",
"--_second-h",
"--_second-c-0",
"--_second-c-1",
"--_second-c-2",
"--_second-c-3",
"--_second-c-4",
"--_second-c-5",
"--_chat-h",
"--_radius-box",
"--_radius-field",
"--_radius-selector",
"--_border-width",
"--_size-field",
"--_size-selector",
"--_size-spacing",
] as const;
export type CustomThemeVar = (typeof CUSTOM_THEME_VARS)[number];

View File

@@ -0,0 +1,2 @@
/** `WEST` = Tentatek division, `JPN` = Takoroka division. */
export type XRankPlacementRegion = "WEST" | "JPN";

View File

@@ -1,9 +1,9 @@
import type { LoaderFunctionArgs } from "react-router";
import { z } from "zod";
import { TOURNAMENT_AUDIT_LOG_TYPES } from "~/db/tables";
import { requireUser } from "~/features/auth/core/user.server";
import * as TournamentAuditLogRepository from "~/features/tournament/TournamentAuditLogRepository.server";
import { AUDIT_LOG_PAGE_SIZE } from "~/features/tournament/TournamentAuditLogRepository.server";
import { TOURNAMENT_AUDIT_LOG_TYPES } from "~/features/tournament/tournament-constants";
import { tournamentFromDBCached } from "~/features/tournament-bracket/core/Tournament.server";
import {
forbidden,

View File

@@ -5,8 +5,8 @@ import { Label } from "~/components/Label";
import { LocaleTime } from "~/components/LocaleTime";
import { Pagination } from "~/components/Pagination";
import { Table } from "~/components/Table";
import { TOURNAMENT_AUDIT_LOG_TYPES } from "~/db/tables";
import { useTournament } from "~/features/tournament/routes/to.$id";
import { TOURNAMENT_AUDIT_LOG_TYPES } from "~/features/tournament/tournament-constants";
import { useSearchParamPagination } from "~/hooks/useSearchParamPagination";
import type { CommonUser } from "~/utils/kysely.server";
import { tournamentTeamPage, userPage } from "~/utils/urls";

View File

@@ -30,7 +30,7 @@ import { SendouDialog } from "~/components/elements/Dialog";
import { InfoPopover } from "~/components/InfoPopover";
import { SubmitButton } from "~/components/SubmitButton";
import { Table } from "~/components/Table";
import type { SeedingSnapshot } from "~/db/tables";
import type { SeedingSnapshot } from "~/db/tables-json";
import { useTournament } from "~/features/tournament/routes/to.$id";
import { TOURNAMENT } from "~/features/tournament/tournament-constants";
import * as AbDivisions from "~/features/tournament-bracket/core/AbDivisions";

View File

@@ -4,8 +4,9 @@ import { useTranslation } from "react-i18next";
import { Avatar } from "~/components/Avatar";
import { Divider } from "~/components/Divider";
import { LinkButton, SendouButton } from "~/components/elements/Button";
import { type Tables, TOURNAMENT_ORGANIZATION_ROLES } from "~/db/tables";
import type { Tables } from "~/db/tables";
import { useTournament } from "~/features/tournament/routes/to.$id";
import { TOURNAMENT_ORGANIZATION_ROLES } from "~/features/tournament-organization/tournament-organization-constants";
import { SendouForm } from "~/form/SendouForm";
import { tournamentOrganizationEditPage } from "~/utils/urls";
import { adminStaffFormSchema } from "../tournament-admin-staff-schemas";

View File

@@ -1,5 +1,5 @@
import { z } from "zod";
import { TOURNAMENT_STAFF_ROLES } from "~/db/tables";
import { TOURNAMENT_STAFF_ROLES } from "~/features/tournament/tournament-constants";
import {
array,
fieldset,

View File

@@ -1,5 +1,6 @@
import type { ActionFunction } from "react-router";
import { sql } from "~/db/sql";
import type { PreparedMaps } from "~/db/tables-json";
import { requireUser } from "~/features/auth/core/user.server";
import * as ChatSystemMessage from "~/features/chat/ChatSystemMessage.server";
import { notify } from "~/features/notifications/core/notify.server";
@@ -23,7 +24,6 @@ import {
} from "~/utils/remix.server";
import { assertUnreachable } from "~/utils/types";
import { idObject } from "~/utils/zod";
import type { PreparedMaps } from "../../../db/tables";
import * as AbDivisions from "../core/AbDivisions";
import { getServerTournamentManager } from "../core/brackets-manager/manager.server";
import * as PreparedMapsUtils from "../core/PreparedMaps";

View File

@@ -2,7 +2,7 @@ import clsx from "clsx";
import { differenceInMinutes } from "date-fns";
import * as React from "react";
import { LocaleTime } from "~/components/LocaleTime";
import type { TournamentRoundMaps } from "~/db/tables";
import type { TournamentRoundMaps } from "~/db/tables-json";
import { useTournament } from "~/features/tournament/routes/to.$id";
import { resolveLeagueRoundStartDate } from "~/features/tournament/tournament-utils";
import { databaseTimestampToDate } from "~/utils/dates";

View File

@@ -23,7 +23,7 @@ import { Input } from "~/components/Input";
import { Label } from "~/components/Label";
import { LocaleTime } from "~/components/LocaleTime";
import { SubmitButton } from "~/components/SubmitButton";
import type { CustomPickBanFlow, TournamentRoundMaps } from "~/db/tables";
import type { CustomPickBanFlow, TournamentRoundMaps } from "~/db/tables-json";
import {
useTournament,
useTournamentPreparedMaps,

View File

@@ -2,7 +2,7 @@ import type { ComponentProps } from "react";
import { createMemoryRouter, RouterProvider } from "react-router";
import { describe, expect, test, vi } from "vitest";
import { render } from "vitest-browser-react";
import type { CustomPickBanFlow } from "~/db/tables";
import type { CustomPickBanFlow } from "~/db/tables-json";
import { CustomFlowBuilder } from "./CustomFlowBuilder";
const defaultProps: ComponentProps<typeof CustomFlowBuilder> = {

View File

@@ -35,14 +35,13 @@ import {
SendouTabPanel,
SendouTabs,
} from "~/components/elements/Tabs";
import type { CustomPickBanFlow, CustomPickBanStep } from "~/db/tables-json";
import {
ACTION_TYPES,
type ActionType,
type CustomPickBanFlow,
type CustomPickBanStep,
WHO_SIDES,
type WhoSide,
} from "~/db/tables";
} from "~/features/tournament-bracket/tournament-bracket-constants";
import { useLayoutSize } from "~/hooks/useMainContentWidth";
import {
type CustomFlowValidationError,

View File

@@ -1,6 +1,7 @@
import { sub } from "date-fns";
import * as R from "remeda";
import type { Tables, TournamentStageSettings } from "~/db/tables";
import type { Tables } from "~/db/tables";
import type { TournamentStageSettings } from "~/db/tables-json";
import { TOURNAMENT } from "~/features/tournament/tournament-constants";
import type { TournamentManagerDataSet } from "~/modules/brackets-manager/types";
import type { Round } from "~/modules/brackets-model";

View File

@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import type { TournamentRoundMaps } from "~/db/tables";
import type { TournamentRoundMaps } from "~/db/tables-json";
import type { ModeShort, StageId } from "~/modules/in-game-lists/types";
import {
CUSTOM_FLOW_VALIDATION_ERRORS,

View File

@@ -1,10 +1,9 @@
import * as R from "remeda";
import type { CustomPickBanStep, TournamentRoundMaps } from "~/db/tables-json";
import type {
ActionType,
CustomPickBanStep,
TournamentRoundMaps,
WhoSide,
} from "~/db/tables";
} from "~/features/tournament-bracket/tournament-bracket-constants";
import { isSetOverByResults } from "~/features/tournament-match/tournament-match-utils";
import type {
ModeShort,

View File

@@ -1,5 +1,5 @@
import { describe, expect, test } from "vitest";
import type { PreparedMaps as PreparedMapsType } from "~/db/tables";
import type { PreparedMaps as PreparedMapsType } from "~/db/tables-json";
import * as PreparedMaps from "./PreparedMaps";
import { testTournament } from "./tests/test-utils";

View File

@@ -1,5 +1,5 @@
import * as R from "remeda";
import type { PreparedMaps } from "~/db/tables";
import type { PreparedMaps } from "~/db/tables-json";
import { nullFilledArray } from "~/utils/arrays";
import invariant from "~/utils/invariant";
import type { Bracket } from "./Bracket";

View File

@@ -1,5 +1,6 @@
import * as R from "remeda";
import type { Tables, TournamentStageSettings } from "~/db/tables";
import type { Tables } from "~/db/tables";
import type { TournamentStageSettings } from "~/db/tables-json";
import { TOURNAMENT } from "~/features/tournament/tournament-constants";
import {
databaseTimestampToDate,

View File

@@ -1,8 +1,5 @@
import type {
Tables,
TournamentStage,
TournamentStageSettings,
} from "~/db/tables";
import type { Tables, TournamentStage } from "~/db/tables";
import type { TournamentStageSettings } from "~/db/tables-json";
import {
LEAGUES,
TOURNAMENT,

View File

@@ -1,5 +1,6 @@
import { ordinal } from "openskill";
import * as R from "remeda";
import type { WinLossParticipationArray } from "~/db/tables-json";
import { MATCHES_COUNT_NEEDED_FOR_LEADERBOARD } from "~/features/leaderboards/leaderboards-constants";
import {
identifierToUserIds,
@@ -12,7 +13,7 @@ import type { AllMatchResult } from "~/features/tournament-match/TournamentMatch
import { matchEndedEarly } from "~/features/tournament-match/tournament-match-utils";
import invariant from "~/utils/invariant";
import { roundToNDecimalPlaces } from "~/utils/number";
import type { Tables, WinLossParticipationArray } from "../../../db/tables";
import type { Tables } from "../../../db/tables";
import { ensureOneStandingPerUser } from "../tournament-bracket-utils";
import type { Standing } from "./Bracket";
import type { ParsedBracket } from "./Progression";

View File

@@ -1,6 +1,7 @@
/** Map list generation logic for "TO pick" as in the map list is defined beforehand by TO and teams don't pick */
import type { Tables, TournamentRoundMaps } from "~/db/tables";
import type { Tables } from "~/db/tables";
import type { TournamentRoundMaps } from "~/db/tables-json";
import * as MapList from "~/features/map-list-generator/core/MapList";
import { MapPool } from "~/features/map-list-generator/core/map-pool";
import type { Round } from "~/modules/brackets-model";

View File

@@ -0,0 +1,25 @@
/** Who picks or bans in a custom pick/ban flow step. */
export const WHO_SIDES = [
"RANDOM",
"RANDOM_OTHER",
"ALPHA",
"BRAVO",
"HIGHER_SEED",
"LOWER_SEED",
"WINNER",
"LOSER",
] as const;
export type WhoSide = (typeof WHO_SIDES)[number];
/** What happens in a custom pick/ban flow step. */
export const ACTION_TYPES = [
"ROLL",
"PICK",
"PICK_NO_MODE_REPEAT",
"BAN",
"MODE_PICK",
"MODE_BAN",
] as const;
export type ActionType = (typeof ACTION_TYPES)[number];

View File

@@ -1,5 +1,8 @@
import { z } from "zod";
import { ACTION_TYPES, WHO_SIDES } from "~/db/tables";
import {
ACTION_TYPES,
WHO_SIDES,
} from "~/features/tournament-bracket/tournament-bracket-constants";
import {
_action,
checkboxValueToBoolean,

View File

@@ -12,7 +12,7 @@ import { FormWithConfirm } from "~/components/FormWithConfirm";
import { Image, WeaponImage } from "~/components/Image";
import { NoteAvatar } from "~/components/NoteAvatar";
import { SubmitButton } from "~/components/SubmitButton";
import type { Pronouns } from "~/db/tables";
import type { Pronouns } from "~/db/tables-json";
import { useUser } from "~/features/auth/core/user";
import { IS_Q_LOOKING_MOBILE_BREAKPOINT } from "~/features/sendouq/q-constants";
import { useTournament } from "~/features/tournament/routes/to.$id";

View File

@@ -1,6 +1,6 @@
import type { LoaderFunctionArgs } from "react-router";
import * as R from "remeda";
import type { Pronouns } from "~/db/tables";
import type { Pronouns } from "~/db/tables-json";
import { getUser } from "~/features/auth/core/user.server";
import { tournamentFromDBCached } from "~/features/tournament-bracket/core/Tournament.server";
import * as UserCardRepository from "~/features/user-card/UserCardRepository.server";

View File

@@ -1,7 +1,8 @@
import { sql } from "kysely";
import { jsonArrayFrom } from "kysely/helpers/sqlite";
import { db } from "~/db/sql";
import { TournamentMatchStatus, type TournamentRoundMaps } from "~/db/tables";
import type { TournamentRoundMaps } from "~/db/tables-json";
import { Status } from "~/modules/brackets-model";
import type { ModeShort, StageId } from "~/modules/in-game-lists/types";
import invariant from "~/utils/invariant";
import { customAvatarUrl } from "~/utils/kysely.server";
@@ -429,7 +430,7 @@ export function findByTournamentTeamId(tournamentTeamId: number) {
eb(opponentTwoId, "=", tournamentTeamId),
]),
)
.where("TournamentMatch.status", ">=", TournamentMatchStatus.Completed)
.where("TournamentMatch.status", ">=", Status.Completed)
.where((eb) =>
eb.exists(
eb

View File

@@ -1,6 +1,5 @@
import type { ActionFunction } from "react-router";
import { sql } from "~/db/sql";
import { TournamentMatchStatus } from "~/db/tables";
import { requireUser } from "~/features/auth/core/user.server";
import * as ChatSystemMessage from "~/features/chat/ChatSystemMessage.server";
import * as ReportedWeaponRepository from "~/features/sendouq-match/ReportedWeaponRepository.server";
@@ -24,6 +23,7 @@ import {
tournamentWebsocketRoom,
} from "~/features/tournament-bracket/tournament-bracket-utils";
import * as TournamentMatchRepository from "~/features/tournament-match/TournamentMatchRepository.server";
import { Status } from "~/modules/brackets-model";
import { dateToDatabaseTimestamp } from "~/utils/dates";
import invariant from "~/utils/invariant";
import { logger } from "~/utils/logger";
@@ -78,8 +78,7 @@ export const action: ActionFunction = async ({ params, request }) => {
);
errorToastIfFalsy(
match.status !== TournamentMatchStatus.Locked &&
match.status !== TournamentMatchStatus.Waiting,
match.status !== Status.Locked && match.status !== Status.Waiting,
"Match is locked, waiting for teams to finish their previous matches",
);
@@ -745,10 +744,7 @@ export const action: ActionFunction = async ({ params, request }) => {
);
// can't lock if match status is not Locked or Waiting (team(s) busy with previous match), let's update their view to reflect that
if (
match.status !== TournamentMatchStatus.Locked &&
match.status !== TournamentMatchStatus.Waiting
) {
if (match.status !== Status.Locked && match.status !== Status.Waiting) {
return null;
}

View File

@@ -14,10 +14,10 @@ import { InfoPopover } from "~/components/InfoPopover";
import { Label } from "~/components/Label";
import { TAB_KEYS } from "~/components/match-page/MatchTabs";
import { SubmitButton } from "~/components/SubmitButton";
import { TournamentMatchStatus } from "~/db/tables";
import { useUser } from "~/features/auth/core/user";
import { useTournament } from "~/features/tournament/routes/to.$id";
import type { TournamentDataTeam } from "~/features/tournament-bracket/core/Tournament.server";
import { Status } from "~/modules/brackets-model";
import type { TournamentMatchLoaderData } from "../loaders/to.$id.matches.$mid.server";
import { useMatch } from "../match-page-context";
import { OrganizerMatchMapListDialog } from "./OrganizerMatchMapListDialog";
@@ -96,8 +96,7 @@ function AdminCastSection({
false;
const canLock =
(matchStatus === TournamentMatchStatus.Locked ||
matchStatus === TournamentMatchStatus.Waiting) &&
(matchStatus === Status.Locked || matchStatus === Status.Waiting) &&
!isLocked;
const canUnlock = !isMatchStarted && isLocked;

View File

@@ -23,7 +23,7 @@ import { MatchBannerBottomRow } from "~/components/match-page/MatchBannerBottomR
import { MatchBannerStartedAt } from "~/components/match-page/MatchBannerStartedAt";
import { MatchBannerTimer } from "~/components/match-page/MatchBannerTimer";
import { MatchBannerTopRow } from "~/components/match-page/MatchBannerTopRow";
import type { TournamentRoundMaps } from "~/db/tables";
import type { TournamentRoundMaps } from "~/db/tables-json";
import { useTournament } from "~/features/tournament/routes/to.$id";
import {
isLeagueRoundLocked,

View File

@@ -1,4 +1,4 @@
import type { TournamentRoundMaps } from "~/db/tables";
import type { TournamentRoundMaps } from "~/db/tables-json";
import * as TournamentRepository from "~/features/tournament/TournamentRepository.server";
import * as PickBan from "~/features/tournament-bracket/core/PickBan";
import type { TournamentDataTeam } from "~/features/tournament-bracket/core/Tournament.server";

View File

@@ -1,4 +1,5 @@
import type { Tables, TournamentRoundMaps } from "~/db/tables";
import type { Tables } from "~/db/tables";
import type { TournamentRoundMaps } from "~/db/tables-json";
import { MapPool } from "~/features/map-list-generator/core/map-pool";
import { mapPickingStyleToModes } from "~/features/tournament/tournament-utils";
import type { Bracket } from "~/features/tournament-bracket/core/Bracket";

View File

@@ -1,7 +1,6 @@
import * as React from "react";
import { TAB_KEYS } from "~/components/match-page/MatchTabs";
import { resolveRoomPass } from "~/components/match-page/utils";
import { TournamentMatchStatus } from "~/db/tables";
import { useUser } from "~/features/auth/core/user";
import { useTournament } from "~/features/tournament/routes/to.$id";
import { isLeagueRoundLocked } from "~/features/tournament/tournament-utils";
@@ -11,6 +10,7 @@ import {
groupNumberToLetters,
tournamentTeamToActiveRosterUserIds,
} from "~/features/tournament-bracket/tournament-bracket-utils";
import { Status } from "~/modules/brackets-model";
import type { TournamentMatchLoaderData } from "./loaders/to.$id.matches.$mid.server";
import { matchIsLocked, resolveHostingTeam } from "./tournament-match-utils";
@@ -103,8 +103,7 @@ export function MatchPageProvider({
});
const waitingForPreviousMatch =
data.match.status === TournamentMatchStatus.Locked ||
data.match.status === TournamentMatchStatus.Waiting;
data.match.status === Status.Locked || data.match.status === Status.Waiting;
const joinInfo = resolveJoinInfo({ tournament, data, teams });

View File

@@ -1,6 +1,6 @@
import type { TFunction } from "i18next";
import * as R from "remeda";
import type { TournamentRoundMaps } from "~/db/tables";
import type { TournamentRoundMaps } from "~/db/tables-json";
import type { Tournament } from "~/features/tournament-bracket/core/Tournament";
import type { TournamentDataTeam } from "~/features/tournament-bracket/core/Tournament.server";
import type { ModeShort, StageId } from "~/modules/in-game-lists/types";

View File

@@ -9,6 +9,16 @@ export const ESTABLISHED_ORG = {
LOSE_THRESHOLD: 100,
};
export const TOURNAMENT_ORGANIZATION_ROLES = [
"ADMIN",
"MEMBER",
"ORGANIZER",
"STREAMER",
] as const;
export type TournamentOrganizationRole =
(typeof TOURNAMENT_ORGANIZATION_ROLES)[number];
export const TOURNAMENT_ORGANIZATION = {
DESCRIPTION_MAX_LENGTH: 1_000,
BAN_REASON_MAX_LENGTH: 200,

View File

@@ -1,6 +1,8 @@
import { z } from "zod";
import { TOURNAMENT_ORGANIZATION_ROLES } from "~/db/tables";
import { TOURNAMENT_ORGANIZATION } from "~/features/tournament-organization/tournament-organization-constants";
import {
TOURNAMENT_ORGANIZATION,
TOURNAMENT_ORGANIZATION_ROLES,
} from "~/features/tournament-organization/tournament-organization-constants";
import {
array,
badges,

View File

@@ -1,6 +1,7 @@
import { afterEach, beforeEach, describe, expect, test } from "vitest";
import { db } from "~/db/sql";
import type { Tables, TournamentAuditLogMetadata } from "~/db/tables";
import type { Tables } from "~/db/tables";
import type { TournamentAuditLogMetadata } from "~/db/tables-json";
import { dbInsertUsers, dbReset, withUserId } from "~/utils/Test";
import * as TournamentAuditLogRepository from "./TournamentAuditLogRepository.server";

View File

@@ -2,7 +2,8 @@ import { sub } from "date-fns";
import type { Transaction } from "kysely";
import { jsonObjectFrom } from "kysely/helpers/sqlite";
import { db } from "~/db/sql";
import type { DB, Tables, TournamentAuditLogMetadata } from "~/db/tables";
import type { DB, Tables } from "~/db/tables";
import type { TournamentAuditLogMetadata } from "~/db/tables-json";
import { actorId } from "~/features/auth/core/user.server";
import { databaseTimestampNow, dateToDatabaseTimestamp } from "~/utils/dates";
import { commonUserSelect } from "~/utils/kysely.server";

View File

@@ -3,13 +3,12 @@ import { type Insertable, type NotNull, sql, type Transaction } from "kysely";
import { jsonArrayFrom, jsonObjectFrom } from "kysely/helpers/sqlite";
import { ordinal } from "openskill";
import { db } from "~/db/sql";
import type { DB, Tables } from "~/db/tables";
import type {
CastedMatchesInfo,
DB,
PreparedMaps,
Tables,
TournamentSettings,
} from "~/db/tables";
} from "~/db/tables-json";
import { actorId } from "~/features/auth/core/user.server";
import { identifierToUserIds } from "~/features/mmr/mmr-utils";
import * as Progression from "~/features/tournament-bracket/core/Progression";

View File

@@ -1,5 +1,5 @@
import { sql } from "~/db/sql";
import type { TournamentRoundMaps } from "~/db/tables";
import type { TournamentRoundMaps } from "~/db/tables-json";
const stm = sql.prepare(/*sql*/ `
update "TournamentRound"

View File

@@ -1,4 +1,5 @@
import { Config } from "~/config";
import type { StageType } from "~/modules/brackets-model";
import { TEAM } from "../team/team-constants";
export const TOURNAMENT = {
@@ -35,6 +36,42 @@ export const TOURNAMENT = {
},
} as const;
export const TOURNAMENT_STAGE_TYPES = [
"single_elimination",
"double_elimination",
"round_robin",
"swiss",
] as const satisfies readonly StageType[];
// 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
export type TournamentMapPickingStyle =
| "TO"
| "AUTO_ALL"
| "AUTO_SZ"
| "AUTO_TC"
| "AUTO_RM"
| "AUTO_CB";
export const TOURNAMENT_STAFF_ROLES = ["ORGANIZER", "STREAMER"] as const;
export type TournamentStaffRole = (typeof TOURNAMENT_STAFF_ROLES)[number];
export const TOURNAMENT_AUDIT_LOG_TYPES = [
"MEMBER_ADDED",
"MEMBER_REMOVED",
"TEAM_REGISTERED",
"TEAM_UNREGISTERED",
"TEAM_CHECKED_IN",
"TEAM_CHECKED_OUT",
"TEAM_DROPPED_OUT",
"TEAM_DROP_OUT_UNDONE",
"UPDATE_IN_GAME_NAME",
] as const;
export type TournamentAuditLogType =
(typeof TOURNAMENT_AUDIT_LOG_TYPES)[number];
export const LEAGUES =
process.env.NODE_ENV === "development" && !Config.prodMode
? {

View File

@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import type { CastedMatchesInfo } from "~/db/tables";
import type { CastedMatchesInfo } from "~/db/tables-json";
import * as Seasons from "../mmr/core/Seasons";
import type { ParsedBracket } from "../tournament-bracket/core/Progression";
import { testTournament } from "../tournament-bracket/core/tests/test-utils";

View File

@@ -1,14 +1,14 @@
import { sub } from "date-fns";
import * as R from "remeda";
import type {
CastedMatchesInfo,
TournamentStageSettings,
} from "~/db/tables-json";
import { modesShort, rankedModesShort } from "~/modules/in-game-lists/modes";
import type { ModeShort, StageId } from "~/modules/in-game-lists/types";
import { weekNumberToDate } from "~/utils/dates";
import { SHORT_NANOID_LENGTH } from "~/utils/id";
import type {
CastedMatchesInfo,
Tables,
TournamentStageSettings,
} from "../../db/tables";
import type { Tables } from "../../db/tables";
import { assertUnreachable } from "../../utils/types";
import { MapPool } from "../map-list-generator/core/map-pool";
import { BANNED_MAPS } from "../match-profile/banned-maps";

View File

@@ -3,13 +3,8 @@ import type { Expression, ExpressionBuilder } from "kysely";
import { sql } from "kysely";
import { jsonBuildObject, jsonObjectFrom } from "kysely/helpers/sqlite";
import { db } from "~/db/sql";
import type {
CustomTheme,
HideableUserCardStat,
PeakXP,
Tables,
XRankPlacementRegion,
} from "~/db/tables";
import type { Tables } from "~/db/tables";
import type { CustomTheme, PeakXP } from "~/db/tables-json";
import { actorId, actorIdOrNull } from "~/features/auth/core/user.server";
import { cachedFullUserLeaderboard } from "~/features/leaderboards/core/leaderboards.server";
import { LFG } from "~/features/lfg/lfg-constants";
@@ -17,6 +12,7 @@ import * as Seasons from "~/features/mmr/core/Seasons";
import { TIERS } from "~/features/mmr/mmr-constants";
import type { TieredSkill } from "~/features/mmr/tiered.server";
import { userSkills } from "~/features/mmr/tiered.server";
import type { XRankPlacementRegion } from "~/features/top-search/top-search-types";
import type { StageId } from "~/modules/in-game-lists/types";
import { dateToDatabaseTimestamp } from "~/utils/dates";
import {
@@ -25,6 +21,7 @@ import {
} from "~/utils/kysely.server";
import { PRESET_COLORS } from "../tier-list-maker/tier-list-maker-constants";
import type {
HideableUserCardStat,
UserCardData,
UserCardStat,
UserCardStatXPValue,

View File

@@ -1,11 +1,11 @@
import { type ActionFunction, redirect } from "react-router";
import type { HideableUserCardStat } from "~/db/tables";
import { requireUser } from "~/features/auth/core/user.server";
import * as XRankPlacementRepository from "~/features/top-search/XRankPlacementRepository.server";
import { parseFormDataWithImages } from "~/form/parse.server";
import { userPage } from "~/utils/urls";
import * as UserCardRepository from "../UserCardRepository.server";
import { updateUserCardSchema } from "../user-card-schemas";
import type { HideableUserCardStat } from "../user-card-types";
import { isValidUnverifiedXp } from "../user-card-utils";
export const action: ActionFunction = async ({ request }) => {

View File

@@ -23,8 +23,8 @@ import { Image, TierImage } from "~/components/Image";
import { LocaleTime } from "~/components/LocaleTime";
import { NoteAvatar } from "~/components/NoteAvatar";
import { Placement } from "~/components/Placement";
import type { XRankPlacementRegion } from "~/db/tables";
import { useUser } from "~/features/auth/core/user";
import type { XRankPlacementRegion } from "~/features/top-search/top-search-types";
import { MutualFriends } from "~/features/user-page/components/MutualFriends";
import { ReportUserDialog } from "~/features/user-report/components/ReportUserDialog";
import { useLayoutSize } from "~/hooks/useMainContentWidth";

View File

@@ -6,8 +6,8 @@ import {
useSearchParams,
} from "react-router";
import { Main } from "~/components/Main";
import type { XRankPlacementRegion } from "~/db/tables";
import { HowToLinkPopover } from "~/features/top-search/components/HowToLinkPopover";
import type { XRankPlacementRegion } from "~/features/top-search/top-search-types";
import { type CustomFieldRenderProps, FormField } from "~/form/FormField";
import { existingImage } from "~/form/image-field";
import { SendouForm, useFormFieldContext } from "~/form/SendouForm";

View File

@@ -1,4 +1,6 @@
import type { CustomTheme, Tables, XRankPlacementRegion } from "~/db/tables";
import type { Tables } from "~/db/tables";
import type { CustomTheme } from "~/db/tables-json";
import type { XRankPlacementRegion } from "~/features/top-search/top-search-types";
import type { StageId } from "~/modules/in-game-lists/types";
import type { CommonUser } from "~/utils/kysely.server";
import type { TieredSkill } from "../mmr/tiered.server";
@@ -65,6 +67,9 @@ export type UserCardStat =
top: number | null;
};
/** Card stat types that a user can hide from their card. */
export type HideableUserCardStat = Extract<UserCardStat["type"], "XP" | "DIV">;
export interface UserCardStatXPValue {
isVerified: boolean;
region: XRankPlacementRegion;

View File

@@ -3,15 +3,10 @@ import { sql } from "kysely";
import { jsonArrayFrom } from "kysely/helpers/sqlite";
import * as R from "remeda";
import { db } from "~/db/sql";
import type {
BuildSort,
CustomTheme,
DB,
Tables,
TablesInsertable,
UserPreferences,
} from "~/db/tables";
import type { DB, Tables, TablesInsertable } from "~/db/tables";
import type { CustomTheme, UserPreferences } from "~/db/tables-json";
import { actorId } from "~/features/auth/core/user.server";
import type { BuildSort } from "~/features/user-page/user-page-constants";
import { userRoles } from "~/modules/permissions/mapper.server";
import { isSupporter } from "~/modules/permissions/utils";
import { databaseTimestampNow, dateToDatabaseTimestamp } from "~/utils/dates";

View File

@@ -1,9 +1,9 @@
import { type ActionFunction, redirect } from "react-router";
import { z } from "zod";
import { BUILD_SORT_IDENTIFIERS } from "~/db/tables";
import { requireUser } from "~/features/auth/core/user.server";
import * as BuildRepository from "~/features/builds/BuildRepository.server";
import * as UserRepository from "~/features/user-page/UserRepository.server";
import { BUILD_SORT_IDENTIFIERS } from "~/features/user-page/user-page-constants";
import { errorToastIfFalsy, parseRequestPayload } from "~/utils/remix.server";
import { assertUnreachable } from "~/utils/types";
import { userBuildsPage } from "~/utils/urls";

View File

@@ -2,12 +2,14 @@ import * as React from "react";
import { useTranslation } from "react-i18next";
import { AbilitiesSelector } from "~/components/AbilitiesSelector";
import { GearSelect } from "~/components/GearSelect";
import type { GearType } from "~/db/tables";
import type { CustomFieldRenderProps } from "~/form/FormField";
import { FormFieldWrapper } from "~/form/fields/FormFieldWrapper";
import { SendouForm, useFormFieldContext } from "~/form/SendouForm";
import { rankedModesShort } from "~/modules/in-game-lists/modes";
import type { BuildAbilitiesTupleWithUnknown } from "~/modules/in-game-lists/types";
import type {
BuildAbilitiesTupleWithUnknown,
GearType,
} from "~/modules/in-game-lists/types";
import { newBuildSchema } from "../user-page-schemas";
interface NewBuildFormProps {

View File

@@ -1,5 +1,5 @@
import type { BuildSort } from "~/db/tables";
import type * as BuildRepository from "~/features/builds/BuildRepository.server";
import type { BuildSort } from "~/features/user-page/user-page-constants";
import { modesShort } from "~/modules/in-game-lists/modes";
import type { MainWeaponId } from "~/modules/in-game-lists/types";
import { weaponIdToBaseWeaponId } from "~/modules/in-game-lists/weapon-ids";

View File

@@ -9,8 +9,11 @@ import { SendouMenu, SendouMenuItem } from "~/components/elements/Menu";
import { FormMessage } from "~/components/FormMessage";
import { Image, WeaponImage } from "~/components/Image";
import { SubmitButton } from "~/components/SubmitButton";
import { BUILD_SORT_IDENTIFIERS, type BuildSort } from "~/db/tables";
import { useUser } from "~/features/auth/core/user";
import {
BUILD_SORT_IDENTIFIERS,
type BuildSort,
} from "~/features/user-page/user-page-constants";
import { useSearchParamState } from "~/hooks/useSearchParamState";
import type { MainWeaponId } from "~/modules/in-game-lists/types";
import { mainWeaponIds } from "~/modules/in-game-lists/weapon-ids";

View File

@@ -21,8 +21,39 @@ export const SPL2_JOIN_ORDER_CUTOFF = 13_589;
export const MATCHES_PER_SEASONS_PAGE = 8;
export const RESULTS_PER_PAGE = 25;
export const HIGHLIGHTS_RESULTS_MAX = 500;
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 const DEFAULT_BUILD_SORT = ["WEAPON_POOL", "UPDATED_AT"] as const;
export const SUBJECT_PRONOUNS = ["he", "she", "they", "it", "any"] as const;
export type SubjectPronoun = (typeof SUBJECT_PRONOUNS)[number];
export const OBJECT_PRONOUNS = [
"him",
"her",
"them",
"its",
"all",
...SUBJECT_PRONOUNS,
] as const;
export type ObjectPronoun = (typeof OBJECT_PRONOUNS)[number];
/**
* An array of ISO 3166-1 alpha-2 country codes.
* Each entry is a two-letter uppercase string representing a country or territory. Sorted alphabetically.

View File

@@ -1,7 +1,10 @@
import { z } from "zod";
import { OBJECT_PRONOUNS, SUBJECT_PRONOUNS } from "~/db/tables";
import { BADGE } from "~/features/badges/badges-constants";
import * as Seasons from "~/features/mmr/core/Seasons";
import {
OBJECT_PRONOUNS,
SUBJECT_PRONOUNS,
} from "~/features/user-page/user-page-constants";
import {
badges,
checkboxGroup,

View File

@@ -1,4 +1,4 @@
import type { UserReportCategory } from "~/db/tables";
import type { UserReportCategory } from "~/features/user-report/user-report-constants";
import {
sendModDiscordWebhook,
truncateEmbedValue,

View File

@@ -1,10 +1,18 @@
import type { UserReportCategory } from "~/db/tables";
export const USER_REPORT = {
DESCRIPTION_MAX_LENGTH: 2000,
MATCH_ID_MAX_LENGTH: 10,
};
export const USER_REPORT_CATEGORIES = [
"INAPPROPRIATE_CONTENT",
"ALTING",
"HARASSMENT",
"CHEATING",
"OTHER",
] as const;
export type UserReportCategory = (typeof USER_REPORT_CATEGORIES)[number];
/** English display names, shown on the staff-only admin tab and in the Discord webhook embed. */
export const USER_REPORT_CATEGORY_LABELS: Record<UserReportCategory, string> = {
INAPPROPRIATE_CONTENT: "Inappropriate content",

Some files were not shown because too many files have changed in this diff Show More