From b2527ed041568d20f880515b406fe0b0e10c12c1 Mon Sep 17 00:00:00 2001 From: Janis Date: Tue, 6 Jan 2026 16:38:33 +0100 Subject: [PATCH] feat: Update Dockerfile for multi-platform support and add nginx configuration --- Dockerfile | 4 +- build.sbt | 4 ++ knockoutwhistfrontend | 2 +- knockoutwhistweb/app/logic/Gateway.scala | 45 +++++++++++++++++++ knockoutwhistweb/app/logic/PodManager.scala | 10 ++++- .../app/logic/game/GameLobby.scala | 3 ++ knockoutwhistweb/conf/application.conf | 2 +- 7 files changed, 65 insertions(+), 5 deletions(-) create mode 100644 knockoutwhistweb/app/logic/Gateway.scala diff --git a/Dockerfile b/Dockerfile index e2adafc..2442511 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # === Stage 1: Build the Play application === -FROM sbtscala/scala-sbt:eclipse-temurin-alpine-22_36_1.10.3_3.5.1 AS builder +FROM --platform=$BUILDPLATFORM sbtscala/scala-sbt:eclipse-temurin-alpine-22_36_1.10.3_3.5.1 AS builder WORKDIR /app @@ -21,7 +21,7 @@ COPY . . RUN sbt -Dscoverage.skip=true clean stage # === Stage 2: Runtime image === -FROM eclipse-temurin:21-jre-alpine +FROM --platform=$TARGETPLATFORM eclipse-temurin:22-jre-alpine # Install Argon2 CLI and libraries RUN apk add --no-cache bash argon2 argon2-libs diff --git a/build.sbt b/build.sbt index 53846e6..475cda6 100644 --- a/build.sbt +++ b/build.sbt @@ -35,12 +35,16 @@ lazy val knockoutwhistweb = project.in(file("knockoutwhistweb")) .enablePlugins(PlayScala) .dependsOn(knockoutwhist % "compile->compile;test->test") .settings( + + resolvers += "GitHub Packages" at "https://maven.pkg.github.com/16Janis12/KnockOutWhist-Web", + commonSettings, libraryDependencies += "org.scalatestplus.play" %% "scalatestplus-play" % "7.0.2" % Test, libraryDependencies += "de.mkammerer" % "argon2-jvm" % "2.12", libraryDependencies += "com.auth0" % "java-jwt" % "4.5.0", libraryDependencies += "com.github.ben-manes.caffeine" % "caffeine" % "3.2.3", libraryDependencies += "tools.jackson.module" %% "jackson-module-scala" % "3.0.2", + libraryDependencies += "de.janis" % "knockoutwhist-data" % "1.0-SNAPSHOT", JsEngineKeys.engineType := JsEngineKeys.EngineType.Node ) diff --git a/knockoutwhistfrontend b/knockoutwhistfrontend index db5c70d..e972f1c 160000 --- a/knockoutwhistfrontend +++ b/knockoutwhistfrontend @@ -1 +1 @@ -Subproject commit db5c70d02a646b83ba6e4043e28fc0dc9f76fb04 +Subproject commit e972f1c4321ec2633e2989dbdaa714774632c495 diff --git a/knockoutwhistweb/app/logic/Gateway.scala b/knockoutwhistweb/app/logic/Gateway.scala new file mode 100644 index 0000000..ddb9d8d --- /dev/null +++ b/knockoutwhistweb/app/logic/Gateway.scala @@ -0,0 +1,45 @@ +package logic + +import de.knockoutwhist.data.Pod +import de.knockoutwhist.data.redis.RedisManager +import org.apache.pekko.actor.ActorSystem +import org.redisson.config.Config +import play.api.inject.ApplicationLifecycle + +import java.util.UUID +import javax.inject.* +import scala.concurrent.ExecutionContext +import scala.jdk.CollectionConverters.* + +@Singleton +class Gateway @Inject()( + lifecycle: ApplicationLifecycle, + actorSystem: ActorSystem + )(implicit ec: ExecutionContext) { + + val redis: RedisManager = { + val config: Config = Config() + config.useSingleServer.setAddress("valkey://" + sys.env.getOrElse("VALKEY_HOST", "localhost") + ":" + sys.env.getOrElse("VALKEY_PORT", "6379")) + RedisManager(config) + } + + redis.continuousSyncPod(() => { + createPod() + }) + + def syncPod(): Unit = { + redis.syncPod(createPod()) + } + + private def createPod(): Pod = { + Pod( + UUID.randomUUID().toString, + PodManager.podName, + PodManager.podIp, + 9000, + PodManager.getAllGameIds().asJava, + PodManager.allBoundUsers().asJava + ) + } + +} diff --git a/knockoutwhistweb/app/logic/PodManager.scala b/knockoutwhistweb/app/logic/PodManager.scala index e00979c..b2c8386 100644 --- a/knockoutwhistweb/app/logic/PodManager.scala +++ b/knockoutwhistweb/app/logic/PodManager.scala @@ -21,6 +21,8 @@ object PodManager { private val userSession: mutable.Map[User, String] = mutable.Map() private val injector: Injector = Guice.createInjector(KnockOutWebConfigurationModule()) + private[logic] var redis: Option[Gateway] = None + def createGame( host: User, name: String, @@ -35,7 +37,8 @@ object PodManager { host = host ) sessions += (gameLobby.id -> gameLobby) - userSession += (host -> gameLobby.id) + registerUserToGame(host, gameLobby.id) + redis.foreach(gateway => gateway.syncPod()) gameLobby } @@ -46,6 +49,7 @@ object PodManager { def registerUserToGame(user: User, gameId: String): Boolean = { if (sessions.contains(gameId)) { userSession += (user -> gameId) + redis.foreach(gateway => gateway.syncPod()) true } else { false @@ -54,6 +58,7 @@ object PodManager { def unregisterUserFromGame(user: User): Unit = { userSession.remove(user) + redis.foreach(gateway => gateway.redis.invalidateUser(user.id.toString)) } def identifyGameOfUser(user: User): Option[GameLobby] = { @@ -65,9 +70,12 @@ object PodManager { private[logic] def removeGame(gameId: String): Unit = { sessions.remove(gameId) + redis.foreach(gateway => gateway.redis.invalidateGame(gameId)) // Also remove all user sessions associated with this game userSession.filterInPlace((_, v) => v != gameId) } + private[logic] def getAllGameIds(): List[String] = sessions.keys.toList + private[logic] def allBoundUsers(): List[String] = userSession.keys.map(_.id.toString).toList } diff --git a/knockoutwhistweb/app/logic/game/GameLobby.scala b/knockoutwhistweb/app/logic/game/GameLobby.scala index 23b7aa6..24b4588 100644 --- a/knockoutwhistweb/app/logic/game/GameLobby.scala +++ b/knockoutwhistweb/app/logic/game/GameLobby.scala @@ -106,6 +106,9 @@ class GameLobby private( } if (sessionOpt.get.host) { logic.invoke(SessionClosed()) + for (session <- users.values) { + PodManager.unregisterUserFromGame(session.user) + } users.clear() PodManager.removeGame(id) return diff --git a/knockoutwhistweb/conf/application.conf b/knockoutwhistweb/conf/application.conf index 70674f6..25592e5 100644 --- a/knockoutwhistweb/conf/application.conf +++ b/knockoutwhistweb/conf/application.conf @@ -17,7 +17,7 @@ auth { play.filters.enabled += "play.filters.cors.CORSFilter" play.filters.cors { - allowedOrigins = ["http://localhost:5173", "http://localhost:3000"] + allowedOrigins = ["http://localhost:5173", "http://localhost:3000", "http://localhost:8081"] allowedCredentials = true allowedHttpMethods = ["GET", "POST", "PUT", "DELETE", "OPTIONS"] allowedHttpHeaders = ["Accept", "Content-Type", "Origin", "X-Requested-With"]