Replace custom socket implementation with SignalR.

Replace MSAL and custom cookie auth with Microsoft.Identity.EntityFramework
Also some UI redesign to accommodate different login experience.
This commit is contained in:
2024-08-25 03:46:44 +00:00
parent d688afaeae
commit 51d234d871
172 changed files with 3857 additions and 4045 deletions

View File

@@ -0,0 +1,3 @@
-- This is so I don't have to remember the type used in the dbo.AspNetUsers table for the Id column.
CREATE TYPE [dbo].[AspNetUsersId]
FROM NVARCHAR(450) NOT NULL;

View File

@@ -4,4 +4,11 @@
--CREATE ROLE db_executor
--GRANT EXECUTE To db_executor
-- Give Shogi.Api user permission to db_executor, db_datareader, db_datawriter
-- Give Shogi.Api user permission to db_executor, db_datareader, db_datawriter
/**
* Local setup instructions, in order:
* 1. To setup the Shogi database, use the dacpac process in visual studio with the Shogi.Database project.
* 2. To setup the Entity Framework users database, run this powershell command using Shogi.Api as the target project: dotnet ef database update
*/

View File

@@ -10,6 +10,5 @@ Post-Deployment Script Template
--------------------------------------------------------------------------------------
*/
:r .\Scripts\PopulateLoginPlatforms.sql
:r .\Scripts\PopulatePieces.sql
:r .\Scripts\EnableSnapshotIsolationLevel.sql

View File

@@ -1,16 +0,0 @@
DECLARE @LoginPlatforms TABLE (
[Platform] NVARCHAR(20)
)
INSERT INTO @LoginPlatforms ([Platform])
VALUES
('Guest'),
('Microsoft');
MERGE [user].[LoginPlatform] as t
USING @LoginPlatforms as s
ON t.[Platform] = s.[Platform]
WHEN NOT MATCHED THEN
INSERT ([Platform])
VALUES (s.[Platform]);

View File

@@ -0,0 +1,18 @@
CREATE FUNCTION [session].[MaxNewSessionsPerUser]() RETURNS INT
AS
BEGIN
DECLARE @MaxNewSessionsCreatedByAnyOneUser INT;
WITH CountOfNewSessionsPerPlayer AS
(
SELECT COUNT(*) as TotalNewSessions
FROM [session].[Session]
WHERE Player2Id IS NULL
GROUP BY Player1Id
)
SELECT @MaxNewSessionsCreatedByAnyOneUser = MAX(CountOfNewSessionsPerPlayer.TotalNewSessions)
FROM CountOfNewSessionsPerPlayer
RETURN @MaxNewSessionsCreatedByAnyOneUser
END

View File

@@ -1,9 +1,9 @@
CREATE PROCEDURE [session].[CreateMove]
@To VARCHAR(2),
@From VARCHAR(2) = NULL,
@To VARCHAR(2),
@From VARCHAR(2) = NULL,
@IsPromotion BIT = 0,
@PieceFromHand NVARCHAR(13) = NULL,
@SessionName [session].[SessionName]
@PieceFromHand NVARCHAR(13) = NULL,
@SessionId [session].[SessionSurrogateKey]
AS
BEGIN
@@ -13,11 +13,6 @@ BEGIN
BEGIN TRANSACTION
DECLARE @SessionId BIGINT = 0;
SELECT @SessionId = Id
FROM [session].[Session]
WHERE [Name] = @SessionName;
DECLARE @PieceIdFromhand INT = NULL;
SELECT @PieceIdFromhand = Id
FROM [session].[Piece]

View File

@@ -1,12 +1,13 @@
CREATE PROCEDURE [session].[CreateSession]
@Name [session].[SessionName],
@Player1Name [user].[UserName]
@Id [session].[SessionSurrogateKey],
@Player1Id [dbo].[AspNetUsersId]
AS
BEGIN
SET NOCOUNT ON
INSERT INTO [session].[Session] ([Name], Player1Id)
SELECT @Name, Id
FROM [user].[User]
WHERE [Name] = @Player1Name
INSERT INTO [session].[Session]
([Id], Player1Id)
VALUES
(@Id, @Player1Id)
END

