feat(security): add internal secret handling and Redis integration for bot events

This commit is contained in:
2026-04-28 09:29:05 +02:00
parent c10a4d7e64
commit 1ab6532b0a
50 changed files with 951 additions and 214 deletions
@@ -6,7 +6,13 @@ import scala.compiletime.uninitialized
import java.time.Instant
@Entity
@Table(name = "game_records")
@Table(
name = "game_records",
indexes = Array(
new Index(name = "idx_game_records_white_id", columnList = "whiteId"),
new Index(name = "idx_game_records_black_id", columnList = "blackId"),
),
)
class GameRecord extends PanacheEntityBase:
// scalafix:off DisableSyntax.var
@Id
@@ -79,4 +85,11 @@ class GameRecord extends PanacheEntityBase:
@Column
var pendingDrawOffer: String = uninitialized
// Game result
@Column
var result: String = uninitialized
@Column
var terminationReason: String = uninitialized
// scalafix:on
@@ -21,4 +21,6 @@ case class GameWritebackEventDto(
clockMoveDeadline: Option[Long],
clockActiveColor: Option[String],
pendingDrawOffer: Option[String],
result: Option[String] = None,
terminationReason: Option[String] = None,
)
@@ -24,8 +24,6 @@ class GameWritebackStreamListener:
val topic = redisson.getTopic("game-writeback")
topic.addListener(
classOf[String],
new MessageListener[String]:
def onMessage(channel: CharSequence, json: String): Unit =
Try(objectMapper.readValue(json, classOf[GameWritebackEventDto])).toOption
.foreach(writebackService.writeBack),
(channel: CharSequence, json: String) => Try(objectMapper.readValue(json, classOf[GameWritebackEventDto])).toOption
.foreach(writebackService.writeBack)
)
@@ -5,6 +5,7 @@ import jakarta.enterprise.context.ApplicationScoped
import jakarta.inject.Inject
import jakarta.persistence.EntityManager
import scala.compiletime.uninitialized
import scala.jdk.CollectionConverters.*
@ApplicationScoped
class GameRecordRepository:
@@ -21,3 +22,25 @@ class GameRecordRepository:
def merge(record: GameRecord): Unit =
em.merge(record)
def findByPlayerId(playerId: String, offset: Int, limit: Int): List[GameRecord] =
em.createQuery(
"SELECT g FROM GameRecord g WHERE g.whiteId = :id OR g.blackId = :id ORDER BY g.updatedAt DESC",
classOf[GameRecord],
).setParameter("id", playerId)
.setFirstResult(offset)
.setMaxResults(limit)
.getResultList
.asScala
.toList
def findByPlayerIdRunning(playerId: String, offset: Int, limit: Int): List[GameRecord] =
em.createQuery(
"SELECT g FROM GameRecord g WHERE g.whiteId = :id OR g.blackId = :id AND g.result = null ORDER BY g.updatedAt DESC",
classOf[GameRecord],
).setParameter("id", playerId)
.setFirstResult(offset)
.setMaxResults(limit)
.getResultList
.asScala
.toList
@@ -5,6 +5,7 @@ import jakarta.enterprise.context.ApplicationScoped
import jakarta.inject.Inject
import jakarta.ws.rs.*
import jakarta.ws.rs.core.{MediaType, Response}
import jakarta.ws.rs.DefaultValue
import scala.compiletime.uninitialized
@Path("/game")
@@ -22,3 +23,23 @@ class StoreGameResource:
repository
.findByGameId(gameId)
.fold(Response.status(404).build())(r => Response.ok(r).build())
@GET
@Path("/running/{playerId}")
@Produces(Array(MediaType.APPLICATION_JSON))
def getRunning(
@PathParam("playerId") playerId: String,
@QueryParam("offset") @DefaultValue("0") offset: Int,
@QueryParam("limit") @DefaultValue("20") limit: Int,
): Response =
Response.ok(repository.findByPlayerIdRunning(playerId, offset, limit)).build()
@GET
@Path("/history/{playerId}")
@Produces(Array(MediaType.APPLICATION_JSON))
def getHistory(
@PathParam("playerId") playerId: String,
@QueryParam("offset") @DefaultValue("0") offset: Int,
@QueryParam("limit") @DefaultValue("20") limit: Int,
): Response =
Response.ok(repository.findByPlayerId(playerId, offset, limit)).build()
@@ -41,6 +41,8 @@ class GameWritebackService:
record.clockMoveDeadline = event.clockMoveDeadline.map(java.lang.Long.valueOf).orNull
record.clockActiveColor = event.clockActiveColor.orNull
record.pendingDrawOffer = event.pendingDrawOffer.orNull
record.result = event.result.orNull
record.terminationReason = event.terminationReason.orNull
record.createdAt = Instant.now()
record.updatedAt = Instant.now()
repository.persist(record)
@@ -64,6 +66,8 @@ class GameWritebackService:
r.clockMoveDeadline = event.clockMoveDeadline.map(java.lang.Long.valueOf).orNull
r.clockActiveColor = event.clockActiveColor.orNull
r.pendingDrawOffer = event.pendingDrawOffer.orNull
r.result = event.result.orNull
r.terminationReason = event.terminationReason.orNull
r.updatedAt = Instant.now()
repository.merge(r)
case _ => ()