From 9f38719fc33c64199005b92a3bf378e39da1ca3d Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Fri, 24 Apr 2026 21:44:33 +0300 Subject: [PATCH] Handle race when two groups match up with same target GroupMatch has unique constraints on both alphaGroupId and bravoGroupId (a group can only be in one match). If two managers click MATCH_UP at nearly the same moment against overlapping groups, the second INSERT trips SQLITE_CONSTRAINT_UNIQUE and bubbles up as a 500. Translate that error into a SendouQError inside SQMatchRepository.create so the q/looking action's existing SendouQError catch treats it like any other stale-state error and returns null, which causes the loader to re-run and the user sees the fresh state instead of an error page. --- .../sendouq-match/SQMatchRepository.server.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/app/features/sendouq-match/SQMatchRepository.server.ts b/app/features/sendouq-match/SQMatchRepository.server.ts index bc386617e..8a5aa5d22 100644 --- a/app/features/sendouq-match/SQMatchRepository.server.ts +++ b/app/features/sendouq-match/SQMatchRepository.server.ts @@ -16,8 +16,10 @@ import { concatUserSubmittedImagePrefix, tournamentLogoWithDefault, } from "~/utils/kysely.server"; +import { errorIsSqliteUniqueConstraintFailure } from "~/utils/sql"; import type { Unpacked } from "~/utils/types"; import { FULL_GROUP_SIZE } from "../sendouq/q-constants"; +import { SendouQError } from "../sendouq/q-utils.server"; import * as SQGroupRepository from "../sendouq/SQGroupRepository.server"; import { MATCHES_PER_SEASONS_PAGE } from "../user-page/user-page-constants"; import { compareMatchToReportedScores } from "./core/match.server"; @@ -410,7 +412,15 @@ export function create({ memento: JSON.stringify(memento), }) .returningAll() - .executeTakeFirstOrThrow(); + .executeTakeFirstOrThrow() + .catch((error) => { + // race: another manager matched one of the two groups first, tripping the + // unique constraint on GroupMatch.alphaGroupId / bravoGroupId + if (errorIsSqliteUniqueConstraintFailure(error)) { + throw new SendouQError("Group is already in a match"); + } + throw error; + }); await trx .insertInto("GroupMatchMap")