revamping domain

This commit is contained in:
2022-10-30 18:36:23 -05:00
parent b8ac227199
commit 689de35c3b
16 changed files with 161 additions and 105 deletions

View File

@@ -2,7 +2,7 @@
namespace Shogi.Contracts.Api; namespace Shogi.Contracts.Api;
public class ReadSessionResponse public class ReadSessionResponse
{ {
public Session Session { get; set; } public Session Session { get; set; }
} }

View File

@@ -1,10 +1,10 @@
namespace Shogi.Contracts.Types namespace Shogi.Contracts.Types;
public class Session
{ {
public class Session public string Player1 { get; set; } = string.Empty;
{ public string? Player2 { get; set; }
public string Player1 { get; set; } public string SessionName { get; set; } = string.Empty;
public string? Player2 { get; set; } public bool GameOver { get; set; }
public string SessionName { get; set; } public BoardState BoardState { get; set; }
public BoardState BoardState { get; set; }
}
} }

View File

@@ -1,24 +1,14 @@
CREATE PROCEDURE [session].[CreateSession] CREATE PROCEDURE [session].[CreateSession]
@SessionName [session].[SessionName],
@Player1Name [user].[UserName],
@InitialBoardStateDocument [session].[JsonDocument] @InitialBoardStateDocument [session].[JsonDocument]
AS AS
BEGIN BEGIN
SET NOCOUNT ON
SET NOCOUNT ON INSERT INTO [session].[Session] ([Name], BoardState, Player1Id)
SET XACT_ABORT ON
BEGIN TRANSACTION
INSERT INTO [session].[Session] ([Name], Player1Id)
SELECT SELECT
@SessionName, JSON_VALUE(@InitialBoardStateDocument, '$.Name'),
@InitialBoardStateDocument,
Id Id
FROM [user].[User] FROM [user].[User]
WHERE [Name] = @Player1Name; WHERE [Name] = JSON_VALUE(@InitialBoardStateDocument, '$.Player1');
INSERT INTO [session].[BoardState] (Document, SessionId)
VALUES
(@InitialBoardStateDocument, SCOPE_IDENTITY());
COMMIT
END END

View File

@@ -1,12 +1,13 @@
CREATE PROCEDURE [session].[ReadAllSessionsMetadata] CREATE PROCEDURE [session].[ReadAllSessionsMetadata]
AS AS
BEGIN
SET NOCOUNT ON;
SET NOCOUNT ON; SELECT
[Name],
SELECT CASE
[Name], WHEN Player2Id IS NULL THEN 1
CASE ELSE 2
WHEN Player2Id IS NULL THEN 1 END AS PlayerCount
ELSE 2 FROM [session].[Session];
END AS PlayerCount END
FROM [session].[Session];

View File

@@ -0,0 +1,17 @@
CREATE PROCEDURE [session].[ReadSession]
@Name [session].[SessionName]
AS
BEGIN
SET NOCOUNT ON
SELECT
sess.[Name],
GameOver,
BoardState,
p1.[Name] as Player1,
p2.[Name] as Player2
FROM [session].[Session] sess
INNER JOIN [user].[User] p1 on sess.Player1Id = p1.Id
LEFT JOIN [user].[User] p2 on sess.Player2Id = p2.Id
WHERE sess.[Name] = @Name;
END

View File

@@ -1,10 +0,0 @@
CREATE TABLE [session].[BoardState]
(
[Id] BIGINT NOT NULL PRIMARY KEY IDENTITY,
[Document] NVARCHAR(max) NOT NULL,
[SessionId] BIGINT NOT NULL,
CONSTRAINT [Document must be json] CHECK (isjson(Document)=1),
CONSTRAINT FK_BoardState_Session FOREIGN KEY (SessionId)
REFERENCES [session].[Session] (Id) ON DELETE CASCADE ON UPDATE CASCADE
)

View File

