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
14 changes: 12 additions & 2 deletions convex/_generated/api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import type * as _model_common_themes from "../_model/common/themes.js";
import type * as _model_common_tournamentStatus from "../_model/common/tournamentStatus.js";
import type * as _model_common_types from "../_model/common/types.js";
import type * as _model_files_index from "../_model/files/index.js";
import type * as _model_files_queries_getFileMetadata from "../_model/files/queries/getFileMetadata.js";
import type * as _model_files_queries_getFileUrl from "../_model/files/queries/getFileUrl.js";
import type * as _model_friendships__helpers_deepenFriendship from "../_model/friendships/_helpers/deepenFriendship.js";
import type * as _model_friendships_index from "../_model/friendships/index.js";
Expand All @@ -51,8 +52,10 @@ import type * as _model_gameSystems_battlefront_flamesOfWarV4__model_listData fr
import type * as _model_gameSystems_battlefront_flamesOfWarV4_index from "../_model/gameSystems/battlefront/flamesOfWarV4/index.js";
import type * as _model_gameSystems_index from "../_model/gameSystems/index.js";
import type * as _model_lists__helpers_deepenList from "../_model/lists/_helpers/deepenList.js";
import type * as _model_lists_actions_extractListData from "../_model/lists/actions/extractListData.js";
import type * as _model_lists_actions_importList from "../_model/lists/actions/importList.js";
import type * as _model_lists_index from "../_model/lists/index.js";
import type * as _model_lists_mutations_importListData from "../_model/lists/mutations/importListData.js";
import type * as _model_lists_mutations_createList from "../_model/lists/mutations/createList.js";
import type * as _model_lists_queries_getList from "../_model/lists/queries/getList.js";
import type * as _model_lists_table from "../_model/lists/table.js";
import type * as _model_matchResultComments__helpers_deepenMatchResultComment from "../_model/matchResultComments/_helpers/deepenMatchResultComment.js";
Expand Down Expand Up @@ -91,6 +94,7 @@ 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_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 @@ -130,6 +134,7 @@ 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_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";
Expand Down Expand Up @@ -288,6 +293,7 @@ declare const fullApi: ApiFromModules<{
"_model/common/tournamentStatus": typeof _model_common_tournamentStatus;
"_model/common/types": typeof _model_common_types;
"_model/files/index": typeof _model_files_index;
"_model/files/queries/getFileMetadata": typeof _model_files_queries_getFileMetadata;
"_model/files/queries/getFileUrl": typeof _model_files_queries_getFileUrl;
"_model/friendships/_helpers/deepenFriendship": typeof _model_friendships__helpers_deepenFriendship;
"_model/friendships/index": typeof _model_friendships_index;
Expand All @@ -302,8 +308,10 @@ declare const fullApi: ApiFromModules<{
"_model/gameSystems/battlefront/flamesOfWarV4/index": typeof _model_gameSystems_battlefront_flamesOfWarV4_index;
"_model/gameSystems/index": typeof _model_gameSystems_index;
"_model/lists/_helpers/deepenList": typeof _model_lists__helpers_deepenList;
"_model/lists/actions/extractListData": typeof _model_lists_actions_extractListData;
"_model/lists/actions/importList": typeof _model_lists_actions_importList;
"_model/lists/index": typeof _model_lists_index;
"_model/lists/mutations/importListData": typeof _model_lists_mutations_importListData;
"_model/lists/mutations/createList": typeof _model_lists_mutations_createList;
"_model/lists/queries/getList": typeof _model_lists_queries_getList;
"_model/lists/table": typeof _model_lists_table;
"_model/matchResultComments/_helpers/deepenMatchResultComment": typeof _model_matchResultComments__helpers_deepenMatchResultComment;
Expand Down Expand Up @@ -342,6 +350,7 @@ 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/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 @@ -381,6 +390,7 @@ 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/index": typeof _model_tournamentRegistrations_index;
"_model/tournamentRegistrations/mutations/createTournamentRegistration": typeof _model_tournamentRegistrations_mutations_createTournamentRegistration;
"_model/tournamentRegistrations/mutations/deleteTournamentRegistration": typeof _model_tournamentRegistrations_mutations_deleteTournamentRegistration;
Expand Down
44 changes: 22 additions & 22 deletions convex/_model/common/_helpers/gameSystem/aggregateTournamentData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,21 +30,8 @@ export const aggregateTournamentData = (
tournamentPairings: Doc<'tournamentPairings'>[],
matchResults: Doc<'matchResults'>[],
},
): TournamentAggregateData<AnyBaseStats> => {
// For faster look-up:
const playerUserIdMap = data.tournamentRegistrations.reduce((acc, registration) => ({
...acc,
[registration.userId]: {
registrationId: registration._id,
competitorId: registration.tournamentCompetitorId,
},
}), {} as Record<Id<'users'>, {
registrationId: Id<'tournamentRegistrations'>;
competitorId: Id<'tournamentCompetitors'>
}>);

): TournamentAggregateData<AnyBaseStats> => {
// Game system specifics:

const { extractMatchResultStats, defaultBaseStats } = getGameSystem(gameSystem);
type BaseStats = typeof defaultBaseStats;
type RegistrationStats= Record<Id<'tournamentRegistrations'>, TournamentPlayerMetadata<BaseStats>>;
Expand Down Expand Up @@ -90,27 +77,40 @@ export const aggregateTournamentData = (
if (!self.id) {
return;
}
const { registrationId, competitorId } = playerUserIdMap[self.id];
const competitorIds = [
pairing.tournamentCompetitor0Id,
pairing.tournamentCompetitor1Id,
];
const {
_id: registrationId,
tournamentCompetitorId,
} = data.tournamentRegistrations.find((r) => (
r.userId === self.id && competitorIds.includes(r.tournamentCompetitorId)
)) ?? {};
if (!registrationId || !tournamentCompetitorId) {
return;
}

// Add registration data:
registrationStats[registrationId].baseStats.self.push(self.stats);
registrationStats[registrationId].baseStats.opponent.push(opponent.stats);
registrationStats[registrationId].gamesPlayed += 1;

// Add competitor data:
competitorStats[competitorId].baseStats.self.push(self.stats);
competitorStats[competitorId].baseStats.opponent.push(opponent.stats);
competitorStats[competitorId].gamesPlayed += 1;
competitorStats[tournamentCompetitorId].baseStats.self.push(self.stats);
competitorStats[tournamentCompetitorId].baseStats.opponent.push(opponent.stats);
competitorStats[tournamentCompetitorId].gamesPlayed += 1;

const wasBye = pairing.table === null || !pairing.tournamentCompetitor0Id || !pairing.tournamentCompetitor1Id;
if (wasBye) {
competitorStats[competitorId].byeRounds.push(pairing.round);
competitorStats[tournamentCompetitorId].byeRounds.push(pairing.round);
}
if (pairing.table) {
competitorStats[competitorId].playedTables.push(pairing.table);
competitorStats[tournamentCompetitorId].playedTables.push(pairing.table);
}
if (opponent.id) {
competitorStats[competitorId].opponentIds.push(playerUserIdMap[opponent.id].competitorId);
const opponentCompetitorId = competitorIds.filter((id) => id !== tournamentCompetitorId).pop();
if (opponentCompetitorId) {
competitorStats[tournamentCompetitorId].opponentIds.push(opponentCompetitorId);
}
Comment on lines +111 to 114
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider using find() instead of filter().pop().

The current approach works but find() is more idiomatic and slightly more efficient for getting a single element.

🔎 Proposed refactor
-    const opponentCompetitorId = competitorIds.filter((id) => id !== tournamentCompetitorId).pop();
+    const opponentCompetitorId = competitorIds.find((id) => id !== tournamentCompetitorId);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const opponentCompetitorId = competitorIds.filter((id) => id !== tournamentCompetitorId).pop();
if (opponentCompetitorId) {
competitorStats[tournamentCompetitorId].opponentIds.push(opponentCompetitorId);
}
const opponentCompetitorId = competitorIds.find((id) => id !== tournamentCompetitorId);
if (opponentCompetitorId) {
competitorStats[tournamentCompetitorId].opponentIds.push(opponentCompetitorId);
}
🤖 Prompt for AI Agents
In convex/_model/common/_helpers/gameSystem/aggregateTournamentData.ts around
lines 111 to 114, replace the use of competitorIds.filter(...).pop() with
competitorIds.find(...) to get the first id that is not equal to
tournamentCompetitorId; update the variable name if helpful (e.g.,
opponentCompetitorId) and keep the existing conditional and push into
competitorStats[tournamentCompetitorId].opponentIds when the found id is truthy.
This removes the unnecessary array allocation from filter and uses the more
idiomatic, slightly more efficient find() for a single-element lookup.

};

Expand Down
25 changes: 25 additions & 0 deletions convex/_model/files/queries/getFileMetadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { GenericDoc } from '@convex-dev/auth/server';
import { SystemDataModel } from 'convex/server';
import {
ConvexError,
Infer,
v,
} from 'convex/values';

import { QueryCtx } from '../../../_generated/server';
import { getErrorMessage } from '../../common/errors';

export const getFileMetadataArgs = v.object({
id: v.id('_storage'),
});

export const getFileMetadata = async (
ctx: QueryCtx,
args: Infer<typeof getFileMetadataArgs>,
): Promise<GenericDoc<SystemDataModel,'_storage'> | null> => {
const fileMetadata = await ctx.db.system.get(args.id);
if (!fileMetadata) {
throw new ConvexError(getErrorMessage('FILE_NOT_FOUND'));
}
return fileMetadata;
};
Comment on lines +16 to +25
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Return type includes | null but the function never returns null.

The return type declares Promise<GenericDoc<SystemDataModel,'_storage'> | null>, but the implementation always either throws a ConvexError (line 22) or returns a non-null value (line 24). This creates an inconsistency between the declared contract and actual behavior, potentially leading consumers to write unnecessary null checks.

🔎 Proposed fix: Remove `| null` from return type
 export const getFileMetadata = async (
   ctx: QueryCtx,
   args: Infer<typeof getFileMetadataArgs>,
-): Promise<GenericDoc<SystemDataModel,'_storage'> | null> => {
+): Promise<GenericDoc<SystemDataModel,'_storage'>> => {
   const fileMetadata = await ctx.db.system.get(args.id);
   if (!fileMetadata) {
     throw new ConvexError(getErrorMessage('FILE_NOT_FOUND'));
   }
   return fileMetadata;
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const getFileMetadata = async (
ctx: QueryCtx,
args: Infer<typeof getFileMetadataArgs>,
): Promise<GenericDoc<SystemDataModel,'_storage'> | null> => {
const fileMetadata = await ctx.db.system.get(args.id);
if (!fileMetadata) {
throw new ConvexError(getErrorMessage('FILE_NOT_FOUND'));
}
return fileMetadata;
};
export const getFileMetadata = async (
ctx: QueryCtx,
args: Infer<typeof getFileMetadataArgs>,
): Promise<GenericDoc<SystemDataModel,'_storage'>> => {
const fileMetadata = await ctx.db.system.get(args.id);
if (!fileMetadata) {
throw new ConvexError(getErrorMessage('FILE_NOT_FOUND'));
}
return fileMetadata;
};
🤖 Prompt for AI Agents
In convex/_model/files/queries/getFileMetadata.ts around lines 16 to 25 the
function return type is declared as
Promise<GenericDoc<SystemDataModel,'_storage'> | null> but the implementation
never returns null (it throws on missing data and otherwise returns a non-null
document); update the declared return type to
Promise<GenericDoc<SystemDataModel,'_storage'>> to match actual behavior and
remove the unnecessary nullable union so callers won’t expect a null case.

49 changes: 49 additions & 0 deletions convex/_model/lists/actions/extractListData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { GameSystem } from '@ianpaschal/combat-command-game-systems/common';
import { ForceDiagram, Unit } from '@ianpaschal/combat-command-game-systems/flamesOfWarV4';
import { Infer, v } from 'convex/values';
import { customAlphabet } from 'nanoid';

import { ActionCtx, MutationCtx } from '../../../_generated/server';
import { getStaticEnumConvexValidator } from '../../common/_helpers/getStaticEnumConvexValidator';

const forceDiagram = getStaticEnumConvexValidator(ForceDiagram);
const formation = getStaticEnumConvexValidator(Unit);

const nanoid = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz', 6);

Comment on lines +1 to +14
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Stub implementation with unused imports.

This file appears to be a placeholder with several unused imports (ForceDiagram, Unit, nanoid, validators). The eslint-disable directive suppresses warnings, but these unused dependencies add unnecessary overhead.

If this is intentional scaffolding for future work, consider opening a follow-up issue to track the complete implementation. Would you like me to help generate a proper implementation based on the commented code below?

🤖 Prompt for AI Agents
In convex/_model/lists/actions/extractListData.ts lines 1-14: the file is a stub
with multiple unused imports (ForceDiagram, Unit, nanoid, validators) and an
unnecessary eslint-disable; remove the unused imports and the eslint-disable
directive, and either implement the intended extractListData logic or add a
clear TODO and open a follow-up issue tracking the required implementation so
the file no longer contains dead code. Ensure exports/typing remain correct
after cleanup and run the linter to confirm no unused symbols remain.

export const extractListDataArgs = v.object({
storageId: v.id('_storage'),
gameSystem: v.string(),
});

export const extractListData = async (
ctx: ActionCtx,
args: Infer<typeof extractListDataArgs>,
): Promise<{ fileType: string; data: Record<string, string> }> =>

// const fileMetadata = ctx.runQuery();
({
fileType: 'foo',
data: {
foo: 'foo',
},
})
;
Comment on lines +20 to +32
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Stub implementation returns placeholder data.

The extractListData action returns hardcoded placeholder values (fileType: 'foo', data: { foo: 'foo' }). This will break any functionality that depends on real list data extraction.

The commented line 25 suggests a query should be executed. Please implement the actual data extraction logic before merging.

🔍 Verify usage of this action
#!/bin/bash
# Description: Find all usages of extractListData to assess impact

# Search for extractListData imports and calls
rg -n --type=ts --type=tsx 'extractListData' -A 3 -B 1
🤖 Prompt for AI Agents
In convex/_model/lists/actions/extractListData.ts around lines 20 to 32, the
function currently returns hardcoded placeholder values; replace the stub with
real extraction logic: call the appropriate ctx.runQuery or ctx.db query
(uncomment/use the suggested query) using the provided args to fetch the
file/list metadata and contents, parse and map the result to return an object
with the actual fileType string and a data record (Record<string,string>)
derived from the fetched fields, validate and sanitize values, and propagate or
log errors instead of returning placeholders so callers receive real extracted
data.


// gameSystem: GameSystem.FlamesOfWarV4,
// tournamentRegistrationId:
// // ownerUserId: row.playerUserId,
// // data: {
// // meta: {
// // forceDiagram: row.forceDiagram,
// // pointsLimit: args.pointsLimit,
// // },
// // formations: row.formations.map((sourceId) => ({
// // id: nanoid(),
// // sourceId,
// // })),
// // units: [],
// // commandCards: [],
// // },
// // locked: args.locked,
Comment on lines +34 to +49
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Remove or implement commented code.

This large block of commented code suggests the intended implementation but reduces code maintainability.

Consider either:

  • Implementing the actual logic if this is the correct approach
  • Moving this to a TODO comment or separate documentation
  • Removing it if it's no longer relevant
🔎 Suggested cleanup
-//   gameSystem: GameSystem.FlamesOfWarV4,
-//   tournamentRegistrationId: 
-//   // ownerUserId: row.playerUserId,
-//   // data: {
-//   //   meta: {
-//   //     forceDiagram: row.forceDiagram,
-//   //     pointsLimit: args.pointsLimit,
-//   //   },
-//   //   formations: row.formations.map((sourceId) => ({
-//   //     id: nanoid(),
-//   //     sourceId,
-//   //   })),
-//   //   units: [],
-//   //   commandCards: [],
-//   // },
-//   // locked: args.locked,
+// TODO: Implement data extraction logic
+// - Query file metadata using ctx.runQuery()
+// - Parse list data based on gameSystem
+// - Return structured data with appropriate fileType
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// gameSystem: GameSystem.FlamesOfWarV4,
// tournamentRegistrationId:
// // ownerUserId: row.playerUserId,
// // data: {
// // meta: {
// // forceDiagram: row.forceDiagram,
// // pointsLimit: args.pointsLimit,
// // },
// // formations: row.formations.map((sourceId) => ({
// // id: nanoid(),
// // sourceId,
// // })),
// // units: [],
// // commandCards: [],
// // },
// // locked: args.locked,
// TODO: Implement data extraction logic
// - Query file metadata using ctx.runQuery()
// - Parse list data based on gameSystem
// - Return structured data with appropriate fileType
🤖 Prompt for AI Agents
In convex/_model/lists/actions/extractListData.ts around lines 34 to 49, there
is a large block of commented-out code that either belongs as implemented logic
or should be removed/moved to a TODO; remove or restore it to keep the codebase
clean. Decide whether this block represents required behavior (e.g., populating
gameSystem, tournamentRegistrationId, ownerUserId, data.meta, data.formations,
data.units, data.commandCards, locked) — if so, implement that logic inline
using existing helpers and types and replace the comments with working code;
otherwise delete the commented block or convert it to a single TODO comment with
a short rationale and a ticket/PR reference. Ensure any newly implemented fields
conform to the surrounding function signature and tests, and run
linting/formatting after the change.

36 changes: 36 additions & 0 deletions convex/_model/lists/actions/importList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { Infer, v } from 'convex/values';

import { ActionCtx } from '../../../_generated/server';

export const importListArgs = v.object({
url: v.string(),
gameSystem: v.string(),
});

export const importList = async (
ctx: ActionCtx,
args: Infer<typeof importListArgs>,
): Promise<void> => {

const url = new URL(args.url);

// Extract basic components
const domain = url.hostname; // "example.com"
const protocol = url.protocol; // "https:"
const pathname = url.pathname; // "/users/550e8400-e29b-41d4-a801-146655440000"
const searchParams = url.searchParams; // URLSearchParams object

// Extract path segments
const pathSegments = url.pathname.split('/').filter(Boolean);
// ["users", "550e8400-e29b-41d4-a801-146655440000"]

// Extract UUID from path (if you know its position)
const uuid = pathSegments[1];

// Or use regex to find UUID anywhere in the path
const uuidRegex = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i;
const match = url.pathname.match(uuidRegex);
const extractedUuid = match ? match[0] : null;

};
Comment on lines +1 to +36
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Complete the stub implementation before merging.

This action is incomplete and non-functional:

  • All extracted URL components are unused
  • No actual import logic is implemented
  • Function returns void without side effects
  • The eslint-disable @typescript-eslint/no-unused-vars confirms the incomplete state

This stub should not be merged to main until the import logic is implemented.

Do you want me to help implement the list import logic, or should this file be removed from the PR until the implementation is complete?

🤖 Prompt for AI Agents
In convex/_model/lists/actions/importList.ts lines 1–36, the function is a stub:
it parses the URL but never uses any values, has eslint-disable for unused vars,
and performs no import side effects; implement the import logic by removing the
eslint-disable and unused variables, validate args.url and args.gameSystem,
fetch the remote resource at args.url (handle network errors and non-200
responses), parse the payload according to the expected source format (JSON or
HTML), extract list metadata and entries (use the UUID or extractedUuid as the
canonical external id if present), map/normalize entries to our schema and
upsert the list and its items into Convex using ctx.db (or provided server APIs)
within a transaction, add logging and error handling, and return/resolve only
after successful DB writes; include unit/integration tests or error responses
for invalid inputs and ensure no unused variables remain.

21 changes: 18 additions & 3 deletions convex/_model/lists/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,24 @@ import { Id } from '../../_generated/dataModel';
export type ListId = Id<'lists'>;

export {
importListData,
importListDataArgs,
} from './mutations/importListData';
extractListData,
extractListDataArgs,
} from './actions/extractListData';
export {
importList,
importListArgs,
} from './actions/importList';
// export {
// uploadList,
// uploadListArgs,
// } from './actions/uploadList';
export {
type DeepList,
} from './_helpers/deepenList';
export {
createList,
createListArgs,
} from './mutations/createList';
export {
getList,
getListArgs,
Expand Down
42 changes: 42 additions & 0 deletions convex/_model/lists/mutations/createList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import {
ConvexError,
Infer,
v,
} from 'convex/values';

import { Id } from '../../../_generated/dataModel';
import { MutationCtx } from '../../../_generated/server';
import { getErrorMessage } from '../../common/errors';
import { editableFields } from '../table';

// const forceDiagram = getStaticEnumConvexValidator(ForceDiagram);
// const formation = getStaticEnumConvexValidator(Unit);

// const nanoid = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz', 6);
Comment on lines +12 to +15
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Remove commented-out code.

These commented-out imports and code should be removed. If they're needed for future features, they can be restored from version control when required.

🔎 Proposed cleanup
-// const forceDiagram = getStaticEnumConvexValidator(ForceDiagram);
-// const formation = getStaticEnumConvexValidator(Unit);
-
-// const nanoid = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz', 6);
-
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// const forceDiagram = getStaticEnumConvexValidator(ForceDiagram);
// const formation = getStaticEnumConvexValidator(Unit);
// const nanoid = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz', 6);
🤖 Prompt for AI Agents
In convex/_model/lists/mutations/createList.ts around lines 12 to 15, remove the
three commented-out lines (the two getStaticEnumConvexValidator calls and the
nanoid customAlphabet declaration); delete these commented blocks to clean up
unused code—if needed later they can be restored from VCS.


export const createListArgs = v.object(editableFields);

// TODO: Auto lock if after deadline
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Clarify or remove the TODO comment.

The auto-locking logic appears to already be implemented on line 40, where locked is set based on comparing Date.now() with listSubmissionClosesAt. Either remove this TODO or clarify if additional auto-locking behavior is needed.

🤖 Prompt for AI Agents
In convex/_model/lists/mutations/createList.ts around line 19, the TODO comment
“Auto lock if after deadline” is misleading because auto-lock logic is already
implemented at line 40 by setting `locked` based on comparing `Date.now()` with
`listSubmissionClosesAt`; either remove this TODO comment or update it to
specify what additional behavior (if any) is still required (for example:
scheduled background job, webhook, or notification when auto-locked), and if no
extra behavior is needed, delete the TODO to avoid confusion.


export const createList = async (
ctx: MutationCtx,
args: Infer<typeof createListArgs>,
): Promise<Id<'lists'>> => {
if (!args.tournamentRegistrationId) {
throw new Error('Tournament registration required!'); // TODO: Remove once required
}
Comment on lines +25 to +27
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Use ConvexError for consistency.

For consistency with the error handling pattern used elsewhere in this function (lines 30, 34), consider using ConvexError with getErrorMessage instead of a generic Error.

🔎 Proposed fix
  if (!args.tournamentRegistrationId) {
-    throw new Error('Tournament registration required!'); // TODO: Remove once required
+    throw new ConvexError(getErrorMessage('TOURNAMENT_REGISTRATION_REQUIRED'));
  }

Note: This assumes the error code 'TOURNAMENT_REGISTRATION_REQUIRED' exists in the error messages configuration. If it doesn't, it should be added.

🤖 Prompt for AI Agents
In convex/_model/lists/mutations/createList.ts around lines 25 to 27, replace
the generic Error thrown when args.tournamentRegistrationId is missing with a
ConvexError using getErrorMessage for consistency with the other errors in this
file; import ConvexError and getErrorMessage if not already imported, throw new
ConvexError(getErrorMessage('TOURNAMENT_REGISTRATION_REQUIRED')) (or the
appropriate error-code string), and if that error code is missing add it to the
error messages configuration.

const tournamentRegistration = await ctx.db.get(args.tournamentRegistrationId);
if (!tournamentRegistration) {
throw new ConvexError(getErrorMessage('TOURNAMENT_REGISTRATION_NOT_FOUND'));
}
const tournament = await ctx.db.get(tournamentRegistration.tournamentId);
if (!tournament) {
throw new ConvexError(getErrorMessage('TOURNAMENT_NOT_FOUND'));
}
const { listSubmissionClosesAt } = tournament;
return await ctx.db.insert('lists', {
...args,
approvalStatus: null,
locked: Date.now() > listSubmissionClosesAt,
});
Comment on lines +37 to +41
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Clarify timing of the locked field calculation across retries.

The locked field uses Date.now(), which is frozen at the start of each function execution. If the mutation retries due to conflicts, each retry attempt will evaluate Date.now() with the current wall-clock time. This means locked could have different values across retries if the deadline passes between attempts—a valid but unlikely edge case given the small time window. The current implementation is acceptable unless deterministic locking behavior across retries is a requirement.

🤖 Prompt for AI Agents
convex/_model/lists/mutations/createList.ts around lines 37 to 41: the code
calls Date.now() inline when setting the locked field, which can yield different
values across retry attempts; compute a single timestamp at the start of the
mutation (e.g. const now = Date.now()) before any operations that may be retried
and use locked: now > listSubmissionClosesAt so the locked value is
deterministic across retries.

};
51 changes: 0 additions & 51 deletions convex/_model/lists/mutations/importListData.ts

This file was deleted.

Loading