Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions convex/_fixtures/createMockTournamentCompetitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,12 @@ export const createMockTournamentCompetitor = (
rank: -1,
rankingFactors: {} as RankingFactorValues,
registrations: [],
displayName: 'Test Tournament',
tournamentId: 'T0' as Id<'tournaments'>,
...overrides,
_id: overrides.id as Id<'tournamentCompetitors'>,
activeRegistrationCount: 0,
availableActions: [],
});

export const createMockTournamentCompetitors = (
Expand Down
18 changes: 16 additions & 2 deletions convex/_generated/api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import type * as _model_common__helpers_gameSystem_computeRankingFactors from ".
import type * as _model_common__helpers_gameSystem_createSortByRanking from "../_model/common/_helpers/gameSystem/createSortByRanking.js";
import type * as _model_common__helpers_gameSystem_divideBaseStats from "../_model/common/_helpers/gameSystem/divideBaseStats.js";
import type * as _model_common__helpers_gameSystem_sumBaseStats from "../_model/common/_helpers/gameSystem/sumBaseStats.js";
import type * as _model_common__helpers_getCountryName from "../_model/common/_helpers/getCountryName.js";
import type * as _model_common__helpers_getEnvironment from "../_model/common/_helpers/getEnvironment.js";
import type * as _model_common__helpers_getRange from "../_model/common/_helpers/getRange.js";
import type * as _model_common__helpers_getStaticEnumConvexValidator from "../_model/common/_helpers/getStaticEnumConvexValidator.js";
Expand Down Expand Up @@ -119,6 +120,8 @@ import type * as _model_photos_mutations_createPhoto from "../_model/photos/muta
import type * as _model_photos_queries_getPhoto from "../_model/photos/queries/getPhoto.js";
import type * as _model_photos_table from "../_model/photos/table.js";
import type * as _model_tournamentCompetitors__helpers_deepenTournamentCompetitor from "../_model/tournamentCompetitors/_helpers/deepenTournamentCompetitor.js";
import type * as _model_tournamentCompetitors__helpers_getAvailableActions from "../_model/tournamentCompetitors/_helpers/getAvailableActions.js";
import type * as _model_tournamentCompetitors__helpers_getDisplayName from "../_model/tournamentCompetitors/_helpers/getDisplayName.js";
import type * as _model_tournamentCompetitors__helpers_sortTournamentCompetitorsByName from "../_model/tournamentCompetitors/_helpers/sortTournamentCompetitorsByName.js";
import type * as _model_tournamentCompetitors_index from "../_model/tournamentCompetitors/index.js";
import type * as _model_tournamentCompetitors_mutations_createTournamentCompetitor from "../_model/tournamentCompetitors/mutations/createTournamentCompetitor.js";
Expand Down Expand Up @@ -160,10 +163,13 @@ import type * as _model_tournamentPairings_queries_getTournamentPairingsByTourna
import type * as _model_tournamentPairings_table from "../_model/tournamentPairings/table.js";
import type * as _model_tournamentRegistrations__helpers_checkUserIsRegistered from "../_model/tournamentRegistrations/_helpers/checkUserIsRegistered.js";
import type * as _model_tournamentRegistrations__helpers_deepenTournamentRegistration from "../_model/tournamentRegistrations/_helpers/deepenTournamentRegistration.js";
import type * as _model_tournamentRegistrations__helpers_getAvailableActions from "../_model/tournamentRegistrations/_helpers/getAvailableActions.js";
import type * as _model_tournamentRegistrations__helpers_getCreateSuccessMessage from "../_model/tournamentRegistrations/_helpers/getCreateSuccessMessage.js";
import type * as _model_tournamentRegistrations_index from "../_model/tournamentRegistrations/index.js";
import type * as _model_tournamentRegistrations_mutations_createTournamentRegistration from "../_model/tournamentRegistrations/mutations/createTournamentRegistration.js";
import type * as _model_tournamentRegistrations_mutations_deleteTournamentRegistration from "../_model/tournamentRegistrations/mutations/deleteTournamentRegistration.js";
import type * as _model_tournamentRegistrations_mutations_toggleActive from "../_model/tournamentRegistrations/mutations/toggleActive.js";
import type * as _model_tournamentRegistrations_queries_getTournamentRegistrationByTournamentUser from "../_model/tournamentRegistrations/queries/getTournamentRegistrationByTournamentUser.js";
import type * as _model_tournamentRegistrations_queries_getTournamentRegistrationsByCompetitor from "../_model/tournamentRegistrations/queries/getTournamentRegistrationsByCompetitor.js";
import type * as _model_tournamentRegistrations_queries_getTournamentRegistrationsByTournament from "../_model/tournamentRegistrations/queries/getTournamentRegistrationsByTournament.js";
import type * as _model_tournamentRegistrations_queries_getTournamentRegistrationsByUser from "../_model/tournamentRegistrations/queries/getTournamentRegistrationsByUser.js";
Expand Down Expand Up @@ -196,6 +202,8 @@ import type * as _model_tournaments__helpers_checkTournamentAuth from "../_model
import type * as _model_tournaments__helpers_checkTournamentVisibility from "../_model/tournaments/_helpers/checkTournamentVisibility.js";
import type * as _model_tournaments__helpers_deepenTournament from "../_model/tournaments/_helpers/deepenTournament.js";
import type * as _model_tournaments__helpers_extractSearchTokens from "../_model/tournaments/_helpers/extractSearchTokens.js";
import type * as _model_tournaments__helpers_getAvailableActions from "../_model/tournaments/_helpers/getAvailableActions.js";
import type * as _model_tournaments__helpers_getDisplayName from "../_model/tournaments/_helpers/getDisplayName.js";
import type * as _model_tournaments__helpers_getTournamentDeep from "../_model/tournaments/_helpers/getTournamentDeep.js";
import type * as _model_tournaments__helpers_getTournamentNextRound from "../_model/tournaments/_helpers/getTournamentNextRound.js";
import type * as _model_tournaments__helpers_getTournamentPlayerUserIds from "../_model/tournaments/_helpers/getTournamentPlayerUserIds.js";
Expand All @@ -210,7 +218,6 @@ import type * as _model_tournaments_mutations_publishTournament from "../_model/
import type * as _model_tournaments_mutations_startTournament from "../_model/tournaments/mutations/startTournament.js";
import type * as _model_tournaments_mutations_startTournamentRound from "../_model/tournaments/mutations/startTournamentRound.js";
import type * as _model_tournaments_mutations_updateTournament from "../_model/tournaments/mutations/updateTournament.js";
import type * as _model_tournaments_queries_getAvailableTournamentActions from "../_model/tournaments/queries/getAvailableTournamentActions.js";
import type * as _model_tournaments_queries_getTournament from "../_model/tournaments/queries/getTournament.js";
import type * as _model_tournaments_queries_getTournamentByTournamentPairing from "../_model/tournaments/queries/getTournamentByTournamentPairing.js";
import type * as _model_tournaments_queries_getTournamentOpenRound from "../_model/tournaments/queries/getTournamentOpenRound.js";
Expand Down Expand Up @@ -313,6 +320,7 @@ declare const fullApi: ApiFromModules<{
"_model/common/_helpers/gameSystem/createSortByRanking": typeof _model_common__helpers_gameSystem_createSortByRanking;
"_model/common/_helpers/gameSystem/divideBaseStats": typeof _model_common__helpers_gameSystem_divideBaseStats;
"_model/common/_helpers/gameSystem/sumBaseStats": typeof _model_common__helpers_gameSystem_sumBaseStats;
"_model/common/_helpers/getCountryName": typeof _model_common__helpers_getCountryName;
"_model/common/_helpers/getEnvironment": typeof _model_common__helpers_getEnvironment;
"_model/common/_helpers/getRange": typeof _model_common__helpers_getRange;
"_model/common/_helpers/getStaticEnumConvexValidator": typeof _model_common__helpers_getStaticEnumConvexValidator;
Expand Down Expand Up @@ -412,6 +420,8 @@ declare const fullApi: ApiFromModules<{
"_model/photos/queries/getPhoto": typeof _model_photos_queries_getPhoto;
"_model/photos/table": typeof _model_photos_table;
"_model/tournamentCompetitors/_helpers/deepenTournamentCompetitor": typeof _model_tournamentCompetitors__helpers_deepenTournamentCompetitor;
"_model/tournamentCompetitors/_helpers/getAvailableActions": typeof _model_tournamentCompetitors__helpers_getAvailableActions;
"_model/tournamentCompetitors/_helpers/getDisplayName": typeof _model_tournamentCompetitors__helpers_getDisplayName;
"_model/tournamentCompetitors/_helpers/sortTournamentCompetitorsByName": typeof _model_tournamentCompetitors__helpers_sortTournamentCompetitorsByName;
"_model/tournamentCompetitors/index": typeof _model_tournamentCompetitors_index;
"_model/tournamentCompetitors/mutations/createTournamentCompetitor": typeof _model_tournamentCompetitors_mutations_createTournamentCompetitor;
Expand Down Expand Up @@ -453,10 +463,13 @@ declare const fullApi: ApiFromModules<{
"_model/tournamentPairings/table": typeof _model_tournamentPairings_table;
"_model/tournamentRegistrations/_helpers/checkUserIsRegistered": typeof _model_tournamentRegistrations__helpers_checkUserIsRegistered;
"_model/tournamentRegistrations/_helpers/deepenTournamentRegistration": typeof _model_tournamentRegistrations__helpers_deepenTournamentRegistration;
"_model/tournamentRegistrations/_helpers/getAvailableActions": typeof _model_tournamentRegistrations__helpers_getAvailableActions;
"_model/tournamentRegistrations/_helpers/getCreateSuccessMessage": typeof _model_tournamentRegistrations__helpers_getCreateSuccessMessage;
"_model/tournamentRegistrations/index": typeof _model_tournamentRegistrations_index;
"_model/tournamentRegistrations/mutations/createTournamentRegistration": typeof _model_tournamentRegistrations_mutations_createTournamentRegistration;
"_model/tournamentRegistrations/mutations/deleteTournamentRegistration": typeof _model_tournamentRegistrations_mutations_deleteTournamentRegistration;
"_model/tournamentRegistrations/mutations/toggleActive": typeof _model_tournamentRegistrations_mutations_toggleActive;
"_model/tournamentRegistrations/queries/getTournamentRegistrationByTournamentUser": typeof _model_tournamentRegistrations_queries_getTournamentRegistrationByTournamentUser;
"_model/tournamentRegistrations/queries/getTournamentRegistrationsByCompetitor": typeof _model_tournamentRegistrations_queries_getTournamentRegistrationsByCompetitor;
"_model/tournamentRegistrations/queries/getTournamentRegistrationsByTournament": typeof _model_tournamentRegistrations_queries_getTournamentRegistrationsByTournament;
"_model/tournamentRegistrations/queries/getTournamentRegistrationsByUser": typeof _model_tournamentRegistrations_queries_getTournamentRegistrationsByUser;
Expand Down Expand Up @@ -489,6 +502,8 @@ declare const fullApi: ApiFromModules<{
"_model/tournaments/_helpers/checkTournamentVisibility": typeof _model_tournaments__helpers_checkTournamentVisibility;
"_model/tournaments/_helpers/deepenTournament": typeof _model_tournaments__helpers_deepenTournament;
"_model/tournaments/_helpers/extractSearchTokens": typeof _model_tournaments__helpers_extractSearchTokens;
"_model/tournaments/_helpers/getAvailableActions": typeof _model_tournaments__helpers_getAvailableActions;
"_model/tournaments/_helpers/getDisplayName": typeof _model_tournaments__helpers_getDisplayName;
"_model/tournaments/_helpers/getTournamentDeep": typeof _model_tournaments__helpers_getTournamentDeep;
"_model/tournaments/_helpers/getTournamentNextRound": typeof _model_tournaments__helpers_getTournamentNextRound;
"_model/tournaments/_helpers/getTournamentPlayerUserIds": typeof _model_tournaments__helpers_getTournamentPlayerUserIds;
Expand All @@ -503,7 +518,6 @@ declare const fullApi: ApiFromModules<{
"_model/tournaments/mutations/startTournament": typeof _model_tournaments_mutations_startTournament;
"_model/tournaments/mutations/startTournamentRound": typeof _model_tournaments_mutations_startTournamentRound;
"_model/tournaments/mutations/updateTournament": typeof _model_tournaments_mutations_updateTournament;
"_model/tournaments/queries/getAvailableTournamentActions": typeof _model_tournaments_queries_getAvailableTournamentActions;
"_model/tournaments/queries/getTournament": typeof _model_tournaments_queries_getTournament;
"_model/tournaments/queries/getTournamentByTournamentPairing": typeof _model_tournaments_queries_getTournamentByTournamentPairing;
"_model/tournaments/queries/getTournamentOpenRound": typeof _model_tournaments_queries_getTournamentOpenRound;
Expand Down
20 changes: 20 additions & 0 deletions convex/_model/common/_helpers/getCountryName.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { country, subdivision } from 'iso-3166-2';

export const getCountryName = (code: string): string | undefined => {
if (code === 'xx-lkt') {
return 'Landsknechte';
}
if (code === 'xx-mrc') {
return 'Mercenaries';
}
if (code === 'xx-prt') {
return 'Pirates';
}
if (code === 'un') {
return 'United Nations';
}
if (code.includes('-')) {
return subdivision(code)?.name;
}
return country(code)?.name;
};
3 changes: 3 additions & 0 deletions convex/_model/common/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ export const errors = {
CANNOT_REMOVE_LAST_ORGANIZER_FROM_TOURNAMENT: 'Cannot remove the last organizer from tournament.',
CANNOT_REMOVE_LAST_OWNER_FROM_TOURNAMENT: 'Please appoint another organizer as owner before deleting this one.',
COMPETITOR_ALREADY_HAS_MAX_PLAYERS: 'Team already has the maximum number of active players.',
CANNOT_CREATE_REGISTRATION_WITHOUT_COMPETITOR: 'Cannot create a registration without a competitor ID or name.',
CANNOT_CREATE_REGISTRATION_WITH_COMPETITOR_NAME_ID: 'Cannot create a registration with both a competitor ID and name.',
CANNOT_CREATE_REGISTRATION_WITHOUT_REAL_NAME: 'Cannot create a registration without revealing real name.',

// Tournament Lifecycle
CANNOT_CLOSE_ROUND_ON_ARCHIVED_TOURNAMENT: 'Cannot close a round on an archived tournament.',
Expand Down
14 changes: 14 additions & 0 deletions convex/_model/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,17 @@ export type TriggerChange<TableName extends TableNames> = {
newDoc: Doc<TableName> | null;
oldDoc: Doc<TableName> | null;
};

export type MutationIssue = {
fieldPath: string;
message: string;
};

export type MutationResponse = {
success?: {
message: string;
}
error?: {
issues: MutationIssue[];
}
};
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { Doc } from '../../../_generated/dataModel';
import { QueryCtx } from '../../../_generated/server';
import { getTournamentRegistrationsByCompetitor } from '../../tournamentRegistrations';
import { getTournamentResultsByCompetitor } from '../../tournamentResults/queries/getTournamentResultsByCompetitor';
import { getTournamentResultsByCompetitor } from '../../tournamentResults';
import { getAvailableActions } from './getAvailableActions';
import { getDisplayName } from './getDisplayName';

/* eslint-disable @typescript-eslint/explicit-function-return-type */
/**
Expand Down Expand Up @@ -29,10 +31,14 @@ export const deepenTournamentCompetitor = async (
tournamentId: doc.tournamentId,
round: round ?? tournament?.lastRound ?? 0,
});

const availableActions = await getAvailableActions(ctx, doc);
const displayName = await getDisplayName(ctx, doc);
return {
...doc,
...results,
activeRegistrationCount: registrations.filter((r) => r.active).length,
availableActions,
displayName,
registrations,
};
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { getAuthUserId } from '@convex-dev/auth/server';

import { Doc } from '../../../_generated/dataModel';
import { QueryCtx } from '../../../_generated/server';
import { checkUserIsTournamentOrganizer } from '../../tournamentOrganizers';
import { checkUserIsRegistered } from '../../tournamentRegistrations/_helpers/checkUserIsRegistered';

export enum TournamentCompetitorActionKey {
// ---- TO Actions ----

/** Create a TournamentRegistration for this TournamentCompetitor. */
AddPlayer = 'addPlayer',

/** Toggle this TournamentCompetitor's 'active' field to true or false. */
ToggleActive = 'toggleActive',

// ---- TO or Captain Actions ----

/** Edit this TournamentCompetitor. */
Edit = 'edit',

/** Delete this TournamentCompetitor. */
Delete = 'delete',

// ---- Player Actions ----
/** Create own TournamentRegistration for this TournamentCompetitor. */
Join = 'join',

/** Delete own TournamentRegistration for this TournamentCompetitor. */
Leave = 'leave',
}

/**
* Gets a list of tournament competitor actions which are available to a user.
*
* @param ctx - Convex query context
* @param args - Convex query args
* @param args.id - ID of the tournament competitor
* @returns An array of TournamentCompetitorActionKey(s)
*/
export const getAvailableActions = async (
ctx: QueryCtx,
doc: Doc<'tournamentCompetitors'>,
): Promise<TournamentCompetitorActionKey[]> => {

const tournament = await ctx.db.get(doc.tournamentId);
if (!tournament) {
return [];
}
const tournamentRegistrations = await ctx.db.query('tournamentRegistrations')
.withIndex('by_tournament_competitor', (q) => q.eq('tournamentCompetitorId', doc._id))
.collect();

// --- CHECK AUTH ----
const userId = await getAuthUserId(ctx);

const isOrganizer = await checkUserIsTournamentOrganizer(ctx, tournament._id, userId);
const isPlayer = await checkUserIsRegistered(ctx, tournament._id, userId);
const isTeamTournament = tournament.competitorSize > 1;
const isCaptain = isTeamTournament && userId && doc.captainUserId === userId;
const isTeamPlayer = tournamentRegistrations.find((r) => r.userId === userId);
const hasCurrentRound = tournament.currentRound !== undefined;

// ---- PRIMARY ACTIONS ----
const actions: TournamentCompetitorActionKey[] = [];

if (tournament.status === 'archived') {
return actions;
}

// TO Actions:
if (isOrganizer && tournament.status !== 'draft' && isTeamTournament && !hasCurrentRound) {
actions.push(TournamentCompetitorActionKey.AddPlayer);
}

if (isOrganizer && tournament.status === 'active' && !hasCurrentRound) {
actions.push(TournamentCompetitorActionKey.ToggleActive);
}

// TO or Captain Actions:
if (isOrganizer || isCaptain) {
actions.push(TournamentCompetitorActionKey.Edit);
}

if ((isOrganizer || isCaptain) && tournament.status === 'published') {
actions.push(TournamentCompetitorActionKey.Delete);
}

// Player Actions:
if (!isPlayer && tournament.status === 'published' && isTeamTournament) {
actions.push(TournamentCompetitorActionKey.Join);
}

if (isTeamPlayer && tournament.status === 'published') {
actions.push(TournamentCompetitorActionKey.Leave);
}

return actions;
};
42 changes: 42 additions & 0 deletions convex/_model/tournamentCompetitors/_helpers/getDisplayName.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { ConvexError } from 'convex/values';

import { Doc } from '../../../_generated/dataModel';
import { QueryCtx } from '../../../_generated/server';
import { getCountryName } from '../../common/_helpers/getCountryName';
import { getErrorMessage } from '../../common/errors';
import { getTournamentRegistrationsByCompetitor } from '../../tournamentRegistrations';

export const getDisplayName = async (
ctx: QueryCtx,
doc?: Doc<'tournamentCompetitors'> | null,
): Promise<string> => {
const fallBack = 'Unknown Competitor'; // TODO: Language support

if (!doc) {
return fallBack;
}

const activeRegistrations = await getTournamentRegistrationsByCompetitor(ctx, {
tournamentCompetitorId: doc._id,
activeOnly: true,
});

const tournament = await ctx.db.get(doc.tournamentId);
if (!tournament) {
throw new ConvexError(getErrorMessage('TOURNAMENT_NOT_FOUND'));
}

// If competitor has only 1 player, just use the player's name:
if (tournament?.competitorSize === 1 && activeRegistrations[0].user) {
return activeRegistrations[0].user.displayName;
}

// Use the country name if there is one, otherwise just use the team name:
if (doc.teamName) {
const countryName = getCountryName(doc.teamName);
return countryName ?? doc.teamName;
}

// Fallback:
return fallBack;
};
10 changes: 10 additions & 0 deletions convex/_model/tournamentCompetitors/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
import { Infer } from 'convex/values';

import { Id } from '../../_generated/dataModel';
import { scoreAdjustment } from '../common/scoreAdjustment';

export type TournamentCompetitorId = Id<'tournamentCompetitors'>;
export type ScoreAdjustment = Infer<typeof scoreAdjustment>;

// Helpers
export {
deepenTournamentCompetitor,
type DeepTournamentCompetitor,
} from './_helpers/deepenTournamentCompetitor';
export {
TournamentCompetitorActionKey,
} from './_helpers/getAvailableActions';
export {
getDisplayName,
} from './_helpers/getDisplayName';

// Mutations
export {
Expand Down
Loading