using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Shogi.Api.Extensions; using Shogi.Api.Managers; using Shogi.Api.Repositories; using Shogi.Contracts.Api; using Shogi.Contracts.Socket; using Shogi.Contracts.Types; using System.Data.SqlClient; namespace Shogi.Api.Controllers; [ApiController] [Route("[controller]")] [Authorize] public class SessionsController : ControllerBase { private readonly ISocketConnectionManager communicationManager; private readonly IModelMapper mapper; private readonly ISessionRepository sessionRepository; private readonly IQueryRespository queryRespository; private readonly ILogger logger; public SessionsController( ISocketConnectionManager communicationManager, IModelMapper mapper, ISessionRepository sessionRepository, IQueryRespository queryRespository, ILogger logger) { this.communicationManager = communicationManager; this.mapper = mapper; this.sessionRepository = sessionRepository; this.queryRespository = queryRespository; this.logger = logger; } [HttpPost] public async Task CreateSession([FromBody] CreateSessionCommand request) { var userId = User.GetShogiUserId(); var session = new Domain.Session(request.Name, userId); try { await sessionRepository.CreateSession(session); } catch (SqlException e) { logger.LogError(exception: e, message: "Uh oh"); return this.Conflict(); } await communicationManager.BroadcastToAll(new SessionCreatedSocketMessage()); return CreatedAtAction(nameof(CreateSession), new { sessionName = request.Name }, null); } [HttpDelete("{name}")] public async Task DeleteSession(string name) { var userId = User.GetShogiUserId(); var session = await sessionRepository.ReadSession(name); if (session == null) return this.NoContent(); if (session.Player1 == userId) { await sessionRepository.DeleteSession(name); return this.NoContent(); } return this.StatusCode(StatusCodes.Status403Forbidden, "Cannot delete sessions created by others."); } [HttpGet("PlayerCount")] public async Task> GetSessionsPlayerCount() { return Ok(await this.queryRespository.ReadSessionPlayerCount(this.User.GetShogiUserId())); } /// /// Fetch the session and latest board state. Also subscribe the user to socket events for this session. /// /// /// [HttpGet("{name}")] public async Task> GetSession(string name) { var session = await sessionRepository.ReadSession(name); if (session == null) return this.NotFound(); return new ReadSessionResponse { Session = new Session { BoardState = new BoardState { Board = session.Board.BoardState.State.ToContract(), Player1Hand = session.Board.BoardState.Player1Hand.ToContract(), Player2Hand = session.Board.BoardState.Player2Hand.ToContract(), PlayerInCheck = session.Board.BoardState.InCheck?.ToContract(), WhoseTurn = session.Board.BoardState.WhoseTurn.ToContract() }, Player1 = session.Player1, Player2 = session.Player2, SessionName = session.Name } }; } [HttpPatch("{name}/Join")] public async Task JoinSession(string name) { var session = await sessionRepository.ReadSession(name); if (session == null) return this.NotFound(); if (string.IsNullOrEmpty(session.Player2)) { session.AddPlayer2(User.GetShogiUserId()); await sessionRepository.SetPlayer2(name, User.GetShogiUserId()); await communicationManager.BroadcastToAll(new SessionJoinedByPlayerSocketMessage(session.Name)); return this.Ok(); } return this.Conflict("This game already has two players."); } [HttpPatch("{sessionName}/Move")] public async Task Move([FromRoute] string sessionName, [FromBody] MovePieceCommand command) { var userId = User.GetShogiUserId(); var session = await sessionRepository.ReadSession(sessionName); if (session == null) return this.NotFound("Shogi session does not exist."); if (!session.IsSeated(userId)) return this.StatusCode(StatusCodes.Status403Forbidden, "Player is not a member of the Shogi session."); try { if (command.PieceFromHand.HasValue) { session.Board.Move(command.PieceFromHand.Value.ToDomain(), command.To); } else { session.Board.Move(command.From!, command.To, command.IsPromotion ?? false); } } catch (InvalidOperationException e) { return this.Conflict(e.Message); } await sessionRepository.CreateMove(sessionName, command); // Send socket message to both players so their clients know that new board state is available. await communicationManager.BroadcastToPlayers( new PlayerHasMovedMessage { PlayerName = userId, SessionName = session.Name, }, session.Player1, session.Player2); return this.NoContent(); } }