using Gameboard.ShogiUI.Sockets.Extensions; using Gameboard.ShogiUI.Sockets.Managers; using Gameboard.ShogiUI.Sockets.Repositories; using Gameboard.ShogiUI.Sockets.ServiceModels.Api; using Gameboard.ShogiUI.Sockets.ServiceModels.Socket; using Gameboard.ShogiUI.Sockets.ServiceModels.Types; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Security.Claims; using System.Threading.Tasks; namespace Gameboard.ShogiUI.Sockets.Controllers { [ApiController] [Route("[controller]")] [Authorize(Roles = "Shogi")] public class GameController : ControllerBase { private readonly IGameboardManager gameboardManager; private readonly IGameboardRepository gameboardRepository; private readonly ISocketConnectionManager communicationManager; public GameController( IGameboardRepository repository, IGameboardManager manager, ISocketConnectionManager communicationManager) { gameboardManager = manager; gameboardRepository = repository; this.communicationManager = communicationManager; } [HttpPost("JoinCode")] public async Task PostGameInvitation([FromBody] PostGameInvitation request) { //var isPlayer1 = await gameboardManager.IsPlayer1(request.SessionName, userName); //if (isPlayer1) //{ // var code = await gameboardRepository.PostJoinCode(request.SessionName, userName); // return new CreatedResult("", new PostGameInvitationResponse(code)); //} //else //{ return new UnauthorizedResult(); //} } [AllowAnonymous] [HttpPost("GuestJoinCode")] public async Task PostGuestGameInvitation([FromBody] PostGuestGameInvitation request) { //var isGuest = gameboardManager.IsGuest(request.GuestId); //var isPlayer1 = gameboardManager.IsPlayer1(request.SessionName, request.GuestId); //if (isGuest && await isPlayer1) //{ // var code = await gameboardRepository.PostJoinCode(request.SessionName, request.GuestId); // return new CreatedResult("", new PostGameInvitationResponse(code)); //} //else //{ return new UnauthorizedResult(); //} } [HttpPost("{gameName}/Move")] public async Task PostMove([FromRoute] string gameName, [FromBody] PostMove request) { var user = await gameboardManager.ReadUser(User); var session = await gameboardRepository.ReadSession(gameName); if (session == null) { return NotFound(); } if (user == null || (session.Player1.Id != user.Id && session.Player2?.Id != user.Id)) { return Forbid("User is not seated at this game."); } var move = request.Move; var moveModel = move.PieceFromCaptured.HasValue ? new Models.Move(move.PieceFromCaptured.Value, move.To, move.IsPromotion) : new Models.Move(move.From!, move.To, move.IsPromotion); var moveSuccess = session.Shogi.Move(moveModel); if (moveSuccess) { var createSuccess = await gameboardRepository.CreateBoardState(session); if (!createSuccess) { throw new ApplicationException("Unable to persist board state."); } await communicationManager.BroadcastToPlayers(new MoveResponse { GameName = session.Name, PlayerName = user.Id }, session.Player1.Id, session.Player2?.Id); return Ok(); } return Conflict("Illegal move."); } // TODO: Use JWT tokens for guests so they can authenticate and use API routes, too. //[Route("")] //public async Task PostSession([FromBody] PostSession request) //{ // var model = new Models.Session(request.Name, request.IsPrivate, request.Player1, request.Player2); // var success = await repository.CreateSession(model); // if (success) // { // var message = new ServiceModels.Socket.Messages.CreateGameResponse(ServiceModels.Types.ClientAction.CreateGame) // { // Game = model.ToServiceModel(), // PlayerName = // } // var task = request.IsPrivate // ? communicationManager.BroadcastToPlayers(response, userName) // : communicationManager.BroadcastToAll(response); // return new CreatedResult("", null); // } // return new ConflictResult(); //} [HttpPost] public async Task PostSession([FromBody] PostSession request) { var user = await ReadUserOrThrow(); var session = new Models.SessionMetadata(request.Name, request.IsPrivate, user!); var success = await gameboardRepository.CreateSession(session); if (success) { try { await communicationManager.BroadcastToAll(new CreateGameResponse { Game = session.ToServiceModel(), PlayerName = user.Id }); } catch (Exception e) { Console.Error.WriteLine("Error broadcasting during PostSession"); } return Ok(); } return Conflict(); } /// /// Reads the board session and subscribes the caller to socket events for that session. /// [HttpGet("{gameName}")] public async Task GetSession([FromRoute] string gameName) { var user = await ReadUserOrThrow(); var session = await gameboardRepository.ReadSession(gameName); if (session == null) { return NotFound(); } var playerPerspective = WhichPerspective.Spectator; if (session.Player1.Id == user.Id) { playerPerspective = WhichPerspective.Player1; } else if (session.Player2?.Id == user.Id) { playerPerspective = WhichPerspective.Player2; } communicationManager.SubscribeToGame(session, user!.Id); var response = new GetSessionResponse() { Game = new Models.SessionMetadata(session).ToServiceModel(), BoardState = session.Shogi.ToServiceModel(), MoveHistory = session.Shogi.MoveHistory.Select(_ => _.ToServiceModel()).ToList(), PlayerPerspective = playerPerspective }; return new JsonResult(response); } [HttpGet] public async Task GetSessions() { var user = await ReadUserOrThrow(); var sessions = await gameboardRepository.ReadSessionMetadatas(); var sessionsJoinedByUser = sessions .Where(s => s.IsSeated(user)) .Select(s => s.ToServiceModel()) .ToList(); var sessionsNotJoinedByUser = sessions .Where(s => !s.IsSeated(user)) .Select(s => s.ToServiceModel()) .ToList(); return new GetSessionsResponse { PlayerHasJoinedSessions = new Collection(sessionsJoinedByUser), AllOtherSessions = new Collection(sessionsNotJoinedByUser) }; } [HttpPut("{gameName}")] public async Task PutJoinSession([FromRoute] string gameName) { var user = await ReadUserOrThrow(); var session = await gameboardRepository.ReadSessionMetaData(gameName); if (session == null) { return NotFound(); } if (session.Player2 != null) { return this.Conflict("This session already has two seated players and is full."); } session.SetPlayer2(user); var success = await gameboardRepository.UpdateSession(session); if (!success) return this.Problem(detail: "Unable to update session."); var opponentName = user.Id == session.Player1.Id ? session.Player2!.Id : session.Player1.Id; await communicationManager.BroadcastToPlayers(new JoinGameResponse { GameName = session.Name, PlayerName = user.Id }, opponentName); return Ok(); } private async Task ReadUserOrThrow() { var user = await gameboardManager.ReadUser(User); if (user == null) { throw new UnauthorizedAccessException("Unknown user claims."); } return user; } } }