@@ -1,12 +1,13 @@
CREATE TABLE [session].[Session] CREATE TABLE [session].[Session]
( (
Id BIGINT NOT NULL PRIMARY KEY IDENTITY, Id BIGINT NOT NULL PRIMARY KEY IDENTITY,
[Name] [session].[SessionName] NOT NULL UNIQUE,
Created DATETIMEOFFSET NOT NULL DEFAULT SYSDATETIMEOFFSET(), Created DATETIMEOFFSET NOT NULL DEFAULT SYSDATETIMEOFFSET(),
GameOver BIT NOT NULL DEFAULT 0, DomainDocument [session].[JsonDocument] NOT NULL,
[Name] AS JSON_VALUE(DomainDocument, '$.Name') UNIQUE,
Player1Id BIGINT NOT NULL, Player1Id BIGINT NOT NULL,
Player2Id BIGINT NULL, Player2Id BIGINT NULL,
CONSTRAINT [BoardState must be json] CHECK (isjson(DomainDocument)=1),
CONSTRAINT FK_Player1_User FOREIGN KEY (Player1Id) REFERENCES [user].[User] (Id) CONSTRAINT FK_Player1_User FOREIGN KEY (Player1Id) REFERENCES [user].[User] (Id)
ON DELETE CASCADE ON DELETE CASCADE
ON UPDATE CASCADE, ON UPDATE CASCADE,

View File

@@ -71,7 +71,6 @@
<Build Include="Session\session.sql" /> <Build Include="Session\session.sql" />
<Build Include="User\user.sql" /> <Build Include="User\user.sql" />
<Build Include="Session\Tables\Session.sql" /> <Build Include="Session\Tables\Session.sql" />
<Build Include="Session\Tables\BoardState.sql" />
<Build Include="Session\Stored Procedures\CreateSession.sql" /> <Build Include="Session\Stored Procedures\CreateSession.sql" />
<Build Include="Session\Stored Procedures\CreateBoardState.sql" /> <Build Include="Session\Stored Procedures\CreateBoardState.sql" />
<Build Include="User\Tables\User.sql" /> <Build Include="User\Tables\User.sql" />
@@ -84,6 +83,7 @@
<Build Include="User\Tables\LoginPlatform.sql" /> <Build Include="User\Tables\LoginPlatform.sql" />
<None Include="Post Deployment\Scripts\PopulateLoginPlatforms.sql" /> <None Include="Post Deployment\Scripts\PopulateLoginPlatforms.sql" />
<Build Include="Session\Stored Procedures\UpdateSession.sql" /> <Build Include="Session\Stored Procedures\UpdateSession.sql" />
<Build Include="Session\Stored Procedures\ReadSession.sql" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PostDeploy Include="Post Deployment\Script.PostDeployment.sql" /> <PostDeploy Include="Post Deployment\Script.PostDeployment.sql" />

View File

@@ -8,11 +8,11 @@ namespace Shogi.Domain;
/// The board is always from Player1's perspective. /// The board is always from Player1's perspective.
/// [0,0] is the lower-left position, [8,8] is the higher-right position /// [0,0] is the lower-left position, [8,8] is the higher-right position
/// </summary> /// </summary>
public sealed class Session public sealed class ShogiBoard
{ {
private readonly StandardRules rules; private readonly StandardRules rules;
public Session(string name, BoardState initialState, string player1, string? player2 = null) public ShogiBoard(string name, BoardState initialState, string player1, string? player2 = null)
{ {
Name = name; Name = name;
Player1 = player1; Player1 = player1;
@@ -23,8 +23,6 @@ public sealed class Session
public BoardState BoardState { get; } public BoardState BoardState { get; }
public string Name { get; } public string Name { get; }
public string Player1 { get; }
public string? Player2 { get; }
/// <summary> /// <summary>
/// Move a piece from a board position to another board position, potentially capturing an opponents piece. Respects all rules of the game. /// Move a piece from a board position to another board position, potentially capturing an opponents piece. Respects all rules of the game.

View File

@@ -37,7 +37,7 @@ public class SessionController : ControllerBase
{ {
var userId = User.GetShogiUserId(); var userId = User.GetShogiUserId();
if (string.IsNullOrWhiteSpace(userId)) return this.Unauthorized(); if (string.IsNullOrWhiteSpace(userId)) return this.Unauthorized();
var session = new Domain.Session(request.Name, Domain.BoardState.StandardStarting, userId); var session = new Domain.ShogiBoard(request.Name, Domain.BoardState.StandardStarting, userId);
try try
{ {
await sessionRepository.CreateSession(session); await sessionRepository.CreateSession(session);
@@ -161,6 +161,13 @@ public class SessionController : ControllerBase
}); });
} }
[HttpGet("{name}")]
public async Task<ActionResult<ReadSessionResponse>> GetSession(string name)
{
await Task.CompletedTask;
return new ReadSessionResponse();
}
//[HttpPut("{sessionName}")] //[HttpPut("{sessionName}")]
//public async Task<IActionResult> PutJoinSession([FromRoute] string sessionName) //public async Task<IActionResult> PutJoinSession([FromRoute] string sessionName)
//{ //{

View File

@@ -3,24 +3,25 @@ using System;
namespace Shogi.Api.Repositories.CouchModels namespace Shogi.Api.Repositories.CouchModels
{ {
public abstract class CouchDocument [Obsolete]
{ public abstract class CouchDocument
[JsonProperty("_id")] public string Id { get; set; } {
[JsonProperty("_rev")] public string? RevisionId { get; set; } [JsonProperty("_id")] public string Id { get; set; }
public WhichDocumentType DocumentType { get; } [JsonProperty("_rev")] public string? RevisionId { get; set; }
public DateTimeOffset CreatedDate { get; set; } public WhichDocumentType DocumentType { get; }
public DateTimeOffset CreatedDate { get; set; }
public CouchDocument(WhichDocumentType documentType) public CouchDocument(WhichDocumentType documentType)
: this(string.Empty, documentType, DateTimeOffset.UtcNow) { } : this(string.Empty, documentType, DateTimeOffset.UtcNow) { }
public CouchDocument(string id, WhichDocumentType documentType) public CouchDocument(string id, WhichDocumentType documentType)
: this(id, documentType, DateTimeOffset.UtcNow) { } : this(id, documentType, DateTimeOffset.UtcNow) { }
public CouchDocument(string id, WhichDocumentType documentType, DateTimeOffset createdDate) public CouchDocument(string id, WhichDocumentType documentType, DateTimeOffset createdDate)
{ {
Id = id; Id = id;
DocumentType = documentType; DocumentType = documentType;
CreatedDate = createdDate; CreatedDate = createdDate;
} }
} }
} }

View File

@@ -0,0 +1,11 @@
namespace Shogi.Api.Repositories.Dto
{
public class SessionDto
{
public string Name { get; set; }
public string Player1 { get; set; }
public string Player2 { get; set; }
public bool GameOver { get; set; }
public string BoardState { get; set; }
}
}

View File

@@ -6,23 +6,33 @@ namespace Shogi.Api.Repositories;
public class QueryRepository : IQueryRespository public class QueryRepository : IQueryRespository
{ {
private readonly string connectionString; private readonly string connectionString;
public QueryRepository(IConfiguration configuration) public QueryRepository(IConfiguration configuration)
{ {
connectionString = configuration.GetConnectionString("ShogiDatabase"); connectionString = configuration.GetConnectionString("ShogiDatabase");
} }
public async Task<IEnumerable<SessionMetadata>> ReadAllSessionsMetadata() public async Task<IEnumerable<SessionMetadata>> ReadAllSessionsMetadata()
{ {
using var connection = new SqlConnection(connectionString); using var connection = new SqlConnection(connectionString);
return await connection.QueryAsync<SessionMetadata>( return await connection.QueryAsync<SessionMetadata>(
"session.ReadAllSessionsMetadata", "session.ReadAllSessionsMetadata",
commandType: System.Data.CommandType.StoredProcedure); commandType: System.Data.CommandType.StoredProcedure);
} }
public async Task<SessionMetadata?> ReadSession(string name)
{
using var connection = new SqlConnection(connectionString);
var results = await connection.QueryAsync<SessionMetadata>(
"session.ReadSession",
commandType: System.Data.CommandType.StoredProcedure);
return results.SingleOrDefault();
}
} }
public interface IQueryRespository public interface IQueryRespository
{ {
Task<IEnumerable<SessionMetadata>> ReadAllSessionsMetadata(); Task<IEnumerable<SessionMetadata>> ReadAllSessionsMetadata();
Task<SessionMetadata?> ReadSession(string name);
} }

View File

@@ -1,4 +1,5 @@
using Dapper; using Dapper;
using Shogi.Api.Repositories.Dto;
using Shogi.Domain; using Shogi.Domain;
using System.Data; using System.Data;
using System.Data.SqlClient; using System.Data.SqlClient;
@@ -8,30 +9,48 @@ namespace Shogi.Api.Repositories;
public class SessionRepository : ISessionRepository public class SessionRepository : ISessionRepository
{ {
private readonly string connectionString; private readonly string connectionString;
public SessionRepository(IConfiguration configuration) public SessionRepository(IConfiguration configuration)
{ {
connectionString = configuration.GetConnectionString("ShogiDatabase"); connectionString = configuration.GetConnectionString("ShogiDatabase");
} }
public async Task CreateSession(Session session) public async Task CreateSession(ShogiBoard session, string player1)
{ {
var initialBoardState = JsonSerializer.Serialize(session.BoardState); var initialBoardState = JsonSerializer.Serialize(session.BoardState);
using var connection = new SqlConnection(connectionString); using var connection = new SqlConnection(connectionString);
await connection.ExecuteAsync( await connection.ExecuteAsync(
"session.CreateSession", "session.CreateSession",
new new
{ {
SessionName = session.Name, SessionName = session.Name,
Player1Name = session.Player1, Player1Name = player1,
InitialBoardStateDocument = initialBoardState InitialBoardStateDocument = initialBoardState
}, },
commandType: CommandType.StoredProcedure); commandType: CommandType.StoredProcedure);
} }
public async Task ReadSession(string name)
{
using var connection = new SqlConnection(connectionString);
var results = await connection.QueryAsync<SessionDto>(
"session.ReadSession",
commandType: CommandType.StoredProcedure);
if (!results.Any())
{
return null;
}
var dto = results.First();
return new Session(
name: dto.Name,
initialState: JsonSerializer.Deserialize< dto.BoardState)
}
} }
public interface ISessionRepository public interface ISessionRepository
{ {
Task CreateSession(Session session); Task CreateSession(ShogiBoard session);
} }

View File

@@ -11,6 +11,17 @@
<UserSecretsId>973a1f5f-ef25-4f1c-a24d-b0fc7d016ab8</UserSecretsId> <UserSecretsId>973a1f5f-ef25-4f1c-a24d-b0fc7d016ab8</UserSecretsId>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<Compile Remove="Repositories\CouchModels\**" />
<Content Remove="Repositories\CouchModels\**" />
<EmbeddedResource Remove="Repositories\CouchModels\**" />
<None Remove="Repositories\CouchModels\**" />
</ItemGroup>
<ItemGroup>
<Compile Remove="Repositories\GameboardRepository.cs" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Azure.Extensions.AspNetCore.Configuration.Secrets" Version="1.2.2" /> <PackageReference Include="Azure.Extensions.AspNetCore.Configuration.Secrets" Version="1.2.2" />
<PackageReference Include="Azure.Identity" Version="1.6.1" /> <PackageReference Include="Azure.Identity" Version="1.6.1" />

View File

@@ -457,6 +457,6 @@ namespace Shogi.Domain.UnitTests
board.InCheck.Should().Be(WhichPlayer.Player2); board.InCheck.Should().Be(WhichPlayer.Player2);
} }
private static Session MockSession() => new Session("Test Session", BoardState.StandardStarting, "Test P1", "Test P2"); private static ShogiBoard MockSession() => new ShogiBoard("Test Session", BoardState.StandardStarting, "Test P1", "Test P2");
} }
} }