Compare commits

...

19 Commits

Author SHA1 Message Date
TeamCity
103b341488 ci: bump version to v4.21.0 2026-01-07 13:57:09 +00:00
6ef7401443 feat: Add logging to Gateway for pod synchronization and startup events 2026-01-07 14:54:26 +01:00
TeamCity
bbbbf33c41 ci: bump version to v4.20.0 2026-01-07 13:37:05 +00:00
dbad818fda feat: Add caching headers for env.js in Nginx configuration 2026-01-07 14:33:35 +01:00
TeamCity
5f8afbf236 ci: bump version to v4.19.0 2026-01-07 13:02:56 +00:00
3b7a1e3c64 feat: Update configuration files for CORS settings and add production environment 2026-01-07 14:00:12 +01:00
TeamCity
5af5785fa0 ci: bump version to v4.18.1 2026-01-07 12:19:47 +00:00
TeamCity
9510147806 ci: bump version to v4.18.0 2026-01-07 11:07:11 +00:00
e17f59e614 feat: Simplify Dockerfile by removing multi-stage build and adjusting file copy paths 2026-01-07 12:04:44 +01:00
8126b46a0a feat: Update build.sbt and Dockerfile for improved GitHub credentials handling 2026-01-07 11:52:00 +01:00
25dd9264b5 feat: Update build.sbt and Dockerfile for improved GitHub credentials handling 2026-01-07 11:47:31 +01:00
5f9ef2beb0 feat: Enhance Dockerfile with secret management for GitHub credentials 2026-01-07 11:40:10 +01:00
ccf993bff2 feat: Update Dockerfile for multi-platform support and add nginx configuration 2026-01-07 11:24:00 +01:00
1d05bd43b4 feat: Add GatewayModule for enhanced module configuration 2026-01-07 10:17:13 +01:00
3e067346ff feat: Update Dockerfile for multi-platform support and add nginx configuration 2026-01-06 16:49:27 +01:00
b2527ed041 feat: Update Dockerfile for multi-platform support and add nginx configuration 2026-01-06 16:38:33 +01:00
TeamCity
06d150b8c5 ci: bump version to v4.17.0 2026-01-06 11:18:43 +00:00
0cbac61b4a fix: Fixed dockerfile 2026-01-06 12:13:32 +01:00
088dda8d52 feat: Enhance win effects and animations in OfflineView component 2026-01-06 12:07:08 +01:00
15 changed files with 174 additions and 28 deletions

View File

