fix(tournament): mirror bot join onto native twin
Build & Test (NowChessSystems) TeamCity build finished
Build & Test (NowChessSystems) TeamCity build finished
The UI reads participant/standings fields from the native-server twin (nativeOverlay), but bot join only wrote the NowChess participant list, so bots never appeared in replicated/native-published tournaments. On join, register the bot on the native server by name and join the twin as that bot. Also run this for the AlreadyJoined case so bots stuck in the NowChess list (but missing on native) get reconciled, and return 200 instead of 409 for it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+18
-1
@@ -119,6 +119,16 @@ class TournamentResource:
|
||||
case Some(nativeId) => tournamentService.setNativeTournamentId(t.id, nativeId)
|
||||
case None => log.warnf("Failed to publish tournament %s to native server %s", t.id, nativeServerUrl)
|
||||
|
||||
// Mirror a bot join onto the native twin so it surfaces in the UI, which reads participant and
|
||||
// standings fields from the native server (see nativeOverlay).
|
||||
private def joinNativeTwin(id: String, botName: String): Unit =
|
||||
if nativeServerUrl.nonEmpty then
|
||||
tournamentService.get(id).flatMap(nativeIdFor).foreach { nativeId =>
|
||||
if externalClient.joinNativeAsBot(nativeServerUrl, nativeId, botName) then
|
||||
log.infof("Joined bot %s on native twin %s of tournament %s", botName, nativeId, id)
|
||||
else log.warnf("Failed to join bot %s on native twin %s of tournament %s", botName, nativeId, id)
|
||||
}
|
||||
|
||||
// Resolve the native-server twin of a local tournament. Backfills the stored id by matching
|
||||
// fullName against the native list for tournaments created before the id was captured.
|
||||
private def nativeIdFor(t: de.nowchess.tournament.domain.Tournament): Option[String] =
|
||||
@@ -250,7 +260,14 @@ class TournamentResource:
|
||||
val botId = Option(jwt.getSubject).getOrElse("")
|
||||
val botName = Option(jwt.getClaim[AnyRef]("name")).map(_.toString).getOrElse(botId)
|
||||
tournamentService.join(id, botId, botName) match
|
||||
case Right(_) => Response.ok(OkDto()).build()
|
||||
case Right(_) =>
|
||||
joinNativeTwin(id, botName)
|
||||
Response.ok(OkDto()).build()
|
||||
// Already in the NowChess participant list but possibly never mirrored onto the native
|
||||
// twin (where the UI reads participants from) — make the native join idempotent.
|
||||
case Left(TournamentError.AlreadyJoined) =>
|
||||
joinNativeTwin(id, botName)
|
||||
Response.ok(OkDto()).build()
|
||||
case Left(error) =>
|
||||
error match
|
||||
case TournamentError.NotFound(_) =>
|
||||
|
||||
+22
-1
@@ -60,9 +60,12 @@ class ExternalTournamentClient:
|
||||
}
|
||||
|
||||
private def registerDirector(serverUrl: String, name: String): Option[String] =
|
||||
registerAccount(serverUrl, name, isBot = false)
|
||||
|
||||
private def registerAccount(serverUrl: String, name: String, isBot: Boolean): Option[String] =
|
||||
Try {
|
||||
val client = buildClient()
|
||||
val body = s"""{"name":"${name.replace("\"", "\\\"")}","isBot":false}"""
|
||||
val body = s"""{"name":"${name.replace("\"", "\\\"")}","isBot":$isBot}"""
|
||||
val response = client
|
||||
.target(s"$serverUrl/api/auth/register")
|
||||
.request(MediaType.APPLICATION_JSON)
|
||||
@@ -76,6 +79,24 @@ class ExternalTournamentClient:
|
||||
client.close()
|
||||
}.getOrElse(None)
|
||||
|
||||
// The tournament server holds only the director token, which cannot join as a bot. Register the
|
||||
// bot on the native server by name to mint a bot token, then join the native twin as that bot.
|
||||
def joinNativeAsBot(serverUrl: String, tournamentId: String, botName: String): Boolean =
|
||||
registerAccount(serverUrl, botName, isBot = true).exists { token =>
|
||||
Try {
|
||||
val client = buildClient()
|
||||
val response = client
|
||||
.target(s"$serverUrl/api/tournament/$tournamentId/join")
|
||||
.request(MediaType.APPLICATION_JSON)
|
||||
.header("Authorization", s"Bearer $token")
|
||||
.post(Entity.json(""))
|
||||
try response.getStatus / 100 == 2 || response.getStatus == 409
|
||||
finally
|
||||
response.close()
|
||||
client.close()
|
||||
}.getOrElse(false)
|
||||
}
|
||||
|
||||
private def createNative(serverUrl: String, token: String, form: String): Option[String] =
|
||||
Try {
|
||||
val client = buildClient()
|
||||
|
||||
Reference in New Issue
Block a user