View File

@@ -1,5 +1,5 @@
CREATE PROCEDURE [session].[DeleteSession]
@Name [session].[SessionName]
@Id [session].[SessionSurrogateKey]
AS
DELETE FROM [session].[Session] WHERE [Name] = @Name;
DELETE FROM [session].[Session] WHERE [Id] = @Id;

View File

@@ -1,5 +1,5 @@
CREATE PROCEDURE [session].[ReadSession]
@Name [session].[SessionName]
@Id [session].[SessionSurrogateKey]
AS
BEGIN
SET NOCOUNT ON -- Performance boost
@@ -10,13 +10,12 @@ BEGIN
-- Session
SELECT
sess.[Name],
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;
Id,
Player1Id,
Player2Id,
CreatedDate
FROM [session].[Session]
WHERE Id = @Id;
-- Player moves
SELECT
@@ -27,7 +26,7 @@ BEGIN
FROM [session].[Move] mv
INNER JOIN [session].[Session] sess ON sess.Id = mv.SessionId
LEFT JOIN [session].Piece piece on piece.Id = mv.PieceIdFromHand
WHERE sess.[Name] = @Name;
WHERE sess.[Id] = @Id;
COMMIT
END

View File

@@ -1,31 +0,0 @@
CREATE PROCEDURE [session].[ReadSessionPlayerCount]
@PlayerName [user].UserName
AS
BEGIN
SET NOCOUNT ON;
DECLARE @PlayerId as BIGINT;
SELECT @PlayerId = Id
FROM [user].[User]
WHERE [Name] = @PlayerName;
-- Result set of sessions which @PlayerName participates in.
SELECT
[Name],
CASE
WHEN Player2Id IS NULL THEN 1
ELSE 2
END AS PlayerCount
FROM [session].[Session]
WHERE Player1Id = @PlayerId OR Player2Id = @PlayerId;
-- Result set of sessions which @PlayerName does not participate in.
SELECT
[Name],
CASE
WHEN Player2Id IS NULL THEN 1
ELSE 2
END AS PlayerCount
FROM [session].[Session]
WHERE Player1Id <> @PlayerId AND ISNULL(Player2Id, 0) <> @PlayerId;
END

View File

@@ -0,0 +1,21 @@
CREATE PROCEDURE [session].[ReadSessionsMetadata]
@PlayerId [dbo].[AspNetUsersId]
AS
BEGIN
SET NOCOUNT ON;
-- Read all sessions, in this order:
-- 1. sessions created by the logged-in user
-- 2. any other sessions the logged-in user participates in
-- 3. all other sessions
SELECT
Id, Player1Id, Player2Id, [Session].CreatedDate,
case
when Player1Id = @PlayerId then 0
when Player2Id = @PlayerId then 1
else 2
end as OrderBy
FROM [session].[Session]
Order By OrderBy ASC, CreatedDate DESC
END

View File

@@ -1,13 +0,0 @@
CREATE PROCEDURE [session].[ReadUsersBySession]
@SessionName [session].[SessionName]
AS
SELECT
p1.[Name] as Player1Name,
p1.DisplayName as Player1DisplayName,
p2.[Name] as Player2Name,
p2.DisplayName as Player2Displayname
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] = @SessionName;

View File

@@ -1,16 +1,13 @@
CREATE PROCEDURE [session].[SetPlayer2]
@SessionName [session].[SessionName],
@Player2Name [user].[UserName] NULL
@SessionId [session].[SessionSurrogateKey],
@PlayerId [dbo].[AspNetUsersId]
AS
BEGIN
SET NOCOUNT ON;
DECLARE @player2Id BIGINT;
SELECT @player2Id = Id FROM [user].[User] WHERE [Name] = @Player2Name;
UPDATE sess
SET Player2Id = @player2Id
FROM [session].[Session] sess
WHERE sess.[Name] = @SessionName;
UPDATE [session].[Session]
SET Player2Id = @PlayerId
FROM [session].[Session]
WHERE Id = @SessionId;
END

