From b72c27a6c1a26c9a2da7bce32e20b8ecec274b2d Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Sun, 3 Aug 2025 11:37:56 +0200 Subject: [PATCH] task: Add mergeUser() to back-end --- convex/_generated/api.d.ts | 2 + .../queries/getTournamentsByUser.ts | 2 +- convex/_model/utils/index.ts | 4 + convex/_model/utils/mergeUser.ts | 103 ++++++++++++++++++ convex/utils.ts | 26 ++--- 5 files changed, 122 insertions(+), 15 deletions(-) create mode 100644 convex/_model/utils/mergeUser.ts diff --git a/convex/_generated/api.d.ts b/convex/_generated/api.d.ts index 3ef653ab..2cb13c80 100644 --- a/convex/_generated/api.d.ts +++ b/convex/_generated/api.d.ts @@ -152,6 +152,7 @@ import type * as _model_utils_createTestTournament from "../_model/utils/createT import type * as _model_utils_createTestTournamentMatchResults from "../_model/utils/createTestTournamentMatchResults.js"; import type * as _model_utils_deleteTestTournament from "../_model/utils/deleteTestTournament.js"; import type * as _model_utils_index from "../_model/utils/index.js"; +import type * as _model_utils_mergeUser from "../_model/utils/mergeUser.js"; import type * as auth_ResendOtpPasswordReset from "../auth/ResendOtpPasswordReset.js"; import type * as auth_ResendOtpVerification from "../auth/ResendOtpVerification.js"; import type * as auth from "../auth.js"; @@ -343,6 +344,7 @@ declare const fullApi: ApiFromModules<{ "_model/utils/createTestTournamentMatchResults": typeof _model_utils_createTestTournamentMatchResults; "_model/utils/deleteTestTournament": typeof _model_utils_deleteTestTournament; "_model/utils/index": typeof _model_utils_index; + "_model/utils/mergeUser": typeof _model_utils_mergeUser; "auth/ResendOtpPasswordReset": typeof auth_ResendOtpPasswordReset; "auth/ResendOtpVerification": typeof auth_ResendOtpVerification; auth: typeof auth; diff --git a/convex/_model/tournaments/queries/getTournamentsByUser.ts b/convex/_model/tournaments/queries/getTournamentsByUser.ts index b736d851..a965b880 100644 --- a/convex/_model/tournaments/queries/getTournamentsByUser.ts +++ b/convex/_model/tournaments/queries/getTournamentsByUser.ts @@ -39,7 +39,7 @@ export const getTournamentsByUser = async ( } const userIds = [ ...tournament.organizerUserIds, - ...tournament.activePlayerUserIds, + ...tournament.playerUserIds, ]; if (!userIds.includes(args.userId)) { return false; diff --git a/convex/_model/utils/index.ts b/convex/_model/utils/index.ts index fcc0a86d..73e7b17a 100644 --- a/convex/_model/utils/index.ts +++ b/convex/_model/utils/index.ts @@ -10,3 +10,7 @@ export { deleteTestTournament, deleteTestTournamentArgs, } from './deleteTestTournament'; +export { + mergeUser, + mergeUserArgs, +} from './mergeUser'; diff --git a/convex/_model/utils/mergeUser.ts b/convex/_model/utils/mergeUser.ts new file mode 100644 index 00000000..99997c99 --- /dev/null +++ b/convex/_model/utils/mergeUser.ts @@ -0,0 +1,103 @@ +import { getAuthUserId } from '@convex-dev/auth/server'; +import { Infer, v } from 'convex/values'; + +import { MutationCtx } from '../../_generated/server'; + +export const mergeUserArgs = v.object({ + primaryId: v.id('users'), + secondaryId: v.id('users'), +}); + +/** + * Merges a secondary user into a primary user. + * + * @param ctx - Convex query context + * @param args - User data + */ +export const mergeUser = async ( + ctx: MutationCtx, + args: Infer, +): Promise => { + + // --- CHECK AUTH ---- + // TODO: This should be admin roles or something + const userId = await getAuthUserId(ctx); + if (!userId) { + throw new Error('Auth required'); + } + + // ---- PRIMARY ACTIONS ---- + // Match Result Comments + const matchResultComments = await ctx.db.query('matchResultComments') + .withIndex('by_user_id', (q) => q.eq('userId', args.secondaryId)) + .collect(); + for (const record of matchResultComments) { + await ctx.db.patch(record._id, { userId: args.primaryId }); + } + + // Match Result Likes + const matchResultLikes = await ctx.db.query('matchResultLikes') + .withIndex('by_user_id', (q) => q.eq('userId', args.secondaryId)) + .collect(); + for (const record of matchResultLikes) { + await ctx.db.patch(record._id, { userId: args.primaryId }); + } + + // Match Results + const matchResults = await ctx.db.query('matchResults') + .filter((q) => q.or( + q.eq(q.field('player0UserId'), args.secondaryId), + q.eq(q.field('player1UserId'), args.secondaryId), + )) + .collect(); + for (const record of matchResults) { + if (record.player0UserId === args.secondaryId) { + await ctx.db.patch(record._id, { player0UserId: args.primaryId }); + } + if (record.player1UserId === args.secondaryId) { + await ctx.db.patch(record._id, { player1UserId: args.primaryId }); + } + } + + // Tournament Competitors + const tournamentCompetitors = await ctx.db.query('tournamentCompetitors').collect(); + for (const record of tournamentCompetitors) { + if (record.players.find((p) => p.userId === args.secondaryId)) { + await ctx.db.patch(record._id, { + players: record.players.map((p) => p.userId === args.secondaryId ? { + ...p, + userId: args.primaryId, + } : p), + }); + } + } + + // Tournament Organizers + const tournaments = await ctx.db.query('tournaments').collect(); + for (const record of tournaments) { + if (record.organizerUserIds.includes(args.secondaryId)) { + await ctx.db.patch(record._id, { + organizerUserIds: record.organizerUserIds.map((id) => id === args.secondaryId ? args.primaryId : id), + }); + } + } + + // Friendships + const friendships = await ctx.db.query('friendships') + .filter((q) => q.or( + q.eq(q.field('senderUserId'), args.secondaryId), + q.eq(q.field('recipientUserId'), args.secondaryId), + )) + .collect(); + for (const record of friendships) { + if (record.senderUserId === args.secondaryId) { + await ctx.db.patch(record._id, { senderUserId: args.primaryId }); + } + if (record.recipientUserId === args.secondaryId) { + await ctx.db.patch(record._id, { recipientUserId: args.primaryId }); + } + } + + // Clean up the loose ID + await ctx.db.delete(args.secondaryId); +}; diff --git a/convex/utils.ts b/convex/utils.ts index 72f5808d..ede11f42 100644 --- a/convex/utils.ts +++ b/convex/utils.ts @@ -1,24 +1,22 @@ import { mutation } from './_generated/server'; -import { - createTestTournament as createTestTournamentHandler, - createTestTournamentArgs, - createTestTournamentMatchResults as createTestTournamentMatchResultsHandler, - createTestTournamentMatchResultsArgs, - deleteTestTournament as deleteTestTournamentHandler, - deleteTestTournamentArgs, -} from './_model/utils'; +import * as model from './_model/utils'; export const createTestTournament = mutation({ - args: createTestTournamentArgs, - handler: createTestTournamentHandler, + args: model.createTestTournamentArgs, + handler: model.createTestTournament, }); export const createTestTournamentMatchResults = mutation({ - args: createTestTournamentMatchResultsArgs, - handler: createTestTournamentMatchResultsHandler, + args: model.createTestTournamentMatchResultsArgs, + handler: model.createTestTournamentMatchResults, }); export const deleteTestTournament = mutation({ - args: deleteTestTournamentArgs, - handler: deleteTestTournamentHandler, + args: model.deleteTestTournamentArgs, + handler: model.deleteTestTournament, +}); + +export const mergeUser = mutation({ + args: model.mergeUserArgs, + handler: model.mergeUser, });