Skip to content

Commit d979118

Browse files
committed
WIP
1 parent 86867df commit d979118

87 files changed

Lines changed: 2319 additions & 272 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/settings.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"permissions": {
3+
"allow": [
4+
"Read(//Users/ian/Code/combat-command-components/src/components/Wizard/**)",
5+
"Read(//Users/ian/Code/combat-command-components/**)",
6+
"Bash(npm run lint:*)"
7+
],
8+
"additionalDirectories": [
9+
"/Users/ian/Code/combat-command-components/src/components/Wizard"
10+
]
11+
}
12+
}

.eslintrc.cjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ module.exports = {
4949
"arrow-body-style": ["error", "as-needed"],
5050
"no-console": ["warn", { allow: ["warn", "error", "info"] }],
5151
"@typescript-eslint//explicit-function-return-type": "off",
52+
"implicit-arrow-linebreak": ["error", "beside"],
5253

5354
// Plugin configurations
5455
'import-newlines/enforce': ['error', 2],

convex/_generated/api.d.ts

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import type * as _model_common__helpers_gameSystem_createSortByRanking from "../
2121
import type * as _model_common__helpers_gameSystem_divideBaseStats from "../_model/common/_helpers/gameSystem/divideBaseStats.js";
2222
import type * as _model_common__helpers_gameSystem_sumBaseStats from "../_model/common/_helpers/gameSystem/sumBaseStats.js";
2323
import type * as _model_common__helpers_getCountryName from "../_model/common/_helpers/getCountryName.js";
24+
import type * as _model_common__helpers_getDocStrict from "../_model/common/_helpers/getDocStrict.js";
2425
import type * as _model_common__helpers_getEnvironment from "../_model/common/_helpers/getEnvironment.js";
2526
import type * as _model_common__helpers_getRange from "../_model/common/_helpers/getRange.js";
2627
import type * as _model_common__helpers_getStaticEnumConvexValidator from "../_model/common/_helpers/getStaticEnumConvexValidator.js";
@@ -41,7 +42,9 @@ import type * as _model_common_themes from "../_model/common/themes.js";
4142
import type * as _model_common_tournamentPairingConfig from "../_model/common/tournamentPairingConfig.js";
4243
import type * as _model_common_tournamentStatus from "../_model/common/tournamentStatus.js";
4344
import type * as _model_common_types from "../_model/common/types.js";
45+
import type * as _model_files_actions_convertImageToPdf from "../_model/files/actions/convertImageToPdf.js";
4446
import type * as _model_files_index from "../_model/files/index.js";
47+
import type * as _model_files_queries_getFileMetadata from "../_model/files/queries/getFileMetadata.js";
4548
import type * as _model_files_queries_getFileUrl from "../_model/files/queries/getFileUrl.js";
4649
import type * as _model_friendships__helpers_deepenFriendship from "../_model/friendships/_helpers/deepenFriendship.js";
4750
import type * as _model_friendships_index from "../_model/friendships/index.js";
@@ -51,10 +54,6 @@ import type * as _model_friendships_mutations_deleteFriendship from "../_model/f
5154
import type * as _model_friendships_queries_getFriendship from "../_model/friendships/queries/getFriendship.js";
5255
import type * as _model_friendships_queries_getFriendshipsByUser from "../_model/friendships/queries/getFriendshipsByUser.js";
5356
import type * as _model_friendships_table from "../_model/friendships/table.js";
54-
import type * as _model_gameSystems_battlefront_flamesOfWarV4__helpers_deepenListData from "../_model/gameSystems/battlefront/flamesOfWarV4/_helpers/deepenListData.js";
55-
import type * as _model_gameSystems_battlefront_flamesOfWarV4__model_listData from "../_model/gameSystems/battlefront/flamesOfWarV4/_model/listData.js";
56-
import type * as _model_gameSystems_battlefront_flamesOfWarV4_index from "../_model/gameSystems/battlefront/flamesOfWarV4/index.js";
57-
import type * as _model_gameSystems_index from "../_model/gameSystems/index.js";
5857
import type * as _model_leagueOrganizers__helpers_checkUserIsLeagueOrganizer from "../_model/leagueOrganizers/_helpers/checkUserIsLeagueOrganizer.js";
5958
import type * as _model_leagueOrganizers__helpers_deepenLeagueOrganizer from "../_model/leagueOrganizers/_helpers/deepenLeagueOrganizer.js";
6059
import type * as _model_leagueOrganizers_index from "../_model/leagueOrganizers/index.js";
@@ -80,10 +79,18 @@ import type * as _model_leagues_queries_getLeague from "../_model/leagues/querie
8079
import type * as _model_leagues_queries_getLeagues from "../_model/leagues/queries/getLeagues.js";
8180
import type * as _model_leagues_table from "../_model/leagues/table.js";
8281
import type * as _model_leagues_types from "../_model/leagues/types.js";
82+
import type * as _model_lists__helpers_checkListSubmittedOnTime from "../_model/lists/_helpers/checkListSubmittedOnTime.js";
83+
import type * as _model_lists__helpers_checkListVisibility from "../_model/lists/_helpers/checkListVisibility.js";
8384
import type * as _model_lists__helpers_deepenList from "../_model/lists/_helpers/deepenList.js";
85+
import type * as _model_lists__helpers_getAvailableActions from "../_model/lists/_helpers/getAvailableActions.js";
8486
import type * as _model_lists_index from "../_model/lists/index.js";
85-
import type * as _model_lists_mutations_importListData from "../_model/lists/mutations/importListData.js";
87+
import type * as _model_lists_mutations_createList from "../_model/lists/mutations/createList.js";
88+
import type * as _model_lists_mutations_deleteList from "../_model/lists/mutations/deleteList.js";
89+
import type * as _model_lists_mutations_toggleListApproval from "../_model/lists/mutations/toggleListApproval.js";
90+
import type * as _model_lists_mutations_updateList from "../_model/lists/mutations/updateList.js";
8691
import type * as _model_lists_queries_getList from "../_model/lists/queries/getList.js";
92+
import type * as _model_lists_queries_getListsByTournamentRegistration from "../_model/lists/queries/getListsByTournamentRegistration.js";
93+
import type * as _model_lists_queries_getListsByUser from "../_model/lists/queries/getListsByUser.js";
8794
import type * as _model_lists_table from "../_model/lists/table.js";
8895
import type * as _model_matchResultComments__helpers_deepenMatchResultComment from "../_model/matchResultComments/_helpers/deepenMatchResultComment.js";
8996
import type * as _model_matchResultComments_index from "../_model/matchResultComments/index.js";
@@ -122,6 +129,8 @@ import type * as _model_photos_index from "../_model/photos/index.js";
122129
import type * as _model_photos_mutations_createPhoto from "../_model/photos/mutations/createPhoto.js";
123130
import type * as _model_photos_queries_getPhoto from "../_model/photos/queries/getPhoto.js";
124131
import type * as _model_photos_table from "../_model/photos/table.js";
132+
import type * as _model_tournamentCompetitors__helpers_checkUserIsTeamCaptain from "../_model/tournamentCompetitors/_helpers/checkUserIsTeamCaptain.js";
133+
import type * as _model_tournamentCompetitors__helpers_checkUsersAreTeammates from "../_model/tournamentCompetitors/_helpers/checkUsersAreTeammates.js";
125134
import type * as _model_tournamentCompetitors__helpers_deepenTournamentCompetitor from "../_model/tournamentCompetitors/_helpers/deepenTournamentCompetitor.js";
126135
import type * as _model_tournamentCompetitors__helpers_getAvailableActions from "../_model/tournamentCompetitors/_helpers/getAvailableActions.js";
127136
import type * as _model_tournamentCompetitors__helpers_getDetails from "../_model/tournamentCompetitors/_helpers/getDetails.js";
@@ -335,6 +344,7 @@ declare const fullApi: ApiFromModules<{
335344
"_model/common/_helpers/gameSystem/divideBaseStats": typeof _model_common__helpers_gameSystem_divideBaseStats;
336345
"_model/common/_helpers/gameSystem/sumBaseStats": typeof _model_common__helpers_gameSystem_sumBaseStats;
337346
"_model/common/_helpers/getCountryName": typeof _model_common__helpers_getCountryName;
347+
"_model/common/_helpers/getDocStrict": typeof _model_common__helpers_getDocStrict;
338348
"_model/common/_helpers/getEnvironment": typeof _model_common__helpers_getEnvironment;
339349
"_model/common/_helpers/getRange": typeof _model_common__helpers_getRange;
340350
"_model/common/_helpers/getStaticEnumConvexValidator": typeof _model_common__helpers_getStaticEnumConvexValidator;
@@ -355,7 +365,9 @@ declare const fullApi: ApiFromModules<{
355365
"_model/common/tournamentPairingConfig": typeof _model_common_tournamentPairingConfig;
356366
"_model/common/tournamentStatus": typeof _model_common_tournamentStatus;
357367
"_model/common/types": typeof _model_common_types;
368+
"_model/files/actions/convertImageToPdf": typeof _model_files_actions_convertImageToPdf;
358369
"_model/files/index": typeof _model_files_index;
370+
"_model/files/queries/getFileMetadata": typeof _model_files_queries_getFileMetadata;
359371
"_model/files/queries/getFileUrl": typeof _model_files_queries_getFileUrl;
360372
"_model/friendships/_helpers/deepenFriendship": typeof _model_friendships__helpers_deepenFriendship;
361373
"_model/friendships/index": typeof _model_friendships_index;
@@ -365,10 +377,6 @@ declare const fullApi: ApiFromModules<{
365377
"_model/friendships/queries/getFriendship": typeof _model_friendships_queries_getFriendship;
366378
"_model/friendships/queries/getFriendshipsByUser": typeof _model_friendships_queries_getFriendshipsByUser;
367379
"_model/friendships/table": typeof _model_friendships_table;
368-
"_model/gameSystems/battlefront/flamesOfWarV4/_helpers/deepenListData": typeof _model_gameSystems_battlefront_flamesOfWarV4__helpers_deepenListData;
369-
"_model/gameSystems/battlefront/flamesOfWarV4/_model/listData": typeof _model_gameSystems_battlefront_flamesOfWarV4__model_listData;
370-
"_model/gameSystems/battlefront/flamesOfWarV4/index": typeof _model_gameSystems_battlefront_flamesOfWarV4_index;
371-
"_model/gameSystems/index": typeof _model_gameSystems_index;
372380
"_model/leagueOrganizers/_helpers/checkUserIsLeagueOrganizer": typeof _model_leagueOrganizers__helpers_checkUserIsLeagueOrganizer;
373381
"_model/leagueOrganizers/_helpers/deepenLeagueOrganizer": typeof _model_leagueOrganizers__helpers_deepenLeagueOrganizer;
374382
"_model/leagueOrganizers/index": typeof _model_leagueOrganizers_index;
@@ -394,10 +402,18 @@ declare const fullApi: ApiFromModules<{
394402
"_model/leagues/queries/getLeagues": typeof _model_leagues_queries_getLeagues;
395403
"_model/leagues/table": typeof _model_leagues_table;
396404
"_model/leagues/types": typeof _model_leagues_types;
405+
"_model/lists/_helpers/checkListSubmittedOnTime": typeof _model_lists__helpers_checkListSubmittedOnTime;
406+
"_model/lists/_helpers/checkListVisibility": typeof _model_lists__helpers_checkListVisibility;
397407
"_model/lists/_helpers/deepenList": typeof _model_lists__helpers_deepenList;
408+
"_model/lists/_helpers/getAvailableActions": typeof _model_lists__helpers_getAvailableActions;
398409
"_model/lists/index": typeof _model_lists_index;
399-
"_model/lists/mutations/importListData": typeof _model_lists_mutations_importListData;
410+
"_model/lists/mutations/createList": typeof _model_lists_mutations_createList;
411+
"_model/lists/mutations/deleteList": typeof _model_lists_mutations_deleteList;
412+
"_model/lists/mutations/toggleListApproval": typeof _model_lists_mutations_toggleListApproval;
413+
"_model/lists/mutations/updateList": typeof _model_lists_mutations_updateList;
400414
"_model/lists/queries/getList": typeof _model_lists_queries_getList;
415+
"_model/lists/queries/getListsByTournamentRegistration": typeof _model_lists_queries_getListsByTournamentRegistration;
416+
"_model/lists/queries/getListsByUser": typeof _model_lists_queries_getListsByUser;
401417
"_model/lists/table": typeof _model_lists_table;
402418
"_model/matchResultComments/_helpers/deepenMatchResultComment": typeof _model_matchResultComments__helpers_deepenMatchResultComment;
403419
"_model/matchResultComments/index": typeof _model_matchResultComments_index;
@@ -436,6 +452,8 @@ declare const fullApi: ApiFromModules<{
436452
"_model/photos/mutations/createPhoto": typeof _model_photos_mutations_createPhoto;
437453
"_model/photos/queries/getPhoto": typeof _model_photos_queries_getPhoto;
438454
"_model/photos/table": typeof _model_photos_table;
455+
"_model/tournamentCompetitors/_helpers/checkUserIsTeamCaptain": typeof _model_tournamentCompetitors__helpers_checkUserIsTeamCaptain;
456+
"_model/tournamentCompetitors/_helpers/checkUsersAreTeammates": typeof _model_tournamentCompetitors__helpers_checkUsersAreTeammates;
439457
"_model/tournamentCompetitors/_helpers/deepenTournamentCompetitor": typeof _model_tournamentCompetitors__helpers_deepenTournamentCompetitor;
440458
"_model/tournamentCompetitors/_helpers/getAvailableActions": typeof _model_tournamentCompetitors__helpers_getAvailableActions;
441459
"_model/tournamentCompetitors/_helpers/getDetails": typeof _model_tournamentCompetitors__helpers_getDetails;

convex/_model/common/_helpers/filterWithSearchTerm.ts

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,17 @@ export const filterWithSearchTerm = <T extends object>(
77
return items;
88
}
99
const tokens = searchTerm.trim().toLowerCase().split(' ');
10-
return items.filter((item) =>
11-
fields.some((field) => {
12-
const value = item[field];
13-
return tokens.some((token) => {
14-
if (typeof value === 'string') {
15-
return value.toLowerCase().includes(token);
16-
}
17-
if (typeof value === 'number') {
18-
return value.toString().includes(token);
19-
}
20-
return false;
21-
});
22-
}),
10+
return items.filter((item) => fields.some((field) => {
11+
const value = item[field];
12+
return tokens.some((token) => {
13+
if (typeof value === 'string') {
14+
return value.toLowerCase().includes(token);
15+
}
16+
if (typeof value === 'number') {
17+
return value.toString().includes(token);
18+
}
19+
return false;
20+
});
21+
}),
2322
);
2423
};
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { GenericDatabaseReader } from 'convex/server';
2+
import { ConvexError, GenericId } from 'convex/values';
3+
4+
import {
5+
DataModel,
6+
Doc,
7+
TableNames,
8+
} from '../../../_generated/dataModel';
9+
10+
/**
11+
* Fetches a document by ID, throwing a `ConvexError` if it doesn't exist.
12+
*
13+
* @param ctx - A context object with a database reader.
14+
* @param id - The ID of the document to fetch.
15+
* @returns The document.
16+
* @throws {ConvexError} If no document exists with the given ID.
17+
*/
18+
export const getDocStrict = async <T extends TableNames>(
19+
ctx: { db: GenericDatabaseReader<DataModel> },
20+
id: GenericId<T>,
21+
): Promise<Doc<T>> => {
22+
const doc = await ctx.db.get(id);
23+
if (!doc) {
24+
throw new ConvexError({ message: `Document not found: ${id}`, code: 'DOCUMENT_NOT_FOUND' });
25+
}
26+
return doc;
27+
};
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import {
2+
ConvexError,
3+
Infer,
4+
v,
5+
} from 'convex/values';
6+
import { PDFDocument } from 'pdf-lib';
7+
8+
import { Id } from '../../../_generated/dataModel';
9+
import { ActionCtx } from '../../../_generated/server';
10+
11+
const IMAGE_MIME_TYPES = ['image/png', 'image/jpeg', 'image/jpg'] as const;
12+
type ImageMimeType = (typeof IMAGE_MIME_TYPES)[number];
13+
14+
const isImageMimeType = (mimeType: string): mimeType is ImageMimeType => (IMAGE_MIME_TYPES as ReadonlyArray<string>).includes(mimeType);
15+
16+
export const convertImageToPdfArgs = v.object({
17+
storageId: v.id('_storage'),
18+
mimeType: v.string(),
19+
});
20+
21+
export const convertImageToPdf = async (
22+
ctx: ActionCtx,
23+
args: Infer<typeof convertImageToPdfArgs>,
24+
): Promise<Id<'_storage'>> => {
25+
if (!isImageMimeType(args.mimeType)) {
26+
throw new ConvexError(`Unsupported MIME type: ${args.mimeType}. Expected one of: ${IMAGE_MIME_TYPES.join(', ')}`);
27+
}
28+
29+
const blob = await ctx.storage.get(args.storageId);
30+
if (!blob) {
31+
throw new ConvexError('File not found in storage');
32+
}
33+
34+
const imageBytes = new Uint8Array(await blob.arrayBuffer());
35+
const pdfDoc = await PDFDocument.create();
36+
37+
const image = args.mimeType === 'image/png' ? await pdfDoc.embedPng(imageBytes) : await pdfDoc.embedJpg(imageBytes);
38+
39+
const page = pdfDoc.addPage([image.width, image.height]);
40+
page.drawImage(image, {
41+
x: 0,
42+
y: 0,
43+
width: image.width,
44+
height: image.height,
45+
});
46+
47+
const pdfBytes = await pdfDoc.save();
48+
const pdfBuffer = pdfBytes.buffer as ArrayBuffer;
49+
const pdfBlob = new Blob([pdfBuffer], { type: 'application/pdf' });
50+
const pdfStorageId = await ctx.storage.store(pdfBlob);
51+
52+
await ctx.storage.delete(args.storageId);
53+
54+
return pdfStorageId;
55+
};

convex/_model/files/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
export {
2+
convertImageToPdf,
3+
convertImageToPdfArgs,
4+
} from './actions/convertImageToPdf';
5+
export {
6+
getFileMetadata,
7+
getFileMetadataArgs,
8+
} from './queries/getFileMetadata';
19
export {
210
getFileUrl,
311
getFileUrlArgs,
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { GenericDoc } from '@convex-dev/auth/server';
2+
import { SystemDataModel } from 'convex/server';
3+
import {
4+
ConvexError,
5+
Infer,
6+
v,
7+
} from 'convex/values';
8+
9+
import { QueryCtx } from '../../../_generated/server';
10+
import { getErrorMessage } from '../../common/errors';
11+
12+
export const getFileMetadataArgs = v.object({
13+
id: v.id('_storage'),
14+
});
15+
16+
export const getFileMetadata = async (
17+
ctx: QueryCtx,
18+
args: Infer<typeof getFileMetadataArgs>,
19+
): Promise<GenericDoc<SystemDataModel,'_storage'> & { url: string } | null> => {
20+
const fileUrl = await ctx.storage.getUrl(args.id);
21+
const fileMetadata = await ctx.db.system.get(args.id);
22+
if (!fileMetadata || !fileUrl) {
23+
throw new ConvexError(getErrorMessage('FILE_NOT_FOUND'));
24+
}
25+
return {
26+
...fileMetadata,
27+
url: fileUrl,
28+
};
29+
};

convex/_model/gameSystems/battlefront/flamesOfWarV4/_helpers/deepenListData.ts

Lines changed: 0 additions & 17 deletions
This file was deleted.

convex/_model/gameSystems/battlefront/flamesOfWarV4/_model/listData.ts

Lines changed: 0 additions & 32 deletions
This file was deleted.

0 commit comments

Comments
 (0)