View File

@@ -1,11 +1,11 @@
CREATE TABLE [session].[Move]
(
[Id] INT NOT NULL PRIMARY KEY IDENTITY,
[SessionId] BIGINT NOT NULL,
[To] VARCHAR(2) NOT NULL,
[From] VARCHAR(2) NULL,
[Id] INT NOT NULL PRIMARY KEY IDENTITY,
[SessionId] [session].[SessionSurrogateKey] NOT NULL,
[To] VARCHAR(2) NOT NULL,
[From] VARCHAR(2) NULL,
[PieceIdFromHand] INT NULL,
[IsPromotion] BIT DEFAULT 0
[IsPromotion] BIT DEFAULT 0
CONSTRAINT [Cannot end where you start]
CHECK ([From] <> [To]),

View File

@@ -1,16 +1,8 @@
CREATE TABLE [session].[Session]
(
Id BIGINT NOT NULL PRIMARY KEY IDENTITY,
[Name] [session].[SessionName] UNIQUE,
Player1Id BIGINT NOT NULL,
Player2Id BIGINT NULL,
Created DATETIMEOFFSET NOT NULL DEFAULT SYSDATETIMEOFFSET(),
CONSTRAINT FK_Player1_User FOREIGN KEY (Player1Id) REFERENCES [user].[User] (Id)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT FK_Player2_User FOREIGN KEY (Player2Id) REFERENCES [user].[User] (Id)
ON DELETE NO ACTION
ON UPDATE NO ACTION
Id [session].[SessionSurrogateKey] PRIMARY KEY,
Player1Id [dbo].[AspNetUsersId] NOT NULL,
Player2Id [dbo].[AspNetUsersId] NULL,
[CreatedDate] DATETIMEOFFSET NOT NULL DEFAULT SYSDATETIMEOFFSET(),
CONSTRAINT [CK_Session_LimitedNewSessions] CHECK ([session].MaxNewSessionsPerUser() < 4),
)

View File

@@ -1,2 +0,0 @@
CREATE TYPE [session].[SessionName]
FROM nvarchar(50) NOT NULL

View File

@@ -0,0 +1,2 @@
CREATE TYPE [session].[SessionSurrogateKey]
FROM CHAR(36) NOT NULL

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<Operations Version="1.0" xmlns="http://schemas.microsoft.com/sqlserver/dac/Serialization/2012/02">
<Operation Name="Rename Refactor" Key="3cbf39d6-79cd-48fe-b8e3-dab0060d1092" ChangeDateTime="08/17/2024 03:58:12">
<Property Name="ElementName" Value="[session].[Session].[Name]" />
<Property Name="ElementType" Value="SqlSimpleColumn" />
<Property Name="ParentElementName" Value="[session].[Session]" />
<Property Name="ParentElementType" Value="SqlTable" />
<Property Name="NewName" Value="SessionId" />
</Operation>
<Operation Name="Rename Refactor" Key="6ec1662d-c600-4558-af11-95a94acd23d9" ChangeDateTime="08/17/2024 15:59:09">
<Property Name="ElementName" Value="[session].[Session].[Created]" />
<Property Name="ElementType" Value="SqlSimpleColumn" />
<Property Name="ParentElementName" Value="[session].[Session]" />
<Property Name="ParentElementType" Value="SqlTable" />
<Property Name="NewName" Value="CreatedDate" />
</Operation>
</Operations>

View File

@@ -58,36 +58,27 @@
<ItemGroup>
<Folder Include="Properties" />
<Folder Include="Session" />
<Folder Include="User" />
<Folder Include="Session\Tables" />
<Folder Include="Session\Stored Procedures" />
<Folder Include="User\Tables" />
<Folder Include="Session\Types" />
<Folder Include="User\Types" />
<Folder Include="User\StoredProcedures" />
<Folder Include="Post Deployment" />
<Folder Include="Post Deployment\Scripts" />
<Folder Include="Session\Functions" />
</ItemGroup>
<ItemGroup>
<Build Include="Session\session.sql" />
<Build Include="User\user.sql" />
<Build Include="Session\Tables\Session.sql" />
<Build Include="Session\Stored Procedures\CreateSession.sql" />
<Build Include="User\Tables\User.sql" />
<Build Include="Session\Types\SessionName.sql" />
<Build Include="User\Types\UserName.sql" />
<Build Include="User\StoredProcedures\CreateUser.sql" />
<Build Include="Session\Stored Procedures\ReadSessionPlayerCount.sql" />
<Build Include="User\StoredProcedures\ReadUser.sql" />
<Build Include="User\Tables\LoginPlatform.sql" />
<None Include="Post Deployment\Scripts\PopulateLoginPlatforms.sql" />
<Build Include="Session\Types\SessionSurrogateKey.sql" />
<Build Include="Session\Stored Procedures\SetPlayer2.sql" />
<Build Include="Session\Stored Procedures\ReadSession.sql" />
<Build Include="Session\Tables\Move.sql" />
<Build Include="Session\Tables\Piece.sql" />
<Build Include="Session\Stored Procedures\DeleteSession.sql" />
<Build Include="Session\Stored Procedures\CreateMove.sql" />
<Build Include="Session\Stored Procedures\ReadUsersBySession.sql" />
<Build Include="Session\Stored Procedures\ReadSessionsMetadata.sql" />
<Build Include="AspNetUsersId.sql" />
<Build Include="Session\Functions\MaxNewSessionsPerUser.sql" />
</ItemGroup>
<ItemGroup>
<PostDeploy Include="Post Deployment\Script.PostDeployment.sql" />
@@ -97,4 +88,7 @@
<None Include="Post Deployment\Scripts\EnableSnapshotIsolationLevel.sql" />
<None Include="FirstTimeSetup.sql" />
</ItemGroup>
<ItemGroup>
<RefactorLog Include="Shogi.Database.refactorlog" />
</ItemGroup>
</Project>

View File

@@ -1,14 +0,0 @@
CREATE PROCEDURE [user].[CreateUser]
@Name [user].[UserName],
@DisplayName NVARCHAR(100),
@Platform NVARCHAR(20)
AS
BEGIN
SET NOCOUNT ON
INSERT INTO [user].[User] ([Name], DisplayName, [Platform])
VALUES
(@Name, @DisplayName, @Platform);
END

View File

@@ -1,11 +0,0 @@
CREATE PROCEDURE [user].[ReadUser]
@Name [user].[UserName]
AS
BEGIN
SELECT
[Name] as Id,
DisplayName,
[Platform]
FROM [user].[User]
WHERE [Name] = @Name;
END

View File

@@ -1,4 +0,0 @@
CREATE TABLE [user].[LoginPlatform]
(
[Platform] NVARCHAR(20) NOT NULL PRIMARY KEY
)

View File

@@ -1,12 +0,0 @@
CREATE TABLE [user].[User]
(
[Id] BIGINT NOT NULL PRIMARY KEY IDENTITY, -- TODO: Consider using user.UserName as the PK to avoid confusing "Id" in the database vs "Id" in the domain model.
[Name] [user].[UserName] NOT NULL UNIQUE,
[DisplayName] NVARCHAR(100) NOT NULL,
[Platform] NVARCHAR(20) NOT NULL,
[CreatedDate] DATETIMEOFFSET DEFAULT SYSDATETIMEOFFSET()
CONSTRAINT User_Platform FOREIGN KEY ([Platform]) References [user].[LoginPlatform] ([Platform])
ON DELETE CASCADE
ON UPDATE CASCADE
)

View File

@@ -1,2 +0,0 @@
CREATE TYPE [user].[UserName]
FROM nvarchar(100) NOT NULL

View File

@@ -1 +0,0 @@
CREATE SCHEMA [user]