Compare commits

...

6 Commits

Author SHA1 Message Date
TeamCity
421f769cb6 ci: bump version to v4.13.0 2025-12-10 14:19:44 +00:00
bd7a055a09 feat(api): FRO-14 Create Game (#107)
Added functionality to create Game so that it creates a game in the Backend

Co-authored-by: LQ63 <lkhermann@web.de>
Reviewed-on: #107
Reviewed-by: Janis <janis-e@gmx.de>
Co-authored-by: lq64 <lq@blackhole.local>
Co-committed-by: lq64 <lq@blackhole.local>
2025-12-10 15:16:53 +01:00
TeamCity
e2a2b56174 ci: bump version to v4.12.0 2025-12-10 13:15:50 +00:00
2a29ca8cdd feat: FRO-20 Create scoreboard component (#106)
Reviewed-on: #106
Co-authored-by: Janis <janis.e.20@gmx.de>
Co-committed-by: Janis <janis.e.20@gmx.de>
2025-12-10 14:12:48 +01:00
TeamCity
266cbe7509 ci: bump version to v4.11.0 2025-12-10 10:47:18 +00:00
e8b31b1748 feat: FRO-2 Implement Login Component (#105)
Reviewed-on: #105
Reviewed-by: lq64 <lq@blackhole.local>
Co-authored-by: Janis <janis.e.20@gmx.de>
Co-committed-by: Janis <janis.e.20@gmx.de>
2025-12-10 11:43:51 +01:00
13 changed files with 63 additions and 44 deletions

1
.gitignore vendored
View File

@@ -134,6 +134,7 @@ target
/.project /.project
/.settings /.settings
/RUNNING_PID /RUNNING_PID
/knockoutwhistwebfrontend/
/knockoutwhist/ /knockoutwhist/
/knockoutwhistweb/.g8/ /knockoutwhistweb/.g8/
/knockoutwhistweb/.bsp/ /knockoutwhistweb/.bsp/

View File

@@ -229,3 +229,18 @@
### Features ### Features
* BAC-27 Implemented endpoint which returns information about the current state ([#103](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/issues/103)) ([dd5e8e6](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/commit/dd5e8e65e55f02a7618b3c60e8fc7087774e5106)) * BAC-27 Implemented endpoint which returns information about the current state ([#103](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/issues/103)) ([dd5e8e6](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/commit/dd5e8e65e55f02a7618b3c60e8fc7087774e5106))
## (2025-12-10)
### Features
* FRO-2 Implement Login Component ([#105](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/issues/105)) ([e8b31b1](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/commit/e8b31b174819b5f033034501856c4b1189c4c4ee))
## (2025-12-10)
### Features
* FRO-20 Create scoreboard component ([#106](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/issues/106)) ([2a29ca8](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/commit/2a29ca8cdd3ef55f6f66f00b5e7727e1b1af1458))
## (2025-12-10)
### Features
* **api:** FRO-14 Create Game ([#107](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/issues/107)) ([bd7a055](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/commit/bd7a055a0944a1c5219f21bb080bf658229f49e9))

View File

@@ -23,12 +23,12 @@ class AuthAction @Inject()(val sessionManager: SessionManager, val parser: BodyP
case Some(user) => case Some(user) =>
block(new AuthenticatedRequest(user, request)) block(new AuthenticatedRequest(user, request))
case None => case None =>
Future.successful(Results.Redirect(routes.UserController.login())) Future.successful(Results.Unauthorized)
} }
} }
protected def getUserFromSession(request: RequestHeader): Option[User] = { protected def getUserFromSession(request: RequestHeader): Option[User] = {
val session = request.cookies.get("sessionId") val session = request.cookies.get("accessToken")
if (session.isDefined) if (session.isDefined)
return sessionManager.getUserBySession(session.get.value) return sessionManager.getUserBySession(session.get.value)
None None

View File

@@ -44,8 +44,7 @@ class MainMenuController @Inject()(
) )
Ok(Json.obj( Ok(Json.obj(
"status" -> "success", "status" -> "success",
"redirectUrl" -> routes.IngameController.game(gameLobby.id).url, "gameId" -> gameLobby.id,
"content" -> IngameController.returnInnerHTML(gameLobby, gameLobby.logic.getCurrentState, request.user).toString
)) ))
} else { } else {
BadRequest(Json.obj( BadRequest(Json.obj(

View File

@@ -1,10 +1,13 @@
package controllers package controllers
import auth.{AuthAction, AuthenticatedRequest} import auth.{AuthAction, AuthenticatedRequest}
import dto.subDTO.UserDTO
import logic.user.{SessionManager, UserManager} import logic.user.{SessionManager, UserManager}
import model.users.User
import play.api.* import play.api.*
import play.api.libs.json.Json import play.api.libs.json.Json
import play.api.mvc.* import play.api.mvc.*
import play.api.mvc.Cookie.SameSite.{Lax, None, Strict}
import javax.inject.* import javax.inject.*
@@ -21,22 +24,6 @@ class UserController @Inject()(
val authAction: AuthAction val authAction: AuthAction
) extends BaseController { ) extends BaseController {
def login(): Action[AnyContent] = {
Action { implicit request =>
val session = request.cookies.get("sessionId")
if (session.isDefined) {
val possibleUser = sessionManager.getUserBySession(session.get.value)
if (possibleUser.isDefined) {
Redirect(routes.MainMenuController.mainMenu())
} else {
Ok(views.html.main("Login")(views.html.login.login()))
}
} else {
Ok(views.html.main("Login")(views.html.login.login()))
}
}
}
def login_Post(): Action[AnyContent] = { def login_Post(): Action[AnyContent] = {
Action { implicit request => Action { implicit request =>
val jsonBody = request.body.asJson val jsonBody = request.body.asJson
@@ -51,12 +38,17 @@ class UserController @Inject()(
val possibleUser = userManager.authenticate(username.get, password.get) val possibleUser = userManager.authenticate(username.get, password.get)
if (possibleUser.isDefined) { if (possibleUser.isDefined) {
Ok(Json.obj( Ok(Json.obj(
"status" -> "success", "user" -> Json.obj(
"redirectUrl" -> routes.MainMenuController.mainMenu().url, "id" -> possibleUser.get.id,
"content" -> views.html.mainmenu.creategame(possibleUser).toString "username" -> possibleUser.get.name
)).withCookies( )
Cookie("sessionId", sessionManager.createSession(possibleUser.get)) )).withCookies(Cookie(
) name = "accessToken",
value = sessionManager.createSession(possibleUser.get),
httpOnly = true,
secure = false,
sameSite = Some(Lax)
))
} else { } else {
Unauthorized("Invalid username or password") Unauthorized("Invalid username or password")
} }
@@ -65,14 +57,21 @@ class UserController @Inject()(
} }
} }
} }
def getUserInfo(): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] =>
val user: User = request.user
Ok(Json.obj(
"id" -> user.id,
"username" -> user.name
))
}
// Pass the request-handling function directly to authAction (no nested Action) def logoutPost(): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] =>
def logout(): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] => val sessionCookie = request.cookies.get("accessToken")
val sessionCookie = request.cookies.get("sessionId")
if (sessionCookie.isDefined) { if (sessionCookie.isDefined) {
sessionManager.invalidateSession(sessionCookie.get.value) sessionManager.invalidateSession(sessionCookie.get.value)
} }
Redirect(routes.UserController.login()).discardingCookies(DiscardingCookie("sessionId")) NoContent.discardingCookies(DiscardingCookie("accessToken"))
} }
} }

View File

@@ -28,7 +28,7 @@ object GameInfoDTO {
hand = selfPlayer.flatMap(_.currentHand()).map(HandDTO(_)), hand = selfPlayer.flatMap(_.currentHand()).map(HandDTO(_)),
playerQueue = PlayerQueueDTO(lobby.logic), playerQueue = PlayerQueueDTO(lobby.logic),
currentTrick = lobby.logic.getCurrentTrick.map(TrickDTO(_)), currentTrick = lobby.logic.getCurrentTrick.map(TrickDTO(_)),
currentRound = lobby.logic.getCurrentRound.map(RoundDTO(_)) currentRound = lobby.logic.getCurrentRound.map(r => RoundDTO(r, lobby.logic.getCurrentMatch))
) )
} }

View File

@@ -2,14 +2,16 @@ package dto.subDTO
import de.knockoutwhist.cards.Card import de.knockoutwhist.cards.Card
import de.knockoutwhist.cards.CardValue.Ace import de.knockoutwhist.cards.CardValue.Ace
import de.knockoutwhist.rounds.{Match, Round}
case class RoundDTO(trumpSuit: CardDTO, firstRound: Boolean, trickList: List[TrickDTO]) case class RoundDTO(trumpSuit: CardDTO, playersIn: Seq[PlayerDTO], firstRound: Boolean, trickList: List[TrickDTO])
object RoundDTO { object RoundDTO {
def apply(round: de.knockoutwhist.rounds.Round): RoundDTO = { def apply(round: Round, matchImpl: Option[Match]): RoundDTO = {
RoundDTO( RoundDTO(
trumpSuit = CardDTO(Card(Ace, round.trumpSuit)), trumpSuit = CardDTO(Card(Ace, round.trumpSuit)),
playersIn = matchImpl.map(_.playersIn.map(PlayerDTO(_))).getOrElse(Seq.empty),
firstRound = round.firstRound, firstRound = round.firstRound,
trickList = round.tricklist.map(trick => TrickDTO(trick)) trickList = round.tricklist.map(trick => TrickDTO(trick))
) )

View File

@@ -9,6 +9,7 @@ trait SessionManager {
def createSession(user: User): String def createSession(user: User): String
def getUserBySession(sessionId: String): Option[User] def getUserBySession(sessionId: String): Option[User]
def invalidateSession(sessionId: String): Unit def invalidateSession(sessionId: String): Unit

View File

@@ -45,15 +45,9 @@
<li><a class="dropdown-item disabled" href="#" tabindex="-1" aria-disabled="true"> <li><a class="dropdown-item disabled" href="#" tabindex="-1" aria-disabled="true">
Settings</a></li> Settings</a></li>
<li><hr class="dropdown-divider"></li> <li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="@routes.UserController.logout()">Logout</a></li>
</ul> </ul>
</li> </li>
</ul> </ul>
} else {
<div class="d-flex ms-auto">
<a class="btn btn-outline-primary me-2" href="@routes.UserController.login()">Login</a>
<a class="btn btn-primary" href="@routes.UserController.login()">Sign Up</a>
</div>
} }
</div> </div>

View File

@@ -13,3 +13,12 @@ auth {
publicKeyFile = ${?PUBLIC_KEY_FILE} publicKeyFile = ${?PUBLIC_KEY_FILE}
publicKeyPem = ${?PUBLIC_KEY_PEM} publicKeyPem = ${?PUBLIC_KEY_PEM}
} }
play.filters.enabled += "play.filters.cors.CORSFilter"
play.filters.cors {
allowedOrigins = ["http://localhost:5173"]
allowedCredentials = true
allowedHttpMethods = ["GET", "POST", "PUT", "DELETE", "OPTIONS"]
allowedHttpHeaders = ["Accept", "Content-Type", "Origin", "X-Requested-With"]
}

View File

@@ -18,10 +18,9 @@ POST /createGame controllers.MainMenuController.createGame()
POST /joinGame controllers.MainMenuController.joinGame() POST /joinGame controllers.MainMenuController.joinGame()
# User authentication routes # User authentication routes
GET /login controllers.UserController.login()
POST /login controllers.UserController.login_Post() POST /login controllers.UserController.login_Post()
POST /logout controllers.UserController.logoutPost()
GET /logout controllers.UserController.logout() GET /userInfo controllers.UserController.getUserInfo()
# In-game routes # In-game routes
GET /game/:id controllers.IngameController.game(id: String) GET /game/:id controllers.IngameController.game(id: String)

View File

@@ -1,3 +1,3 @@
MAJOR=4 MAJOR=4
MINOR=10 MINOR=13
PATCH=0 PATCH=0