@@ -259,3 +259,36 @@
### Features ### Features
* **ui:** FRO-36 PWA ([#110](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/issues/110)) ([e4384ee](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/commit/e4384ee8945ac462fe1f3580215117e0a438f71a)) * **ui:** FRO-36 PWA ([#110](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/issues/110)) ([e4384ee](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/commit/e4384ee8945ac462fe1f3580215117e0a438f71a))
## (2026-01-06)
### Features
* Enhance win effects and animations in OfflineView component ([088dda8](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/commit/088dda8d528edb9f3fd420e7e69eb44144d39eff))
## (2026-01-07)
### Features
* Add GatewayModule for enhanced module configuration ([1d05bd4](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/commit/1d05bd43b482564636345322a2cecf58f7d229d0))
* Enhance Dockerfile with secret management for GitHub credentials ([5f9ef2b](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/commit/5f9ef2beb0a6e7d5e8caf436f545bbd78a6b242e))
* Simplify Dockerfile by removing multi-stage build and adjusting file copy paths ([e17f59e](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/commit/e17f59e614a0060b70514c2337ac6fd84688546e))
* Update build.sbt and Dockerfile for improved GitHub credentials handling ([8126b46](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/commit/8126b46a0a1649d2b20b31975603f7610fafd18b))
* Update build.sbt and Dockerfile for improved GitHub credentials handling ([25dd926](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/commit/25dd9264b57ffafc5b01587ff5dda2e2188b5fbd))
* Update Dockerfile for multi-platform support and add nginx configuration ([ccf993b](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/commit/ccf993bff25b4551a70c1f0263695f828df15a02))
* Update Dockerfile for multi-platform support and add nginx configuration ([3e06734](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/commit/3e067346ffe3bdc62dc936ea8e79ae9293d86351))
* Update Dockerfile for multi-platform support and add nginx configuration ([b2527ed](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/commit/b2527ed041568d20f880515b406fe0b0e10c12c1))
## (2026-01-07)
## (2026-01-07)
### Features
* Update configuration files for CORS settings and add production environment ([3b7a1e3](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/commit/3b7a1e3c646d870134d8d06b4962498b0e282cbd))
## (2026-01-07)
### Features
* Add caching headers for env.js in Nginx configuration ([dbad818](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/commit/dbad818fdaeb237a05f583e5402773a4339e7aa1))
## (2026-01-07)
### Features
* Add logging to Gateway for pod synchronization and startup events ([6ef7401](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/commit/6ef74014430673e725245bf37e44c5b90b81abb3))

View File

@@ -1,26 +1,4 @@
# === Stage 1: Build the Play application === FROM --platform=$TARGETPLATFORM eclipse-temurin:22-jre-alpine
FROM sbtscala/scala-sbt:eclipse-temurin-alpine-22_36_1.10.3_3.5.1 AS builder
WORKDIR /app
# Install Node.js and Less CSS preprocessor
USER root
RUN apk add --no-cache nodejs npm && \
npm install -g less
# Cache dependencies first
COPY project ./project
COPY build.sbt ./
RUN sbt -Dscoverage.skip=true update
# Copy the rest of the code
COPY . .
# Build the app and stage it
RUN sbt -Dscoverage.skip=true clean stage
# === Stage 2: Runtime image ===
FROM eclipse-temurin:21-jre-alpine
# Install Argon2 CLI and libraries # Install Argon2 CLI and libraries
RUN apk add --no-cache bash argon2 argon2-libs RUN apk add --no-cache bash argon2 argon2-libs
@@ -28,7 +6,7 @@ RUN apk add --no-cache bash argon2 argon2-libs
WORKDIR /opt/playapp WORKDIR /opt/playapp
# Copy staged Play build # Copy staged Play build
COPY --from=builder /app/knockoutwhistweb/target/universal/stage /opt/playapp COPY ./knockoutwhistweb/target/universal/stage /opt/playapp
# Expose the default Play port # Expose the default Play port
EXPOSE 9000 EXPOSE 9000

View File

@@ -0,0 +1,15 @@
meta {
name: Request Status
type: http
seq: 1
}
get {
url: {{host}}/status
body: none
auth: inherit
}
settings {
encodeUrl: true
}

View File

@@ -0,0 +1,8 @@
meta {
name: Login
seq: 3
}
auth {
mode: inherit
}

View File

@@ -0,0 +1,3 @@
vars {
host: https://knockout.janis-eccarius.de/api
}

View File

@@ -1,5 +1,12 @@
ThisBuild / scalaVersion := "3.5.1" ThisBuild / scalaVersion := "3.5.1"
credentials += Credentials(
"GitHub Package Registry",
"maven.pkg.github.com",
sys.env.getOrElse("GITHUB_USER", sys.error("GITHUB_USER not set")),
sys.env.getOrElse("GITHUB_TOKEN", sys.error("GITHUB_TOKEN not set"))
)
lazy val commonSettings = Seq( lazy val commonSettings = Seq(
libraryDependencies += "org.scalactic" %% "scalactic" % "3.2.19", libraryDependencies += "org.scalactic" %% "scalactic" % "3.2.19",
libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.19" % "test", libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.19" % "test",
@@ -35,12 +42,16 @@ lazy val knockoutwhistweb = project.in(file("knockoutwhistweb"))
.enablePlugins(PlayScala) .enablePlugins(PlayScala)
.dependsOn(knockoutwhist % "compile->compile;test->test") .dependsOn(knockoutwhist % "compile->compile;test->test")
.settings( .settings(
resolvers += "GitHub Packages" at "https://maven.pkg.github.com/16Janis12/KnockOutWhist-Web",
commonSettings, commonSettings,
libraryDependencies += "org.scalatestplus.play" %% "scalatestplus-play" % "7.0.2" % Test, libraryDependencies += "org.scalatestplus.play" %% "scalatestplus-play" % "7.0.2" % Test,
libraryDependencies += "de.mkammerer" % "argon2-jvm" % "2.12", libraryDependencies += "de.mkammerer" % "argon2-jvm" % "2.12",
libraryDependencies += "com.auth0" % "java-jwt" % "4.5.0", libraryDependencies += "com.auth0" % "java-jwt" % "4.5.0",
libraryDependencies += "com.github.ben-manes.caffeine" % "caffeine" % "3.2.3", libraryDependencies += "com.github.ben-manes.caffeine" % "caffeine" % "3.2.3",
libraryDependencies += "tools.jackson.module" %% "jackson-module-scala" % "3.0.2", libraryDependencies += "tools.jackson.module" %% "jackson-module-scala" % "3.0.2",
libraryDependencies += "de.janis" % "knockoutwhist-data" % "1.0-SNAPSHOT",
JsEngineKeys.engineType := JsEngineKeys.EngineType.Node JsEngineKeys.engineType := JsEngineKeys.EngineType.Node
) )

View File

@@ -0,0 +1,51 @@
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.Logger
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) {
private val logger = Logger(getClass.getName)
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(() => {
logger.info("Syncing pod with Redis")
createPod()
})
logger.info("Gateway started")
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
)
}
}

View File

@@ -21,6 +21,8 @@ object PodManager {
private val userSession: mutable.Map[User, String] = mutable.Map() private val userSession: mutable.Map[User, String] = mutable.Map()
private val injector: Injector = Guice.createInjector(KnockOutWebConfigurationModule()) private val injector: Injector = Guice.createInjector(KnockOutWebConfigurationModule())
private[logic] var redis: Option[Gateway] = None
def createGame( def createGame(
host: User, host: User,
name: String, name: String,
@@ -35,7 +37,8 @@ object PodManager {
host = host host = host
) )
sessions += (gameLobby.id -> gameLobby) sessions += (gameLobby.id -> gameLobby)
userSession += (host -> gameLobby.id) registerUserToGame(host, gameLobby.id)
redis.foreach(gateway => gateway.syncPod())
gameLobby gameLobby
} }
@@ -46,6 +49,7 @@ object PodManager {
def registerUserToGame(user: User, gameId: String): Boolean = { def registerUserToGame(user: User, gameId: String): Boolean = {
if (sessions.contains(gameId)) { if (sessions.contains(gameId)) {
userSession += (user -> gameId) userSession += (user -> gameId)
redis.foreach(gateway => gateway.syncPod())
true true
} else { } else {
false false
@@ -54,6 +58,7 @@ object PodManager {
def unregisterUserFromGame(user: User): Unit = { def unregisterUserFromGame(user: User): Unit = {
userSession.remove(user) userSession.remove(user)
redis.foreach(gateway => gateway.redis.invalidateUser(user.id.toString))
} }
def identifyGameOfUser(user: User): Option[GameLobby] = { def identifyGameOfUser(user: User): Option[GameLobby] = {
@@ -65,9 +70,12 @@ object PodManager {
private[logic] def removeGame(gameId: String): Unit = { private[logic] def removeGame(gameId: String): Unit = {
sessions.remove(gameId) sessions.remove(gameId)
redis.foreach(gateway => gateway.redis.invalidateGame(gameId))
// Also remove all user sessions associated with this game // Also remove all user sessions associated with this game
userSession.filterInPlace((_, v) => v != gameId) 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
} }

View File

@@ -106,6 +106,9 @@ class GameLobby private(
} }
if (sessionOpt.get.host) { if (sessionOpt.get.host) {
logic.invoke(SessionClosed()) logic.invoke(SessionClosed())
for (session <- users.values) {
PodManager.unregisterUserFromGame(session.user)
}
users.clear() users.clear()
PodManager.removeGame(id) PodManager.removeGame(id)
return return

View File

@@ -0,0 +1,10 @@
package modules
import com.google.inject.AbstractModule
import logic.Gateway
class GatewayModule extends AbstractModule {
override def configure(): Unit = {
bind(classOf[Gateway]).asEagerSingleton()
}
}

View File

@@ -17,7 +17,7 @@ auth {
play.filters.enabled += "play.filters.cors.CORSFilter" play.filters.enabled += "play.filters.cors.CORSFilter"
play.filters.cors { play.filters.cors {
allowedOrigins = ["http://localhost:5173", "http://localhost:3000"] allowedOrigins = ["http://localhost:5173"]
allowedCredentials = true allowedCredentials = true
allowedHttpMethods = ["GET", "POST", "PUT", "DELETE", "OPTIONS"] allowedHttpMethods = ["GET", "POST", "PUT", "DELETE", "OPTIONS"]
allowedHttpHeaders = ["Accept", "Content-Type", "Origin", "X-Requested-With"] allowedHttpHeaders = ["Accept", "Content-Type", "Origin", "X-Requested-With"]

View File

@@ -0,0 +1,14 @@
include "application.conf"
play.http.secret.key="zg8^v0R*:7-m.>^8T2B1q)sE3MV_9=M{K9zx8,<3}"
play.http.context="/api"
play.modules.enabled += "modules.GatewayModule"
play.filters.cors {
allowedOrigins = ["https://knockout.janis-eccarius.de"]
allowedCredentials = true
allowedHttpMethods = ["GET", "POST", "PUT", "DELETE", "OPTIONS"]
allowedHttpHeaders = ["Accept", "Content-Type", "Origin", "X-Requested-With"]
}

View File

@@ -0,0 +1,12 @@
include "application.conf"
play.http.context="/api"
play.modules.enabled += "modules.GatewayModule"
play.filters.cors {
allowedOrigins = ["https://st.knockout.janis-eccarius.de"]
allowedCredentials = true
allowedHttpMethods = ["GET", "POST", "PUT", "DELETE", "OPTIONS"]
allowedHttpHeaders = ["Accept", "Content-Type", "Origin", "X-Requested-With"]
}

View File

@@ -1,3 +1,3 @@
MAJOR=4 MAJOR=4
MINOR=16 MINOR=21
PATCH=0 PATCH=0